diff --git a/.config/lychee.toml b/.config/lychee.toml index 1de9fcd559dd..b7bb6f0ce495 100644 --- a/.config/lychee.toml +++ b/.config/lychee.toml @@ -32,7 +32,6 @@ exclude = [ "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-try-runtime-node.parity-chains.parity.io/", "https://polkadot.network/the-path-of-a-parachain-block/", "https://research.web3.foundation/en/latest/polkadot/NPoS/3.%20Balancing.html", "https://research.web3.foundation/en/latest/polkadot/Token%20Economics.html#inflation-model", @@ -41,6 +40,7 @@ exclude = [ "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://try-runtime.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", diff --git a/.config/zepter.yaml b/.config/zepter.yaml index 24441e90b1a0..7a67ba2695cf 100644 --- a/.config/zepter.yaml +++ b/.config/zepter.yaml @@ -27,7 +27,7 @@ workflows: ] # The umbrella crate uses more features, so we to check those too: check_umbrella: - - [ $check.0, '--features=serde,experimental,runtime,with-tracing,tuples-96,with-tracing', '-p=polkadot-sdk' ] + - [ $check.0, '--features=serde,experimental,riscv,runtime,with-tracing,tuples-96,with-tracing', '-p=polkadot-sdk' ] # Same as `check_*`, but with the `--fix` flag. default: - [ $check.0, '--fix' ] diff --git a/.github/actions/cargo-check-runtimes/action.yml b/.github/actions/cargo-check-runtimes/action.yml new file mode 100644 index 000000000000..869f17661e4a --- /dev/null +++ b/.github/actions/cargo-check-runtimes/action.yml @@ -0,0 +1,22 @@ +name: 'cargo check runtimes' +description: 'Runs `cargo check` for every directory in provided root.' +inputs: + root: + description: "Root directory. Expected to contain several cargo packages inside." + required: true +runs: + using: "composite" + steps: + - name: Check + shell: bash + run: | + mkdir -p ~/.forklift + cp .forklift/config.toml ~/.forklift/config.toml + cd ${{ inputs.root }} + for directory in $(echo */); do + echo "_____Running cargo check for ${directory} ______"; + cd ${directory}; + pwd; + SKIP_WASM_BUILD=1 forklift cargo check --locked; + cd ..; + done diff --git a/.github/command-screnshot.png b/.github/command-screnshot.png deleted file mode 100644 index 1451fabca8b9..000000000000 Binary files a/.github/command-screnshot.png and /dev/null differ diff --git a/.github/commands-readme.md b/.github/commands-readme.md deleted file mode 100644 index 793524e056f8..000000000000 --- a/.github/commands-readme.md +++ /dev/null @@ -1,265 +0,0 @@ -# Running commands - -Command bot has been migrated, it is no longer a comment parser and now it is a GitHub action that works as a [`workflow_dispatch`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch) event. - -## How to run an action - -To run an action, you need to go to the [_actions tab_](https://github.com/paritytech/polkadot-sdk/actions) and pick the one you desire to run. - -The current available command actions are: - -- [Command FMT](https://github.com/paritytech/polkadot-sdk/actions/workflows/command-fmt.yml) -- [Command Update UI](https://github.com/paritytech/polkadot-sdk/actions/workflows/command-update-ui.yml) -- [Command Sync](https://github.com/paritytech/polkadot-sdk/actions/workflows/command-sync.yml) -- [Command Bench](https://github.com/paritytech/polkadot-sdk/actions/workflows/command-bench.yml) -- [Command Bench All](https://github.com/paritytech/polkadot-sdk/actions/workflows/command-bench-all.yml) -- [Command Bench Overhead](https://github.com/paritytech/polkadot-sdk/actions/workflows/command-bench-overhead.yml) - -You need to select the action, and click on the dropdown that says: `Run workflow`. It is located in the upper right. - -If this dropdown is not visible, you may not have permission to run the action. Contact IT for help. - -![command screenshot](command-screnshot.png) - -Each command will have the same two required values, but it could have more. - -GitHub's official documentation: [Manually running a workflow](https://docs.github.com/en/actions/using-workflows/manually-running-a-workflow) - -#### Running from CLI - -You can use [`gh cli`](https://cli.github.com/) to run the commands too. Refers to the [`gh workflow run`](https://cli.github.com/manual/gh_workflow_run) section from the documentation for more information. - -### Number of the Pull Request - -The number of the pull request. Required so the action can fetch the correct branch and comment if it fails. - -## Action configurations - -### FMT - -For FMT you only need the PR number. - -You can use the following [`gh cli`](https://cli.github.com/) inside the repo: - -```bash -gh workflow run command-fmt.yml -f pr=1000 -``` - -### Update UI - -For Update UI you only need the PR number. - -You can use the following [`gh cli`](https://cli.github.com/) inside the repo: - -```bash -gh workflow run command-update-ui.yml -f pr=1000 -``` - -### Bench - -Runs `benchmark pallet` or `benchmark overhead` against your PR and commits back updated weights. - -Posible combinations based on the `benchmark` dropdown. - -- `substrate-pallet`: Pallet Benchmark for Substrate for specific pallet - - Requires `Subcommand` to be `pallet` - - Requires `Runtime` to be `dev` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Target Directory` to be `substrate` -- `polkadot-pallet`: Pallet Benchmark for Polkadot for specific pallet - - Requires `Subcommand` to be one of the following: - - `pallet` - - `xcm` - - Requires `Runtime` to be one of the following: - - `rococo` - - `westend` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Target Directory` to be `polkadot` -- `cumulus-assets`: Pallet Benchmark for Cumulus assets - - Requires `Subcommand` to be one of the following: - - `pallet` - - `xcm` - - Requires `Runtime` to be one of the following: - - `asset-hub-westend` - - `asset-hub-rococo` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Runtime Dir` to be `assets` - - Requires `Target Directory` to be `cumulus` -- `cumulus-collectives`: Pallet Benchmark for Cumulus collectives - - Requires `Subcommand` to be one of the following: - - `pallet` - - `xcm` - - Requires `Runtime` to be `collectives-westend` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Runtime Dir` to be `collectives` - - Requires `Target Directory` to be `cumulus` -- `cumulus-coretime`: Pallet Benchmark for Cumulus coretime - - Requires `Subcommand` to be one of the following: - - `pallet` - - `xcm` - - Requires `Runtime` to be one of the following: - - `coretime-rococo` - - `coretime-westend` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Runtime Dir` to be `coretime` - - Requires `Target Directory` to be `cumulus` -- `cumulus-bridge-hubs`: Pallet Benchmark for Cumulus bridge-hubs - - Requires `Subcommand` to be one of the following: - - `pallet` - - `xcm` - - Requires `Runtime` to be one of the following: - - `bridge-hub-rococo` - - `bridge-hub-westend` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Runtime Dir` to be `bridge-hub` - - Requires `Target Directory` to be `cumulus` -- `cumulus-contracts`: Pallet Benchmark for Cumulus contracts - - Requires `Subcommand` to be one of the following: - - `pallet` - - `xcm` - - Requires `Runtime` to be one `contracts-rococo` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Runtime Dir` to be `contracts` - - Requires `Target Directory` to be `cumulus` -- `cumulus-glutton`: Pallet Benchmark for Cumulus glutton - - Requires `Subcommand` to be `pallet` - - Requires `Runtime` to be one of the following: - - `glutton-westend` - - `glutton-westend-dev-1300` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Runtime Dir` to be `glutton` - - Requires `Target Directory` to be `cumulus` -- `cumulus-starters`: Pallet Benchmark for Cumulus starters - - Requires `Subcommand` to be one of the following: - - `pallet` - - `xcm` - - Requires `Runtime` to be one of the following: - - `seedling` - - `shell` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Runtime Dir` to be `starters` - - Requires `Target Directory` to be `cumulus` -- `cumulus-people`: Pallet Benchmark for Cumulus people - - Requires `Subcommand` to be one of the following: - - `pallet` - - `xcm` - - Requires `Runtime` to be one of the following: - - `people-westend` - - `people-rococo` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Runtime Dir` to be `people` - - Requires `Target Directory` to be `cumulus` -- `cumulus-testing`: Pallet Benchmark for Cumulus testing - - Requires `Subcommand` to be one of the following: - - `pallet` - - `xcm` - - Requires `Runtime` to be one of the following: - - `penpal` - - `rococo-parachain` - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` - - Requires `Runtime Dir` to be `testing` - - Requires `Target Directory` to be `cumulus` - -You can use the following [`gh cli`](https://cli.github.com/) inside the repo: - -```bash -gh workflow run command-bench.yml -f pr=1000 -f benchmark=polkadot-pallet -f subcommand=pallet -f runtime=rococo -f pallet=pallet_name -f target_dir=polkadot -``` - -### Bench-all - -This is a wrapper to run `bench` for all pallets. - -Posible combinations based on the `benchmark` dropdown. - -- `pallet`: Benchmark for Substrate/Polkadot/Cumulus/Trappist for specific pallet - - Requires field `Pallet` to have an input that applies to `^([a-z_]+)([:]{2}[a-z_]+)?$` -- `substrate`: Pallet + Overhead + Machine Benchmark for Substrate for all pallets - - Requires `Target Directory` to be `substrate` -- `polkadot`: Pallet + Overhead Benchmark for Polkadot - - Requires `Runtime` to be one of the following: - - `rococo` - - `westend` - - Requires `Target Directory` to be `polkadot` -- `cumulus`: Pallet Benchmark for Cumulus - - Requires `Runtime` to be one of the following: - - `rococo` - - `westend` - - `asset-hub-kusama` - - `asset-hub-polkadot` - - `asset-hub-rococo` - - `asset-hub-westend` - - `bridge-hub-kusama` - - `bridge-hub-polkadot` - - `bridge-hub-rococo` - - `bridge-hub-westend` - - `collectives-polkadot` - - `collectives-westend` - - `coretime-rococo` - - `coretime-westend` - - `contracts-rococo` - - `glutton-kusama` - - `glutton-westend` - - `people-rococo` - - `people-westend` - - Requires `Target Directory` to be `cumulus` - -You can use the following [`gh cli`](https://cli.github.com/) inside the repo: - -```bash -gh workflow run command-bench-all.yml -f pr=1000 -f benchmark=pallet -f pallet=pallet_name -f target_dir=polkadot -f runtime=rococo -``` - -### Bench-overhead - -Run benchmarks overhead and commit back results to PR. - -Posible combinations based on the `benchmark` dropdown. - -- `default`: Runs `benchmark overhead` and commits back to PR the updated `extrinsic_weights.rs` files - - Requires `Runtime` to be one of the following: - - `rococo` - - `westend` - - Requires `Target directory` to be `polkadot` -- `substrate`: Runs `benchmark overhead` and commits back to PR the updated `extrinsic_weights.rs` files - - Requires `Target directory` to be `substrate` -- `cumulus`: Runs `benchmark overhead` and commits back to PR the updated `extrinsic_weights.rs` files - - Requires `Runtime` to be one of the following: - - `asset-hub-rococo` - - `asset-hub-westend` - - Requires `Target directory` to be `cumulus` - -You can use the following [`gh cli`](https://cli.github.com/) inside the repo: - -```bash -gh workflow run command-bench-overheard.yml -f pr=1000 -f benchmark=substrate -f runtime=rococo -f target_dir=substrate -``` - -### Sync - -Run sync and commit back results to PR. - -Posible combinations based on the `benchmark` dropdown. - -- `chain` - - Requires one of the following: - - `rococo` - - `westend` -- `sync-type` - - Requires one of the following: - - `warp` - - `full` - - `fast` - - `fast-unsafe` - -You can use the following [`gh cli`](https://cli.github.com/) inside the repo: - -```bash -gh workflow run command-sync.yml -f pr=1000 -f chain=rococo -f sync-type=full -``` - -## How to modify an action - -If you want to modify an action and test it, you can do by simply pushing your changes and then selecting your branch in the `Use worflow from` option. - -This will use a file from a specified branch. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 120000 index 000000000000..7b6b3498755f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1 @@ +../docs/contributor/PULL_REQUEST_TEMPLATE.md \ No newline at end of file diff --git a/.github/scripts/cmd/_help.py b/.github/scripts/cmd/_help.py new file mode 100644 index 000000000000..8ad49dad8461 --- /dev/null +++ b/.github/scripts/cmd/_help.py @@ -0,0 +1,26 @@ +import argparse + +""" + +Custom help action for argparse, it prints the help message for the main parser and all subparsers. + +""" + + +class _HelpAction(argparse._HelpAction): + def __call__(self, parser, namespace, values, option_string=None): + parser.print_help() + + # retrieve subparsers from parser + subparsers_actions = [ + action for action in parser._actions + if isinstance(action, argparse._SubParsersAction)] + # there will probably only be one subparser_action, + # but better save than sorry + for subparsers_action in subparsers_actions: + # get all subparsers and print help + for choice, subparser in subparsers_action.choices.items(): + print("\n### Command '{}'".format(choice)) + print(subparser.format_help()) + + parser.exit() diff --git a/.github/scripts/cmd/cmd.py b/.github/scripts/cmd/cmd.py new file mode 100755 index 000000000000..63bd6a2795aa --- /dev/null +++ b/.github/scripts/cmd/cmd.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 + +import os +import sys +import json +import argparse +import _help + +_HelpAction = _help._HelpAction + +f = open('.github/workflows/runtimes-matrix.json', 'r') +runtimesMatrix = json.load(f) + +runtimeNames = list(map(lambda x: x['name'], runtimesMatrix)) + +common_args = { + '--continue-on-fail': {"action": "store_true", "help": "Won't exit(1) on failed command and continue with next steps. "}, + '--quiet': {"action": "store_true", "help": "Won't print start/end/failed messages in PR"}, + '--clean': {"action": "store_true", "help": "Clean up the previous bot's & author's comments in PR"}, + '--image': {"help": "Override docker image '--image docker.io/paritytech/ci-unified:latest'"}, +} + +parser = argparse.ArgumentParser(prog="/cmd ", description='A command runner for polkadot-sdk repo', add_help=False) +parser.add_argument('--help', action=_HelpAction, help='help for help if you need some help') # help for help +for arg, config in common_args.items(): + parser.add_argument(arg, **config) + +subparsers = parser.add_subparsers(help='a command to run', dest='command') + +""" +BENCH +""" + +bench_example = '''**Examples**: + Runs all benchmarks + %(prog)s + + Runs benchmarks for pallet_balances and pallet_multisig for all runtimes which have these pallets. **--quiet** makes it to output nothing to PR but reactions + %(prog)s --pallet pallet_balances pallet_xcm_benchmarks::generic --quiet + + Runs bench for all pallets for westend runtime and continues even if some benchmarks fail + %(prog)s --runtime westend --continue-on-fail + + Does not output anything and cleans up the previous bot's & author command triggering comments in PR + %(prog)s --runtime westend rococo --pallet pallet_balances pallet_multisig --quiet --clean +''' + +parser_bench = subparsers.add_parser('bench', help='Runs benchmarks', epilog=bench_example, formatter_class=argparse.RawDescriptionHelpFormatter) + +for arg, config in common_args.items(): + parser_bench.add_argument(arg, **config) + +parser_bench.add_argument('--runtime', help='Runtime(s) space separated', choices=runtimeNames, nargs='*', default=runtimeNames) +parser_bench.add_argument('--pallet', help='Pallet(s) space separated', nargs='*', default=[]) + +""" +FMT +""" +parser_fmt = subparsers.add_parser('fmt', help='Formats code (cargo +nightly-VERSION fmt) and configs (taplo format)') +for arg, config in common_args.items(): + parser_fmt.add_argument(arg, **config) + +""" +Update UI +""" +parser_ui = subparsers.add_parser('update-ui', help='Updates UI tests') +for arg, config in common_args.items(): + parser_ui.add_argument(arg, **config) + + +args, unknown = parser.parse_known_args() + +print(f'args: {args}') + +if args.command == 'bench': + runtime_pallets_map = {} + failed_benchmarks = {} + successful_benchmarks = {} + + profile = "release" + + print(f'Provided runtimes: {args.runtime}') + # convert to mapped dict + runtimesMatrix = list(filter(lambda x: x['name'] in args.runtime, runtimesMatrix)) + runtimesMatrix = {x['name']: x for x in runtimesMatrix} + print(f'Filtered out runtimes: {runtimesMatrix}') + + # loop over remaining runtimes to collect available pallets + for runtime in runtimesMatrix.values(): + os.system(f"forklift cargo build -p {runtime['package']} --profile {profile} --features runtime-benchmarks") + print(f'-- listing pallets for benchmark for {runtime["name"]}') + wasm_file = f"target/{profile}/wbuild/{runtime['package']}/{runtime['package'].replace('-', '_')}.wasm" + output = os.popen( + f"frame-omni-bencher v1 benchmark pallet --no-csv-header --no-storage-info --no-min-squares --no-median-slopes --all --list --runtime={wasm_file}").read() + raw_pallets = output.strip().split('\n') + + all_pallets = set() + for pallet in raw_pallets: + if pallet: + all_pallets.add(pallet.split(',')[0].strip()) + + pallets = list(all_pallets) + print(f'Pallets in {runtime}: {pallets}') + runtime_pallets_map[runtime['name']] = pallets + + # filter out only the specified pallets from collected runtimes/pallets + if args.pallet: + print(f'Pallet: {args.pallet}') + new_pallets_map = {} + # keep only specified pallets if they exist in the runtime + for runtime in runtime_pallets_map: + if set(args.pallet).issubset(set(runtime_pallets_map[runtime])): + new_pallets_map[runtime] = args.pallet + + runtime_pallets_map = new_pallets_map + + print(f'Filtered out runtimes & pallets: {runtime_pallets_map}') + + if not runtime_pallets_map: + if args.pallet and not args.runtime: + print(f"No pallets {args.pallet} found in any runtime") + elif args.runtime and not args.pallet: + print(f"{args.runtime} runtime does not have any pallets") + elif args.runtime and args.pallet: + print(f"No pallets {args.pallet} found in {args.runtime}") + else: + print('No runtimes found') + sys.exit(1) + + header_path = os.path.abspath('./substrate/HEADER-APACHE2') + + for runtime in runtime_pallets_map: + for pallet in runtime_pallets_map[runtime]: + config = runtimesMatrix[runtime] + print(f'-- config: {config}') + if runtime == 'dev': + # to support sub-modules (https://github.com/paritytech/command-bot/issues/275) + search_manifest_path = f"cargo metadata --locked --format-version 1 --no-deps | jq -r '.packages[] | select(.name == \"{pallet.replace('_', '-')}\") | .manifest_path'" + print(f'-- running: {search_manifest_path}') + manifest_path = os.popen(search_manifest_path).read() + if not manifest_path: + print(f'-- pallet {pallet} not found in dev runtime') + exit(1) + package_dir = os.path.dirname(manifest_path) + print(f'-- package_dir: {package_dir}') + print(f'-- manifest_path: {manifest_path}') + output_path = os.path.join(package_dir, "src", "weights.rs") + else: + default_path = f"./{config['path']}/src/weights" + xcm_path = f"./{config['path']}/src/weights/xcm" + output_path = default_path if not pallet.startswith("pallet_xcm_benchmarks") else xcm_path + print(f'-- benchmarking {pallet} in {runtime} into {output_path}') + cmd = f"frame-omni-bencher v1 benchmark pallet --extrinsic=* --runtime=target/{profile}/wbuild/{config['package']}/{config['package'].replace('-', '_')}.wasm --pallet={pallet} --header={header_path} --output={output_path} --wasm-execution=compiled --steps=50 --repeat=20 --heap-pages=4096 --no-storage-info --no-min-squares --no-median-slopes" + print(f'-- Running: {cmd}') + status = os.system(cmd) + if status != 0 and not args.continue_on_fail: + print(f'Failed to benchmark {pallet} in {runtime}') + sys.exit(1) + + # Otherwise collect failed benchmarks and print them at the end + # push failed pallets to failed_benchmarks + if status != 0: + failed_benchmarks[f'{runtime}'] = failed_benchmarks.get(f'{runtime}', []) + [pallet] + else: + successful_benchmarks[f'{runtime}'] = successful_benchmarks.get(f'{runtime}', []) + [pallet] + + if failed_benchmarks: + print('❌ Failed benchmarks of runtimes/pallets:') + for runtime, pallets in failed_benchmarks.items(): + print(f'-- {runtime}: {pallets}') + + if successful_benchmarks: + print('✅ Successful benchmarks of runtimes/pallets:') + for runtime, pallets in successful_benchmarks.items(): + print(f'-- {runtime}: {pallets}') + +elif args.command == 'fmt': + command = f"cargo +nightly fmt" + print(f'Formatting with `{command}`') + nightly_status = os.system(f'{command}') + taplo_status = os.system('taplo format --config .config/taplo.toml') + + if (nightly_status != 0 or taplo_status != 0) and not args.continue_on_fail: + print('❌ Failed to format code') + sys.exit(1) + +elif args.command == 'update-ui': + command = 'sh ./scripts/update-ui-tests.sh' + print(f'Updating ui with `{command}`') + status = os.system(f'{command}') + + if status != 0 and not args.continue_on_fail: + print('❌ Failed to format code') + sys.exit(1) + +print('🚀 Done') diff --git a/.github/scripts/generate-prdoc.py b/.github/scripts/generate-prdoc.py new file mode 100644 index 000000000000..ba7def20fcb9 --- /dev/null +++ b/.github/scripts/generate-prdoc.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +""" +Generate the PrDoc for a Pull Request with a specific number, audience and bump level. + +It downloads and parses the patch from the GitHub API to opulate the prdoc with all modified crates. +This will delete any prdoc that already exists for the PR if `--force` is passed. + +Usage: + python generate-prdoc.py --pr 1234 --audience "TODO" --bump "TODO" +""" + +import argparse +import os +import re +import sys +import subprocess +import toml +import yaml +import requests + +from github import Github +import whatthepatch +from cargo_workspace import Workspace + +# Download the patch and pass the info into `create_prdoc`. +def from_pr_number(n, audience, bump, force): + print(f"Fetching PR '{n}' from GitHub") + g = Github() + + repo = g.get_repo("paritytech/polkadot-sdk") + pr = repo.get_pull(n) + + patch_url = pr.patch_url + patch = requests.get(patch_url).text + + create_prdoc(n, audience, pr.title, pr.body, patch, bump, force) + +def create_prdoc(pr, audience, title, description, patch, bump, force): + path = f"prdoc/pr_{pr}.prdoc" + + if os.path.exists(path): + if force == True: + print(f"Overwriting existing PrDoc for PR {pr}") + else: + print(f"PrDoc already exists for PR {pr}. Use --force to overwrite.") + sys.exit(1) + else: + print(f"No preexisting PrDoc for PR {pr}") + + prdoc = { "doc": [{}], "crates": [] } + + prdoc["title"] = title + prdoc["doc"][0]["audience"] = audience + prdoc["doc"][0]["description"] = description + + workspace = Workspace.from_path(".") + + modified_paths = [] + for diff in whatthepatch.parse_patch(patch): + modified_paths.append(diff.header.new_path) + + modified_crates = {} + for p in modified_paths: + # Go up until we find a Cargo.toml + p = os.path.join(workspace.path, p) + while not os.path.exists(os.path.join(p, "Cargo.toml")): + p = os.path.dirname(p) + + with open(os.path.join(p, "Cargo.toml")) as f: + manifest = toml.load(f) + + if not "package" in manifest: + print(f"File was not in any crate: {p}") + continue + + crate_name = manifest["package"]["name"] + if workspace.crate_by_name(crate_name).publish: + modified_crates[crate_name] = True + else: + print(f"Skipping unpublished crate: {crate_name}") + + print(f"Modified crates: {modified_crates.keys()}") + + for crate_name in modified_crates.keys(): + entry = { "name": crate_name } + + if bump == 'silent' or bump == 'ignore' or bump == 'no change': + entry["validate"] = False + else: + entry["bump"] = bump + + print(f"Adding crate {entry}") + prdoc["crates"].append(entry) + + # write the parsed PR documentation back to the file + with open(path, "w") as f: + yaml.dump(prdoc, f) + print(f"PrDoc for PR {pr} written to {path}") + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--pr", type=int, required=True) + parser.add_argument("--audience", type=str, default="TODO") + parser.add_argument("--bump", type=str, default="TODO") + parser.add_argument("--force", type=str) + return parser.parse_args() + +if __name__ == "__main__": + args = parse_args() + force = True if args.force.lower() == "true" else False + print(f"Args: {args}, force: {force}") + from_pr_number(args.pr, args.audience, args.bump, force) diff --git a/.github/scripts/generate-readmes.py b/.github/scripts/generate-readmes.py new file mode 100755 index 000000000000..f838eaa29a74 --- /dev/null +++ b/.github/scripts/generate-readmes.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 + +""" +A script to generate READMEs for all public crates, +if they do not already have one. + +It relies on functions from the `check-workspace.py` script. + +The resulting README is based on a template defined below, +and includes the crate name, description, license, +and optionally - the SDK release version. + +# Example + +```sh +python3 -m pip install toml +.github/scripts/generate-readmes.py . --sdk-version 1.15.0 +``` +""" + +import os +import toml +import importlib +import argparse + +check_workspace = importlib.import_module("check-workspace") + +README_TEMPLATE = """
+ +Polkadot logo + +# {name} + +This crate is part of the [Polkadot SDK](https://github.com/paritytech/polkadot-sdk/). + +
+ +## Description + +{description} + +## Additional Resources + +In order to learn about Polkadot SDK, head over to the [Polkadot SDK Developer Documentation](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/index.html). + +To learn about Polkadot, visit [polkadot.com](https://polkadot.com/). + +## License + +This crate is licensed with {license}. +""" + +VERSION_TEMPLATE = """ +## Version + +This version of `{name}` is associated with Polkadot {sdk_version} release. +""" + + +def generate_readme(member, *, workspace_dir, workspace_license, sdk_version): + print(f"Loading manifest for: {member}") + manifest = toml.load(os.path.join(workspace_dir, member, "Cargo.toml")) + if manifest["package"].get("publish", True) == False: + print(f"⏩ Skipping un-published crate: {member}") + return + if os.path.exists(os.path.join(workspace_dir, member, "README.md")): + print(f"⏩ Skipping crate with an existing readme: {member}") + return + print(f"📝 Generating README for: {member}") + + license = manifest["package"]["license"] + if isinstance(license, dict): + if not license.get("workspace", False): + print( + f"❌ License for {member} is unexpectedly declared as workspace=false." + ) + # Skipping this crate as it is not clear what license it should use. + return + license = workspace_license + + name = manifest["package"]["name"] + description = manifest["package"]["description"] + description = description + "." if not description.endswith(".") else description + + filled_readme = README_TEMPLATE.format( + name=name, description=description, license=license + ) + + if sdk_version: + filled_readme += VERSION_TEMPLATE.format(name=name, sdk_version=sdk_version) + + with open(os.path.join(workspace_dir, member, "README.md"), "w") as new_readme: + new_readme.write(filled_readme) + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Generate readmes for published crates." + ) + + parser.add_argument( + "workspace_dir", + help="The directory to check", + metavar="workspace_dir", + type=str, + nargs=1, + ) + parser.add_argument( + "--sdk-version", + help="Optional SDK release version", + metavar="sdk_version", + type=str, + nargs=1, + required=False, + ) + + args = parser.parse_args() + return (args.workspace_dir[0], args.sdk_version[0] if args.sdk_version else None) + + +def main(): + (workspace_dir, sdk_version) = parse_args() + root_manifest = toml.load(os.path.join(workspace_dir, "Cargo.toml")) + workspace_license = root_manifest["workspace"]["package"]["license"] + members = check_workspace.get_members(workspace_dir, []) + for member in members: + generate_readme( + member, + workspace_dir=workspace_dir, + workspace_license=workspace_license, + sdk_version=sdk_version, + ) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/check-cargo-check-runtimes.yml b/.github/workflows/check-cargo-check-runtimes.yml new file mode 100644 index 000000000000..ebcf6c5fc9bd --- /dev/null +++ b/.github/workflows/check-cargo-check-runtimes.yml @@ -0,0 +1,136 @@ +name: Check Cargo Check Runtimes + +on: + pull_request: + types: [ opened, synchronize, reopened, ready_for_review, labeled ] + + +# Jobs in this workflow depend on each other, only for limiting peak amount of spawned workers + +jobs: + # GitHub Actions allows using 'env' in a container context. + # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 + # This workaround sets the container image for each job using 'set-image' job output. + set-image: + if: contains(github.event.label.name, 'GHA-migration') || contains(github.event.pull_request.labels.*.name, 'GHA-migration') + runs-on: ubuntu-latest + timeout-minutes: 20 + outputs: + IMAGE: ${{ steps.set_image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - id: set_image + run: cat .github/env >> $GITHUB_OUTPUT + check-runtime-assets: + runs-on: arc-runners-polkadot-sdk-beefy + needs: [set-image] + timeout-minutes: 20 + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run cargo check + uses: ./.github/actions/cargo-check-runtimes + with: + root: cumulus/parachains/runtimes/assets + + check-runtime-collectives: + runs-on: arc-runners-polkadot-sdk-beefy + needs: [check-runtime-assets, set-image] + timeout-minutes: 20 + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run cargo check + uses: ./.github/actions/cargo-check-runtimes + with: + root: cumulus/parachains/runtimes/collectives + + check-runtime-coretime: + runs-on: arc-runners-polkadot-sdk-beefy + container: + image: ${{ needs.set-image.outputs.IMAGE }} + needs: [check-runtime-assets, set-image] + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run cargo check + uses: ./.github/actions/cargo-check-runtimes + with: + root: cumulus/parachains/runtimes/coretime + + check-runtime-bridge-hubs: + runs-on: arc-runners-polkadot-sdk-beefy + container: + image: ${{ needs.set-image.outputs.IMAGE }} + needs: [set-image] + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run cargo check + uses: ./.github/actions/cargo-check-runtimes + with: + root: cumulus/parachains/runtimes/bridge-hubs + + check-runtime-contracts: + runs-on: arc-runners-polkadot-sdk-beefy + container: + image: ${{ needs.set-image.outputs.IMAGE }} + needs: [check-runtime-collectives, set-image] + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run cargo check + uses: ./.github/actions/cargo-check-runtimes + with: + root: cumulus/parachains/runtimes/contracts + + check-runtime-starters: + runs-on: arc-runners-polkadot-sdk-beefy + container: + image: ${{ needs.set-image.outputs.IMAGE }} + needs: [check-runtime-assets, set-image] + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run cargo check + uses: ./.github/actions/cargo-check-runtimes + with: + root: cumulus/parachains/runtimes/starters + + check-runtime-testing: + runs-on: arc-runners-polkadot-sdk-beefy + container: + image: ${{ needs.set-image.outputs.IMAGE }} + needs: [check-runtime-starters, set-image] + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run cargo check + uses: ./.github/actions/cargo-check-runtimes + with: + root: cumulus/parachains/runtimes/testing + + confirm-required-jobs-passed: + runs-on: ubuntu-latest + name: All check-runtime-* tests passed + # If any new job gets added, be sure to add it to this array + needs: + - check-runtime-assets + - check-runtime-collectives + - check-runtime-coretime + - check-runtime-bridge-hubs + - check-runtime-contracts + - check-runtime-starters + - check-runtime-testing + steps: + - run: echo '### Good job! All the tests passed 🚀' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/check-runtime-migration.yml b/.github/workflows/check-runtime-migration.yml index 2b963b2230fb..b8962f0e07ad 100644 --- a/.github/workflows/check-runtime-migration.yml +++ b/.github/workflows/check-runtime-migration.yml @@ -6,6 +6,9 @@ on: - master pull_request: types: [opened, synchronize, reopened, ready_for_review] + # Take a snapshot at 5am when most SDK devs are not working. + schedule: + - cron: '0 5 * * *' merge_group: workflow_dispatch: @@ -53,13 +56,13 @@ jobs: # - network: westend # package: westend-runtime # wasm: westend_runtime.compact.compressed.wasm - # uri: "wss://westend-try-runtime-node.parity-chains.parity.io:443" + # uri: "wss://try-runtime-westend.polkadot.io:443" # subcommand_extra_args: "--no-weight-warnings" # command_extra_args: "" # - network: rococo # package: rococo-runtime # wasm: rococo_runtime.compact.compressed.wasm - # uri: "wss://rococo-try-runtime-node.parity-chains.parity.io:443" + # uri: "wss://try-runtime-rococo.polkadot.io:443" # subcommand_extra_args: "--no-weight-warnings" # command_extra_args: "" - network: asset-hub-westend diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index d9d918b44a23..15eb32f4062c 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -12,7 +12,6 @@ concurrency: env: TOOLCHAIN: nightly-2024-06-01 - jobs: check-semver: runs-on: ubuntu-latest @@ -20,14 +19,14 @@ jobs: image: docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408 steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + fetch-depth: 2 - name: extra git setup - env: - BASE: ${{ github.event.pull_request.base.sha }} run: | git config --global --add safe.directory '*' - git fetch --no-tags --no-recurse-submodules --depth=1 origin $BASE - git branch old $BASE + + git branch old HEAD^1 - name: Comment If Backport if: ${{ startsWith(github.event.pull_request.base.ref, 'stable') }} @@ -46,7 +45,7 @@ jobs: as to not impact downstream teams that rely on the stability of it. Some things to consider: - Backports are only for 'patch' or 'minor' changes. No 'major' or other breaking change. - Should be a legit *fix* for some bug, not adding tons of new features. - - Must either be already audited or trivial (not sure audit). + - Must either be already audited or not need an audit.
Emergency Bypass

diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index ee4bd62a558d..ee5ac31e9caa 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -100,6 +100,8 @@ jobs: --exclude "substrate/frame/contracts/fixtures/build" "substrate/frame/contracts/fixtures/contracts/common" + "substrate/frame/revive/fixtures/build" + "substrate/frame/revive/fixtures/contracts/common" - name: deny git deps run: python3 .github/scripts/deny-git-deps.py . check-markdown: @@ -154,3 +156,28 @@ jobs: git diff exit 1 fi + check-fail-ci: + runs-on: ubuntu-latest + container: + # there's no "rg" in ci-unified, and tools is a smaller image anyway + image: "paritytech/tools:latest" + # paritytech/tools uses "nonroot" user by default, which doesn't have enough + # permissions to create GHA context + options: --user root + steps: + - name: Fetch latest code + uses: actions/checkout@v4 + - name: Check + run: | + set +e + rg --line-number --hidden --type rust --glob '!{.git,target}' "$ASSERT_REGEX" .; exit_status=$? + if [ $exit_status -eq 0 ]; then + echo "$ASSERT_REGEX was found, exiting with 1"; + exit 1; + else + echo "No $ASSERT_REGEX was found, exiting with 0"; + exit 0; + fi + env: + ASSERT_REGEX: "FAIL-CI" + GIT_DEPTH: 1 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 054c7d786ca9..ad9d0d1a959d 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -1,12 +1,13 @@ -name: checks +name: Checks on: push: branches: - master pull_request: - types: [opened, synchronize, reopened, ready_for_review, labeled] + types: [opened, synchronize, reopened, ready_for_review] merge_group: + concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -14,29 +15,38 @@ concurrency: permissions: {} jobs: - changes: - # TODO: remove once migration is complete or this workflow is fully stable - if: contains(github.event.label.name, 'GHA-migration') - permissions: - pull-requests: read - uses: ./.github/workflows/reusable-check-changed-files.yml + # temporary disabled because currently doesn't work in merge queue + # changes: + # permissions: + # pull-requests: read + # uses: ./.github/workflows/reusable-check-changed-files.yml set-image: # GitHub Actions allows using 'env' in a container context. # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 # This workaround sets the container image for each job using 'set-image' job output. runs-on: ubuntu-latest - timeout-minutes: 20 outputs: IMAGE: ${{ steps.set_image.outputs.IMAGE }} + RUNNER: ${{ steps.set_runner.outputs.RUNNER }} steps: - name: Checkout uses: actions/checkout@v4 - id: set_image run: cat .github/env >> $GITHUB_OUTPUT + # By default we use spot machines that can be terminated at any time. + # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. + - id: set_runner + run: | + # Run merge queues on persistent runners + if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT + else + echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + fi cargo-clippy: - runs-on: arc-runners-polkadot-sdk-beefy - needs: [set-image, changes] # , build-frame-omni-bencher ] - if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [set-image] + # if: ${{ needs.changes.outputs.rust }} timeout-minutes: 40 container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -50,9 +60,9 @@ jobs: forklift cargo clippy --all-targets --locked --workspace forklift cargo clippy --all-targets --all-features --locked --workspace check-try-runtime: - runs-on: arc-runners-polkadot-sdk-beefy - needs: [set-image, changes] # , build-frame-omni-bencher ] - if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [set-image] + # if: ${{ needs.changes.outputs.rust }} timeout-minutes: 40 container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -69,9 +79,9 @@ jobs: forklift cargo check --locked --all --features try-runtime,experimental # check-core-crypto-features works fast without forklift check-core-crypto-features: - runs-on: arc-runners-polkadot-sdk-beefy - needs: [set-image, changes] # , build-frame-omni-bencher ] - if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} + needs: [set-image] + # if: ${{ needs.changes.outputs.rust }} timeout-minutes: 30 container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -88,3 +98,12 @@ jobs: cd substrate/primitives/keyring ./check-features-variants.sh cd - + # name of this job must be unique across all workflows + # otherwise GitHub will mark all these jobs as required + confirm-required-checks-passed: + runs-on: ubuntu-latest + name: All checks passed + # If any new job gets added, be sure to add it to this array + needs: [cargo-clippy, check-try-runtime, check-core-crypto-features] + steps: + - run: echo '### Good job! All the checks passed 🚀' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml new file mode 100644 index 000000000000..dac46cf435a6 --- /dev/null +++ b/.github/workflows/cmd.yml @@ -0,0 +1,411 @@ +name: Command + +on: + issue_comment: # listen for comments on issues + types: [ created ] + +permissions: # allow the action to comment on the PR + contents: write + issues: write + pull-requests: write + actions: read + +jobs: + is-org-member: + if: startsWith(github.event.comment.body, '/cmd') + runs-on: ubuntu-latest + outputs: + member: ${{ steps.is-member.outputs.result }} + steps: + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v2.1.0 + with: + app_id: ${{ secrets.CMD_BOT_APP_ID }} + private_key: ${{ secrets.CMD_BOT_APP_KEY }} + + - name: Check if user is a member of the organization + id: is-member + uses: actions/github-script@v7 + with: + github-token: ${{ steps.generate_token.outputs.token }} + result-encoding: string + script: | + const fs = require("fs"); + try { + const org = '${{ github.event.repository.owner.login }}'; + const username = '${{ github.event.comment.user.login }}'; + + const membership = await github.rest.orgs.checkMembershipForUser({ + org: org, + username: username + }); + + console.log(membership, membership.status, membership.status === 204); + + if (membership.status === 204) { + return 'true'; + } else { + console.log(membership); + fs.appendFileSync(process.env["GITHUB_STEP_SUMMARY"], `${membership.data && membership.data.message || 'Unknown error happened, please check logs'}`); + } + } catch (error) { + console.log(error) + } + + return 'false'; + + reject-non-members: + needs: is-org-member + if: ${{ startsWith(github.event.comment.body, '/cmd') && needs.is-org-member.outputs.member != 'true' }} + runs-on: ubuntu-latest + steps: + - name: Add reaction to rejected comment + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.reactions.createForIssueComment({ + comment_id: ${{ github.event.comment.id }}, + owner: context.repo.owner, + repo: context.repo.repo, + content: 'confused' + }) + + - name: Comment PR (Rejected) + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Sorry, only members of the organization ${{ github.event.repository.owner.login }} members can run commands.` + }) + + acknowledge: + needs: is-org-member + if: ${{ startsWith(github.event.comment.body, '/cmd') && needs.is-org-member.outputs.member == 'true' }} + runs-on: ubuntu-latest + steps: + - name: Add reaction to triggered comment + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.reactions.createForIssueComment({ + comment_id: ${{ github.event.comment.id }}, + owner: context.repo.owner, + repo: context.repo.repo, + content: 'eyes' + }) + + clean: + needs: is-org-member + runs-on: ubuntu-latest + steps: + - name: Clean previous comments + if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--clean') && needs.is-org-member.outputs.member == 'true' }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo + }).then(comments => { + for (let comment of comments.data) { + console.log(comment) + if ( + ${{ github.event.comment.id }} !== comment.id && + ( + ( + ( + comment.body.startsWith('Command') || + comment.body.startsWith('

Command') || + comment.body.startsWith('Sorry, only ') + ) && comment.user.type === 'Bot' + ) || + (comment.body.startsWith('/cmd') && comment.user.login === context.actor) + ) + ) { + github.rest.issues.deleteComment({ + comment_id: comment.id, + owner: context.repo.owner, + repo: context.repo.repo + }) + } + } + }) + help: + needs: [ clean, is-org-member ] + if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--help') && needs.is-org-member.outputs.member == 'true' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Get command + uses: actions-ecosystem/action-regex-match@v2 + id: get-pr-comment + with: + text: ${{ github.event.comment.body }} + regex: '^(\/cmd )([-\/\s\w.=:]+)$' # see explanation in docs/contributor/commands-readme.md#examples + + - name: Save output of help + id: help + env: + CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command + run: | + echo 'help<> $GITHUB_OUTPUT + python3 .github/scripts/cmd/cmd.py $CMD >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + + - name: Comment PR (Help) + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `
Command help: + + \`\`\` + ${{ steps.help.outputs.help }} + \`\`\` + +
` + }) + + - name: Add confused reaction on failure + uses: actions/github-script@v7 + if: ${{ failure() }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.reactions.createForIssueComment({ + comment_id: ${{ github.event.comment.id }}, + owner: context.repo.owner, + repo: context.repo.repo, + content: 'confused' + }) + + - name: Add 👍 reaction on success + uses: actions/github-script@v7 + if: ${{ !failure() }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.reactions.createForIssueComment({ + comment_id: ${{ github.event.comment.id }}, + owner: context.repo.owner, + repo: context.repo.repo, + content: '+1' + }) + + set-image: + needs: [ clean, is-org-member ] + if: ${{ startsWith(github.event.comment.body, '/cmd') && !contains(github.event.comment.body, '--help') && needs.is-org-member.outputs.member == 'true' }} + runs-on: ubuntu-latest + outputs: + IMAGE: ${{ steps.set-image.outputs.IMAGE }} + RUNNER: ${{ steps.set-image.outputs.RUNNER }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - id: set-image + run: | + BODY=$(echo "${{ github.event.comment.body }}" | xargs) + IMAGE_OVERRIDE=$(echo $BODY | grep -oe 'docker.io/paritytech/ci-unified:.*\s' | xargs) + + cat .github/env >> $GITHUB_OUTPUT + + if [ -n "$IMAGE_OVERRIDE" ]; then + echo "IMAGE=$IMAGE_OVERRIDE" >> $GITHUB_OUTPUT + fi + + if [[ $BODY == "/cmd bench"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-benchmark" >> $GITHUB_OUTPUT + elif [[ $BODY == "/cmd update-ui"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + else + echo "RUNNER=ubuntu-latest" >> $GITHUB_OUTPUT + fi + + cmd: + needs: [ set-image ] + env: + JOB_NAME: 'cmd' + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - name: Get command + uses: actions-ecosystem/action-regex-match@v2 + id: get-pr-comment + with: + text: ${{ github.event.comment.body }} + regex: '^(\/cmd )([-\/\s\w.=:]+)$' # see explanation in docs/contributor/commands-readme.md#examples + + - name: Build workflow link + if: ${{ !contains(github.event.comment.body, '--quiet') }} + id: build-link + run: | + # Get exactly the CMD job link, filtering out the other jobs + jobLink=$(curl -s \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs | jq '.jobs[] | select(.name | contains("${{ env.JOB_NAME }}")) | .html_url') + + runLink=$(curl -s \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }} | jq '.html_url') + + echo "job_url=${jobLink}" + echo "run_url=${runLink}" + echo "job_url=$jobLink" >> $GITHUB_OUTPUT + echo "run_url=$runLink" >> $GITHUB_OUTPUT + + + - name: Comment PR (Start) + if: ${{ !contains(github.event.comment.body, '--quiet') }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + let job_url = ${{ steps.build-link.outputs.job_url }} + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has started 🚀 [See logs here](${job_url})` + }) + + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - name: Install dependencies for bench + if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') + run: cargo install subweight frame-omni-bencher --locked + + - name: Run cmd + id: cmd + env: + CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command + run: | + echo "Running command: '$CMD' on '${{ needs.set-image.outputs.RUNNER }}' runner, container: '${{ needs.set-image.outputs.IMAGE }}'" + echo "RUST_NIGHTLY_VERSION: $RUST_NIGHTLY_VERSION" + # Fixes "detected dubious ownership" error in the ci + git config --global --add safe.directory '*' + git remote -v + python3 .github/scripts/cmd/cmd.py $CMD + git status + git diff + + - name: Commit changes + run: | + if [ -n "$(git status --porcelain)" ]; then + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + git pull origin ${{ github.head_ref }} + git add . + git restore --staged Cargo.lock # ignore changes in Cargo.lock + git commit -m "Update from ${{ github.actor }} running command '${{ steps.get-pr-comment.outputs.group2 }}'" || true + git push origin ${{ github.head_ref }} + else + echo "Nothing to commit"; + fi + + - name: Run Subweight + id: subweight + if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') + shell: bash + run: | + git fetch + result=$(subweight compare commits \ + --path-pattern "./**/weights/**/*.rs,./**/weights.rs" \ + --method asymptotic \ + --format markdown \ + --no-color \ + --change added changed \ + --ignore-errors \ + refs/remotes/origin/master ${{ github.ref }}) + + # Save the multiline result to the output + { + echo "result<> $GITHUB_OUTPUT + + - name: Comment PR (End) + if: ${{ !failure() && !contains(github.event.comment.body, '--quiet') }} + uses: actions/github-script@v7 + env: + SUBWEIGHT: '${{ steps.subweight.outputs.result }}' + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + let runUrl = ${{ steps.build-link.outputs.run_url }} + let subweight = process.env.SUBWEIGHT; + + let subweightCollapsed = subweight + ? `
\n\nSubweight results:\n\n${subweight}\n\n
` + : ''; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has finished ✅ [See logs here](${runUrl})${subweightCollapsed}` + }) + + - name: Comment PR (Failure) + if: ${{ failure() && !contains(github.event.comment.body, '--quiet') }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + let jobUrl = ${{ steps.build-link.outputs.job_url }} + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has failed ❌! [See logs here](${jobUrl})` + }) + + - name: Add 😕 reaction on failure + uses: actions/github-script@v7 + if: ${{ failure() }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.reactions.createForIssueComment({ + comment_id: ${{ github.event.comment.id }}, + owner: context.repo.owner, + repo: context.repo.repo, + content: 'confused' + }) + + - name: Add 👍 reaction on success + uses: actions/github-script@v7 + if: ${{ !failure() }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.reactions.createForIssueComment({ + comment_id: ${{ github.event.comment.id }}, + owner: context.repo.owner, + repo: context.repo.repo, + content: '+1' + }) diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml new file mode 100644 index 000000000000..4c63297efc18 --- /dev/null +++ b/.github/workflows/command-backport.yml @@ -0,0 +1,62 @@ +name: Backport into stable + +on: + # This trigger can be problematic, see: https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ + # In our case it is fine since we only run it on merged Pull Requests and do not execute any of the repo code itself. + pull_request_target: + types: [ closed, labeled ] + +permissions: + contents: write # so it can comment + pull-requests: write # so it can create pull requests + +jobs: + backport: + name: Backport pull request + runs-on: ubuntu-latest + + # The 'github.event.pull_request.merged' ensures that it got into master: + if: > + ( !startsWith(github.event.pull_request.base.ref, 'stable') ) && + ( + github.event_name == 'pull_request_target' && + github.event.pull_request.merged && + github.event.pull_request.base.ref == 'master' && + contains(github.event.pull_request.labels.*.name, 'A4-needs-backport') + ) + steps: + - uses: actions/checkout@v4 + + - name: Create backport pull requests + uses: korthout/backport-action@v3 + id: backport + with: + target_branches: stable2407 + merge_commits: skip + github_token: ${{ secrets.GITHUB_TOKEN }} + pull_description: | + Backport #${pull_number} into `${target_branch}` (cc @${pull_author}). + + + pull_title: | + [${target_branch}] Backport #${pull_number} + + - name: Label Backports + if: ${{ steps.backport.outputs.created_pull_numbers != '' }} + uses: actions/github-script@v7 + with: + script: | + const pullNumbers = '${{ steps.backport.outputs.created_pull_numbers }}'.split(' '); + + for (const pullNumber of pullNumbers) { + await github.rest.issues.addLabels({ + issue_number: parseInt(pullNumber), + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['A3-backport'] + }); + console.log(`Added A3-backport label to PR #${pullNumber}`); + } diff --git a/.github/workflows/command-bench-all.yml b/.github/workflows/command-bench-all.yml deleted file mode 100644 index 4128f86fb7c8..000000000000 --- a/.github/workflows/command-bench-all.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Command Bench All - -on: - workflow_dispatch: - inputs: - pr: - description: Number of the Pull Request - required: true - benchmark: - description: Pallet benchmark - type: choice - required: true - options: - - pallet - - substrate - - polkadot - - cumulus - pallet: - description: Pallet - required: false - type: string - default: pallet_name - target_dir: - description: Target directory - type: choice - options: - - substrate - - polkadot - - cumulus - runtime: - description: Runtime - type: choice - options: - - rococo - - westend - - asset-hub-kusama - - asset-hub-polkadot - - asset-hub-rococo - - asset-hub-westend - - bridge-hub-kusama - - bridge-hub-polkadot - - bridge-hub-rococo - - bridge-hub-westend - - collectives-polkadot - - collectives-westend - - coretime-rococo - - coretime-westend - - contracts-rococo - - glutton-kusama - - glutton-westend - - people-rococo - - people-westend - -jobs: - set-image: - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - cmd-bench-all: - needs: [set-image] - runs-on: arc-runners-polkadot-sdk-weights - container: - image: ${{ needs.set-image.outputs.IMAGE }} - permissions: - contents: write - pull-requests: write - steps: - - name: Download repo - uses: actions/checkout@v4 - - name: Install gh cli - id: gh - uses: ./.github/actions/set-up-gh - with: - pr-number: ${{ inputs.pr }} - GH_TOKEN: ${{ github.token }} - - name: Run bench all - run: | - "./scripts/bench-all.sh" "${{ inputs.benchmark }}" --runtime "${{ inputs.runtime }}" --pallet "${{ inputs.pallet }}" --target_dir "${{ inputs.target_dir }}" - - name: Report failure - if: ${{ failure() }} - run: gh pr comment ${{ inputs.pr }} --body "

Command failed ❌

Run by @${{ github.actor }} for ${{ github.workflow }} failed. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} - - run: git pull --rebase - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: cmd-action - ${{ github.workflow }} - branch: ${{ steps.gh.outputs.branch }} - - name: Report succeed - run: gh pr comment ${{ inputs.pr }} --body "

Action completed 🎉🎉

Run by @${{ github.actor }} for ${{ github.workflow }} completed 🎉. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/command-bench-overhead.yml b/.github/workflows/command-bench-overhead.yml deleted file mode 100644 index fec8d37bb9ef..000000000000 --- a/.github/workflows/command-bench-overhead.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Command Bench Overhead - -on: - workflow_dispatch: - inputs: - pr: - description: Number of the Pull Request - required: true - benchmark: - description: Pallet benchmark - type: choice - required: true - options: - - default - - substrate - - cumulus - runtime: - description: Runtime - type: choice - options: - - rococo - - westend - - asset-hub-rococo - - asset-hub-westend - target_dir: - description: Target directory - type: choice - options: - - polkadot - - substrate - - cumulus - -jobs: - set-image: - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - cmd-bench-overhead: - needs: [set-image] - runs-on: arc-runners-polkadot-sdk-benchmark - container: - image: ${{ needs.set-image.outputs.IMAGE }} - permissions: - contents: write - pull-requests: write - steps: - - name: Download repo - uses: actions/checkout@v4 - - name: Install gh cli - id: gh - uses: ./.github/actions/set-up-gh - with: - pr-number: ${{ inputs.pr }} - GH_TOKEN: ${{ github.token }} - - name: Run bench overhead - run: | - "./scripts/bench.sh" "${{ inputs.benchmark }}" --subcommand "overhead" --runtime "${{ inputs.runtime }}" --target_dir "${{ inputs.target_dir }}" - - name: Report failure - if: ${{ failure() }} - run: gh pr comment ${{ inputs.pr }} --body "

Command failed ❌

Run by @${{ github.actor }} for ${{ github.workflow }} failed. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} - - run: git pull --rebase - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: cmd-action - ${{ github.workflow }} - branch: ${{ steps.gh.outputs.branch }} - - name: Report succeed - run: gh pr comment ${{ inputs.pr }} --body "

Action completed 🎉🎉

Run by @${{ github.actor }} for ${{ github.workflow }} completed 🎉. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/command-bench.yml b/.github/workflows/command-bench.yml deleted file mode 100644 index ac879f443755..000000000000 --- a/.github/workflows/command-bench.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: Command Bench - -on: - workflow_dispatch: - inputs: - pr: - description: Number of the Pull Request - required: true - benchmark: - description: Pallet benchmark - type: choice - required: true - options: - - substrate-pallet - - polkadot-pallet - - cumulus-assets - - cumulus-collectives - - cumulus-coretime - - cumulus-bridge-hubs - - cumulus-contracts - - cumulus-glutton - - cumulus-starters - - cumulus-people - - cumulus-testing - subcommand: - description: Subcommand - type: choice - required: true - options: - - pallet - - xcm - runtime: - description: Runtime - type: choice - options: - - dev - - rococo - - westend - - asset-hub-westend - - asset-hub-rococo - - collectives-westend - - coretime-rococo - - coretime-westend - - bridge-hub-rococo - - bridge-hub-westend - - contracts-rococo - - glutton-westend - - glutton-westend-dev-1300 - - seedling - - shell - - people-westend - - people-rococo - - penpal - - rococo-parachain - pallet: - description: Pallet - type: string - default: pallet_name - target_dir: - description: Target directory - type: choice - options: - - substrate - - polkadot - - cumulus - runtime_dir: - description: Runtime directory - type: choice - options: - - people - - collectives - - coretime - - bridge-hubs - - contracts - - glutton - - starters - - testing - -jobs: - set-image: - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - cmd-bench: - needs: [set-image] - runs-on: arc-runners-polkadot-sdk-benchmark - container: - image: ${{ needs.set-image.outputs.IMAGE }} - permissions: - contents: write - pull-requests: write - steps: - - name: Download repo - uses: actions/checkout@v4 - - name: Install gh cli - id: gh - uses: ./.github/actions/set-up-gh - with: - pr-number: ${{ inputs.pr }} - GH_TOKEN: ${{ github.token }} - - name: Run bench - run: | - "./scripts/bench.sh" "${{ inputs.benchmark }}" --runtime "${{ inputs.runtime }}" --pallet "${{ inputs.pallet }}" --target_dir "${{ inputs.target_dir }}" --subcommand "${{ inputs.subcommand }}" --runtime_dir "${{ inputs.runtime_dir }}" - - name: Report failure - if: ${{ failure() }} - run: gh pr comment ${{ inputs.pr }} --body "

Command failed ❌

Run by @${{ github.actor }} for ${{ github.workflow }} failed. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} - - run: git pull --rebase - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: cmd-action - ${{ github.workflow }} - branch: ${{ steps.gh.outputs.branch }} - - name: Report succeed - run: gh pr comment ${{ inputs.pr }} --body "

Action completed 🎉🎉

Run by @${{ github.actor }} for ${{ github.workflow }} completed 🎉. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/command-fmt.yml b/.github/workflows/command-fmt.yml deleted file mode 100644 index fc37a17ac549..000000000000 --- a/.github/workflows/command-fmt.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Command FMT - -on: - workflow_dispatch: - inputs: - pr: - description: Number of the Pull Request - required: true - -jobs: - set-image: - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - cmd-fmt: - needs: [set-image] - runs-on: ubuntu-latest - timeout-minutes: 20 - container: - image: ${{ needs.set-image.outputs.IMAGE }} - permissions: - contents: write - pull-requests: write - steps: - - name: Download repo - uses: actions/checkout@v4 - - name: Install gh cli - id: gh - uses: ./.github/actions/set-up-gh - with: - pr-number: ${{ inputs.pr }} - GH_TOKEN: ${{ github.token }} - - name: Run FMT - run: | - cargo --version - rustc --version - cargo +nightly --version - rustc +nightly --version - - cargo +nightly fmt - - # format toml. - # since paritytech/ci-unified:bullseye-1.73.0-2023-11-01-v20231204 includes taplo-cli - taplo format --config .config/taplo.toml - - name: Report failure - if: ${{ failure() }} - run: gh pr comment ${{ inputs.pr }} --body "

Command failed ❌

Run by @${{ github.actor }} for ${{ github.workflow }} failed. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} - - run: git pull --rebase - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: cmd-action - ${{ github.workflow }} - branch: ${{ steps.gh.outputs.branch }} - - name: Report succeed - run: gh pr comment ${{ inputs.pr }} --body "

Action completed 🎉🎉

Run by @${{ github.actor }} for ${{ github.workflow }} completed 🎉. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/command-inform.yml b/.github/workflows/command-inform.yml deleted file mode 100644 index 2825f4a60460..000000000000 --- a/.github/workflows/command-inform.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Inform of new command action - -on: - issue_comment: - types: [created] - -jobs: - comment: - runs-on: ubuntu-latest - if: github.event.issue.pull_request && startsWith(github.event.comment.body, 'bot ') - steps: - - name: Inform that the new command exist - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: 'We are migrating the command bot to be a GitHub Action

Please, see the documentation on how to use it' - }) diff --git a/.github/workflows/command-sync.yml b/.github/workflows/command-sync.yml deleted file mode 100644 index c610f4066a87..000000000000 --- a/.github/workflows/command-sync.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Command Sync - -on: - workflow_dispatch: - inputs: - pr: - description: Number of the Pull Request - required: true - chain: - description: Chain - type: choice - required: true - options: - - westend - - rococo - sync-type: - description: Sync type - type: choice - required: true - options: - - warp - - full - - fast - - fast-unsafe - -jobs: - set-image: - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - cmd-sync: - needs: [set-image] - runs-on: arc-runners-polkadot-sdk-warpsync - container: - image: ${{ needs.set-image.outputs.IMAGE }} - permissions: - contents: write - pull-requests: write - steps: - - name: Download repo - uses: actions/checkout@v4 - - name: Install gh cli - id: gh - uses: ./.github/actions/set-up-gh - with: - pr-number: ${{ inputs.pr }} - GH_TOKEN: ${{ github.token }} - - name: Run sync - run: | - "./scripts/sync.sh" --chain "${{ inputs.chain }}" --type "${{ inputs.sync-type }}" - - name: Report failure - if: ${{ failure() }} - run: gh pr comment ${{ inputs.pr }} --body "

Command failed ❌

Run by @${{ github.actor }} for ${{ github.workflow }} failed. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} - - run: git pull --rebase - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: cmd-action - ${{ github.workflow }} - branch: ${{ steps.gh.outputs.branch }} - - name: Report succeed - run: gh pr comment ${{ inputs.pr }} --body "

Action completed 🎉🎉

Run by @${{ github.actor }} for ${{ github.workflow }} completed 🎉. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/command-update-ui.yml b/.github/workflows/command-update-ui.yml deleted file mode 100644 index 860177adc879..000000000000 --- a/.github/workflows/command-update-ui.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Command Update UI - -on: - workflow_dispatch: - inputs: - pr: - description: Number of the Pull Request - required: true - rust-version: - description: Version of rust. Example 1.70 - required: false - -jobs: - set-image: - runs-on: ubuntu-latest - outputs: - IMAGE: ${{ steps.set_image.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - id: set_image - run: cat .github/env >> $GITHUB_OUTPUT - cmd-update-ui: - needs: [set-image] - runs-on: arc-runners-polkadot-sdk-beefy - timeout-minutes: 90 - container: - image: ${{ needs.set-image.outputs.IMAGE }} - permissions: - contents: write - pull-requests: write - steps: - - name: Download repo - uses: actions/checkout@v4 - - name: Install gh cli - id: gh - uses: ./.github/actions/set-up-gh - with: - pr-number: ${{ inputs.pr }} - GH_TOKEN: ${{ github.token }} - - name: Run update-ui - run: | - "./scripts/update-ui-tests.sh" "${{ inputs.rust-version }}" - - name: Report failure - if: ${{ failure() }} - run: gh pr comment ${{ inputs.pr }} --body "

Command failed ❌

Run by @${{ github.actor }} for ${{ github.workflow }} failed. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} - - run: git pull --rebase - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: cmd-action - ${{ github.workflow }} - branch: ${{ steps.gh.outputs.branch }} - - name: Report succeed - run: gh pr comment ${{ inputs.pr }} --body "

Action completed 🎉🎉

Run by @${{ github.actor }} for ${{ github.workflow }} completed 🎉. See logs here." - env: - RUN: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000000..523c2b19ba89 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,141 @@ +name: Docs + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened, ready_for_review, labeled] + merge_group: + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +jobs: + set-image: + # GitHub Actions allows using 'env' in a container context. + # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 + # This workaround sets the container image for each job using 'set-image' job output. + # TODO: remove once migration is complete or this workflow is fully stable + if: contains(github.event.label.name, 'GHA-migration') || contains(github.event.pull_request.labels.*.name, 'GHA-migration') + runs-on: ubuntu-latest + outputs: + IMAGE: ${{ steps.set_image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - id: set_image + run: cat .github/env >> $GITHUB_OUTPUT + test-rustdoc: + runs-on: arc-runners-polkadot-sdk-beefy + needs: [set-image] + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - uses: actions/checkout@v4 + - run: forklift cargo doc --workspace --all-features --no-deps + env: + SKIP_WASM_BUILD: 1 + test-doc: + runs-on: arc-runners-polkadot-sdk-beefy + needs: [set-image] + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - uses: actions/checkout@v4 + - run: forklift cargo test --doc --workspace + env: + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + + build-rustdoc: + runs-on: arc-runners-polkadot-sdk-beefy + needs: [set-image, test-rustdoc] + container: + image: ${{ needs.set-image.outputs.IMAGE }} + steps: + - uses: actions/checkout@v4 + - run: forklift cargo doc --all-features --workspace --no-deps + env: + SKIP_WASM_BUILD: 1 + RUSTDOCFLAGS: "-Dwarnings --default-theme=ayu --html-in-header ./docs/sdk/assets/header.html --extend-css ./docs/sdk/assets/theme.css --html-after-content ./docs/sdk/assets/after-content.html" + - run: rm -f ./target/doc/.lock + - run: mv ./target/doc ./crate-docs + - name: Inject Simple Analytics script + run: | + script_content="" + docs_dir="./crate-docs" + + inject_simple_analytics() { + find "$1" -name '*.html' | xargs -I {} -P "$(nproc)" bash -c 'file="{}"; echo "Adding Simple Analytics script to $file"; sed -i "s||'"$2"'|" "$file";' + } + + inject_simple_analytics "$docs_dir" "$script_content" + - run: echo "" > ./crate-docs/index.html + - uses: actions/upload-artifact@v4 + with: + name: ${{ github.sha }}-doc + path: ./crate-docs/ + retention-days: 1 + if-no-files-found: error + + build-implementers-guide: + runs-on: ubuntu-latest + container: + image: paritytech/mdbook-utils:e14aae4a-20221123 + options: --user root + steps: + - uses: actions/checkout@v4 + - run: mdbook build ./polkadot/roadmap/implementers-guide + - run: mkdir -p artifacts + - run: mv polkadot/roadmap/implementers-guide/book artifacts/ + - uses: actions/upload-artifact@v4 + with: + name: ${{ github.sha }}-guide + path: ./artifacts/ + retention-days: 1 + if-no-files-found: error + + publish-rustdoc: + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + environment: subsystem-benchmarks + needs: [build-rustdoc, build-implementers-guide] + steps: + - uses: actions/checkout@v4 + with: + ref: gh-pages + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.POLKADOTSDK_GHPAGES_APP_ID }} + private-key: ${{ secrets.POLKADOTSDK_GHPAGES_APP_KEY }} + - name: Ensure destination dir does not exist + run: | + rm -rf book/ + rm -rf ${REF_NAME} + env: + REF_NAME: ${{ github.head_ref || github.ref_name }} + - name: Download rustdocs + uses: actions/download-artifact@v4 + with: + name: ${{ github.sha }}-doc + path: ${{ github.head_ref || github.ref_name }} + - name: Download guide + uses: actions/download-artifact@v4 + with: + name: ${{ github.sha }}-guide + path: /tmp + - run: mkdir -p book + - name: Move book files + run: mv /tmp/book/html/* book/ + - name: Push to GH-Pages branch + uses: github-actions-x/commit@v2.9 + with: + github-token: ${{ steps.app-token.outputs.token }} + push-branch: "gh-pages" + commit-message: "___Updated docs for ${{ github.head_ref || github.ref_name }}___" + force-add: "true" + files: ${{ github.head_ref || github.ref_name }}/ book/ + name: devops-parity + email: devops-team@parity.io diff --git a/.github/workflows/misc-update-wishlist-leaderboard.yml b/.github/workflows/misc-update-wishlist-leaderboard.yml index 68625e5433ca..326168717674 100644 --- a/.github/workflows/misc-update-wishlist-leaderboard.yml +++ b/.github/workflows/misc-update-wishlist-leaderboard.yml @@ -11,6 +11,7 @@ permissions: jobs: update-wishlist-leaderboard: + if: github.repository == 'paritytech/polkadot-sdk' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release-30_publish_release_draft.yml b/.github/workflows/release-30_publish_release_draft.yml index 6d31ca7a7365..4343dbf915a9 100644 --- a/.github/workflows/release-30_publish_release_draft.yml +++ b/.github/workflows/release-30_publish_release_draft.yml @@ -26,6 +26,7 @@ jobs: uses: "./.github/workflows/release-srtool.yml" with: excluded_runtimes: "substrate-test bp cumulus-test kitchensink minimal-template parachain-template penpal polkadot-test seedling shell frame-try sp solochain-template" + build_opts: "--features on-chain-release-build" build-binaries: runs-on: ubuntu-latest diff --git a/.github/workflows/release-50_publish-docker.yml b/.github/workflows/release-50_publish-docker.yml index f09ecf1c7998..c5d214ec68ab 100644 --- a/.github/workflows/release-50_publish-docker.yml +++ b/.github/workflows/release-50_publish-docker.yml @@ -75,12 +75,13 @@ env: # EVENT_ACTION: ${{ github.event.action }} EVENT_NAME: ${{ github.event_name }} IMAGE_TYPE: ${{ inputs.image_type }} - VERSION: ${{ inputs.version }} jobs: validate-inputs: runs-on: ubuntu-latest outputs: + version: ${{ steps.validate_inputs.outputs.VERSION }} + release_id: ${{ steps.validate_inputs.outputs.RELEASE_ID }} stable_tag: ${{ steps.validate_inputs.outputs.stable_tag }} steps: @@ -93,10 +94,12 @@ jobs: . ./.github/scripts/common/lib.sh VERSION=$(filter_version_from_input "${{ inputs.version }}") - echo "VERSION=${VERSION}" >> $GITHUB_ENV + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT RELEASE_ID=$(check_release_id "${{ inputs.release_id }}") - echo "RELEASE_ID=${RELEASE_ID}" >> $GITHUB_ENV + echo "RELEASE_ID=${RELEASE_ID}" >> $GITHUB_OUTPUT + + echo "Release ID: $RELEASE_ID" STABLE_TAG=$(validate_stable_tag ${{ inputs.stable_tag }}) echo "stable_tag=${STABLE_TAG}" >> $GITHUB_OUTPUT @@ -104,6 +107,7 @@ jobs: fetch-artifacts: # this job will be triggered for the polkadot-parachain rc and release or polkadot rc image build if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'chain-spec-builder' || inputs.image_type == 'rc' }} runs-on: ubuntu-latest + needs: [validate-inputs] steps: - name: Checkout sources @@ -129,6 +133,7 @@ jobs: run: | . ./.github/scripts/common/lib.sh + VERSION="${{ needs.validate-inputs.outputs.VERSION }}" fetch_release_artifacts_from_s3 - name: Fetch chain-spec-builder rc artifacts or release artifacts based on release id @@ -137,6 +142,7 @@ jobs: run: | . ./.github/scripts/common/lib.sh + RELEASE_ID="${{ needs.validate-inputs.outputs.RELEASE_ID }}" fetch_release_artifacts - name: Upload artifacts @@ -181,8 +187,7 @@ jobs: run: | . ./.github/scripts/common/lib.sh - RELEASE_ID=$(check_release_id "${{ inputs.release_id }}") - release=release-$RELEASE_ID && \ + release="release-${{ needs.validate-inputs.outputs.RELEASE_ID }}" && \ echo "release=${release}" >> $GITHUB_OUTPUT commit=$(git rev-parse --short HEAD) && \ @@ -198,9 +203,14 @@ jobs: id: fetch_release_refs run: | chmod a+rx $BINARY - [[ $BINARY != 'chain-spec-builder' ]] && VERSION=$(./$BINARY --version | awk '{ print $2 }' ) - release=$( echo $VERSION | cut -f1 -d- ) + if [[ $BINARY != 'chain-spec-builder' ]]; then + VERSION=$(./$BINARY --version | awk '{ print $2 }' ) + release=$( echo $VERSION | cut -f1 -d- ) + else + release=$(echo ${{ needs.validate-inputs.outputs.VERSION }} | sed 's/^v//') + fi + echo "tag=latest" >> $GITHUB_OUTPUT echo "release=${release}" >> $GITHUB_OUTPUT echo "stable=${{ needs.validate-inputs.outputs.stable_tag }}" >> $GITHUB_OUTPUT @@ -288,7 +298,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@aa33708b10e362ff993539393ff100fa93ed6a27 # v3.5.0 + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - name: Cache Docker layers uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 @@ -312,7 +322,7 @@ jobs: - name: Build and push id: docker_build - uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # v6.5.0 + uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 with: push: true file: docker/dockerfiles/polkadot/polkadot_injected_debian.Dockerfile diff --git a/.github/workflows/release-srtool.yml b/.github/workflows/release-srtool.yml index e1dc42afc6e9..262203f05005 100644 --- a/.github/workflows/release-srtool.yml +++ b/.github/workflows/release-srtool.yml @@ -9,6 +9,8 @@ on: inputs: excluded_runtimes: type: string + build_opts: + type: string outputs: published_runtimes: value: ${{ jobs.find-runtimes.outputs.runtime }} @@ -74,6 +76,8 @@ jobs: - name: Srtool build id: srtool_build uses: chevdor/srtool-actions@v0.9.2 + env: + BUILD_OPTS: ${{ inputs.build_opts }} with: chain: ${{ matrix.chain }} runtime_dir: ${{ matrix.runtime_dir }} diff --git a/.github/workflows/review-bot.yml b/.github/workflows/review-bot.yml index 80c96b0ef537..3dd5b1114813 100644 --- a/.github/workflows/review-bot.yml +++ b/.github/workflows/review-bot.yml @@ -15,7 +15,6 @@ on: jobs: review-approvals: runs-on: ubuntu-latest - environment: master steps: - name: Generate token id: app_token diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json new file mode 100644 index 000000000000..45a3acd3f166 --- /dev/null +++ b/.github/workflows/runtimes-matrix.json @@ -0,0 +1,98 @@ +[ + { + "name": "dev", + "package": "kitchensink-runtime", + "path": "substrate/frame", + "uri": null, + "is_relay": false + }, + { + "name": "westend", + "package": "westend-runtime", + "path": "polkadot/runtime/westend", + "uri": "wss://try-runtime-westend.polkadot.io:443", + "is_relay": true + }, + { + "name": "rococo", + "package": "rococo-runtime", + "path": "polkadot/runtime/rococo", + "uri": "wss://try-runtime-rococo.polkadot.io:443", + "is_relay": true + }, + { + "name": "asset-hub-westend", + "package": "asset-hub-westend-runtime", + "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", + "uri": "wss://westend-asset-hub-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "asset-hub-rococo", + "package": "asset-hub-rococo-runtime", + "path": "cumulus/parachains/runtimes/assets/asset-hub-rococo", + "uri": "wss://rococo-asset-hub-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "bridge-hub-rococo", + "package": "bridge-hub-rococo-runtime", + "path": "cumulus/parachains/runtimes/bridges/bridge-hub-rococo", + "uri": "wss://rococo-bridge-hub-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "bridge-hub-westend", + "package": "bridge-hub-rococo-runtime", + "path": "cumulus/parachains/runtimes/bridges/bridge-hub-westend", + "uri": "wss://westend-bridge-hub-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "collectives-westend", + "package": "collectives-westend-runtime", + "path": "cumulus/parachains/runtimes/collectives/collectives-westend", + "uri": "wss://westend-collectives-rpc.polkadot.io:443" + }, + { + "name": "contracts-rococo", + "package": "contracts-rococo-runtime", + "path": "cumulus/parachains/runtimes/contracts/contracts-rococo", + "uri": "wss://rococo-contracts-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "coretime-rococo", + "package": "coretime-rococo-runtime", + "path": "cumulus/parachains/runtimes/coretime/coretime-rococo", + "uri": "wss://rococo-coretime-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "coretime-westend", + "package": "coretime-westend-runtime", + "path": "cumulus/parachains/runtimes/coretime/coretime-westend", + "uri": "wss://westend-coretime-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "glutton-westend", + "package": "glutton-westend-runtime", + "path": "cumulus/parachains/runtimes/gluttons/glutton-westend", + "is_relay": false + }, + { + "name": "people-rococo", + "package": "people-rococo-runtime", + "path": "cumulus/parachains/runtimes/people/people-rococo", + "uri": "wss://rococo-people-rpc.polkadot.io:443", + "is_relay": false + }, + { + "name": "people-westend", + "package": "people-westend-runtime", + "path": "cumulus/parachains/runtimes/people/people-westend", + "uri": "wss://westend-people-rpc.polkadot.io:443", + "is_relay": false + } +] diff --git a/.github/workflows/subsystem-benchmarks.yml b/.github/workflows/subsystem-benchmarks.yml new file mode 100644 index 000000000000..7c19b420a6ac --- /dev/null +++ b/.github/workflows/subsystem-benchmarks.yml @@ -0,0 +1,82 @@ +on: + push: + branches: + - master + pull_request: + types: [ opened, synchronize, reopened, closed, labeled ] + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + +jobs: + set-image: + # TODO: remove once migration is complete or this workflow is fully stable + if: contains(github.event.label.name, 'GHA-migration') + # GitHub Actions allows using 'env' in a container context. + # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 + # This workaround sets the container image for each job using 'set-image' job output. + runs-on: ubuntu-latest + outputs: + IMAGE: ${{ steps.set_image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - id: set_image + run: cat .github/env >> $GITHUB_OUTPUT + + build: + needs: [ set-image ] + runs-on: arc-runners-polkadot-sdk-benchmark + container: + image: ${{ needs.set-image.outputs.IMAGE }} + env: + BENCH_DIR: ./charts/bench/${{ matrix.features.bench }} + BENCH_FILE_NAME: ${{ matrix.features.bench }} + strategy: + fail-fast: false + matrix: + features: [ + { name: "polkadot-availability-recovery", bench: "availability-recovery-regression-bench" }, + { name: "polkadot-availability-distribution", bench: "availability-distribution-regression-bench" }, + { name: "polkadot-node-core-approval-voting", bench: "approval-voting-regression-bench" }, + { name: "polkadot-statement-distribution", bench: "statement-distribution-regression-bench" } + ] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check Rust + run: | + rustup show + rustup +nightly show + + - name: Run Benchmarks + continue-on-error: true + id: run-benchmarks + run: | + cargo bench -p ${{ matrix.features.name }} --bench ${{ matrix.features.bench }} --features subsystem-benchmarks || echo "Benchmarks failed" + ls -lsa ./charts + mkdir -p $BENCH_DIR || echo "Directory exists" + cp charts/${BENCH_FILE_NAME}.json $BENCH_DIR + ls -lsa $BENCH_DIR + # Fixes "detected dubious ownership" error in the ci + git config --global --add safe.directory '*' + + - name: Publish result to GH Pages + if: ${{ steps.run-benchmarks.outcome == 'success' }} + uses: benchmark-action/github-action-benchmark@v1 + with: + tool: "customSmallerIsBetter" + name: ${{ env.BENCH_FILE_NAME }} + output-file-path: ${{ env.BENCH_DIR }}/${{ env.BENCH_FILE_NAME }}.json + benchmark-data-dir-path: ${{ env.BENCH_DIR }} + github-token: ${{ secrets.GITHUB_TOKEN }} + comment-on-alert: ${{ github.event_name == 'pull_request' }} # will comment on PRs if regression is detected + auto-push: false # TODO: enable when gitlab part is removed ${{ github.ref == 'refs/heads/master' }} + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1be2dd7921e0..25761fb94fd3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,17 +5,18 @@ on: branches: - master pull_request: - types: [ opened, synchronize, reopened, ready_for_review ] + types: [opened, synchronize, reopened, ready_for_review] merge_group: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: - changes: - permissions: - pull-requests: read - uses: ./.github/workflows/reusable-check-changed-files.yml + # disabled because currently doesn't work in merge queue + # changes: + # permissions: + # pull-requests: read + # uses: ./.github/workflows/reusable-check-changed-files.yml set-image: # GitHub Actions allows using 'env' in a container context. @@ -24,16 +25,28 @@ jobs: runs-on: ubuntu-latest outputs: IMAGE: ${{ steps.set_image.outputs.IMAGE }} + RUNNER: ${{ steps.set_runner.outputs.RUNNER }} steps: - name: Checkout uses: actions/checkout@v4 - id: set_image run: cat .github/env >> $GITHUB_OUTPUT + # By default we use spot machines that can be terminated at any time. + # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. + - id: set_runner + run: | + # Run merge queues on persistent runners + if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then + echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT + else + echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + fi + # This job runs all benchmarks defined in the `/bin/node/runtime` once to check that there are no errors. quick-benchmarks: - needs: [ set-image, changes ] - if: ${{ needs.changes.outputs.rust }} - runs-on: arc-runners-polkadot-sdk-beefy + needs: [set-image] + # if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} timeout-minutes: 60 container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -46,13 +59,13 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: script - run: time forklift cargo run --locked --release -p staging-node-cli --bin substrate-node --features runtime-benchmarks -- benchmark pallet --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 --quiet + run: forklift cargo run --locked --release -p staging-node-cli --bin substrate-node --features runtime-benchmarks -- benchmark pallet --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 --quiet # cf https://github.com/paritytech/polkadot-sdk/issues/1652 test-syscalls: - needs: [ set-image, changes ] - if: ${{ needs.changes.outputs.rust }} - runs-on: arc-runners-polkadot-sdk-beefy + needs: [set-image] + # if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} timeout-minutes: 60 container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -63,21 +76,21 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: script + id: test run: | forklift cargo build --locked --profile production --target x86_64-unknown-linux-musl --bin polkadot-execute-worker --bin polkadot-prepare-worker cd polkadot/scripts/list-syscalls ./list-syscalls.rb ../../../target/x86_64-unknown-linux-musl/production/polkadot-execute-worker --only-used-syscalls | diff -u execute-worker-syscalls - ./list-syscalls.rb ../../../target/x86_64-unknown-linux-musl/production/polkadot-prepare-worker --only-used-syscalls | diff -u prepare-worker-syscalls - - # todo: - # 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"; - # fi + - name: on_failure + if: failure() && steps.test.outcome == 'failure' + run: | + echo "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." >> $GITHUB_STEP_SUMMARY cargo-check-all-benches: - needs: [ set-image, changes ] - if: ${{ needs.changes.outputs.rust }} - runs-on: arc-runners-polkadot-sdk-beefy + needs: [set-image] + # if: ${{ needs.changes.outputs.rust }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} timeout-minutes: 60 container: image: ${{ needs.set-image.outputs.IMAGE }} @@ -87,4 +100,4 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: script - run: time forklift cargo check --all --benches + run: forklift cargo check --all --benches diff --git a/.gitignore b/.gitignore index e3e382af6195..0263626d832d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ **/node_modules **/target/ **/wip/*.stderr +**/__pycache__/ /.cargo/config /.envrc artifacts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7f2babc6bd47..c51d0ce5627e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -340,11 +340,6 @@ cancel-pipeline-check-tracing: needs: - job: check-tracing -cancel-pipeline-cargo-clippy: - extends: .cancel-pipeline-template - needs: - - job: cargo-clippy - cancel-pipeline-build-linux-stable: extends: .cancel-pipeline-template needs: @@ -370,16 +365,6 @@ cancel-pipeline-test-frame-ui: needs: - job: test-frame-ui -cancel-pipeline-quick-benchmarks: - extends: .cancel-pipeline-template - needs: - - job: quick-benchmarks - -cancel-pipeline-check-try-runtime: - extends: .cancel-pipeline-template - needs: - - job: check-try-runtime - cancel-pipeline-test-frame-examples-compile-to-wasm: extends: .cancel-pipeline-template needs: diff --git a/.gitlab/pipeline/check.yml b/.gitlab/pipeline/check.yml index 2b8b90ef19a4..64bef8d6e005 100644 --- a/.gitlab/pipeline/check.yml +++ b/.gitlab/pipeline/check.yml @@ -1,29 +1,3 @@ -cargo-clippy: - stage: check - extends: - - .docker-env - - .common-refs - - .pipeline-stopper-artifacts - variables: - RUSTFLAGS: "-D warnings" - script: - - SKIP_WASM_BUILD=1 cargo clippy --all-targets --locked --workspace --quiet - - SKIP_WASM_BUILD=1 cargo clippy --all-targets --all-features --locked --workspace --quiet - -check-try-runtime: - stage: check - extends: - - .docker-env - - .common-refs - script: - - time cargo check --locked --all --features try-runtime - # this is taken from cumulus - # Check that parachain-template will compile with `try-runtime` feature flag. - - time cargo check --locked -p parachain-template-node --features try-runtime - # add after https://github.com/paritytech/substrate/pull/14502 is merged - # experimental code may rely on try-runtime and vice-versa - - time cargo check --locked --all --features try-runtime,experimental - # from substrate # not sure if it's needed in monorepo check-dependency-rules: @@ -106,7 +80,7 @@ check-runtime-migration-westend: NETWORK: "westend" PACKAGE: "westend-runtime" WASM: "westend_runtime.compact.compressed.wasm" - URI: "wss://westend-try-runtime-node.parity-chains.parity.io:443" + URI: "wss://try-runtime-westend.polkadot.io:443" SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings" check-runtime-migration-rococo: @@ -119,41 +93,5 @@ check-runtime-migration-rococo: NETWORK: "rococo" PACKAGE: "rococo-runtime" WASM: "rococo_runtime.compact.compressed.wasm" - URI: "wss://rococo-try-runtime-node.parity-chains.parity.io:443" + URI: "wss://try-runtime-rococo.polkadot.io:443" SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings" - -find-fail-ci-phrase: - stage: check - variables: - CI_IMAGE: "paritytech/tools:latest" - ASSERT_REGEX: "FAIL-CI" - GIT_DEPTH: 1 - extends: - - .kubernetes-env - - .test-pr-refs - script: - - set +e - - rg --line-number --hidden --type rust --glob '!{.git,target}' "$ASSERT_REGEX" .; exit_status=$? - - if [ $exit_status -eq 0 ]; then - echo "$ASSERT_REGEX was found, exiting with 1"; - exit 1; - else - 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/test.yml b/.gitlab/pipeline/test.yml index 319c95ad6112..85f6e8dc780b 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -403,23 +403,6 @@ test-frame-ui: - time cargo test --locked -q --profile testnet --manifest-path substrate/primitives/runtime-interface/Cargo.toml - cat /cargo_target_dir/debug/.fingerprint/memory_units-759eddf317490d2b/lib-memory_units.json || true -# This job runs all benchmarks defined in the `/bin/node/runtime` once to check that there are no errors. -quick-benchmarks: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - variables: - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-C debug-assertions -D warnings" - RUST_BACKTRACE: "full" - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" - script: - - time cargo run --locked --release -p staging-node-cli --bin substrate-node --features runtime-benchmarks --quiet -- benchmark pallet --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 --quiet - quick-benchmarks-omni: stage: test extends: diff --git a/.gitlab/pipeline/zombienet/bridges.yml b/.gitlab/pipeline/zombienet/bridges.yml index 9d7a8b931193..070bfc8472d5 100644 --- a/.gitlab/pipeline/zombienet/bridges.yml +++ b/.gitlab/pipeline/zombienet/bridges.yml @@ -52,12 +52,12 @@ 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 + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-test.sh 0001-asset-transfer --docker - echo "Done" zombienet-bridges-0002-free-headers-synced-while-idle: extends: - .zombienet-bridges-common script: - - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0002-free-headers-synced-while-idle --docker + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-test.sh 0002-free-headers-synced-while-idle --docker - echo "Done" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 120000 index 000000000000..63b2a0dc1abc --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +docs/contributor/CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 120000 index 000000000000..0f645512e8e4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +docs/contributor/CONTRIBUTING.md \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6ebacc9ec5a8..bb0f01542d31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1465,15 +1465,6 @@ dependencies = [ "serde", ] -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" -dependencies = [ - "serde", -] - [[package]] name = "binary-merkle-tree" version = "13.0.0" @@ -1511,7 +1502,7 @@ dependencies = [ "proc-macro2 1.0.82", "quote 1.0.36", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.61", ] @@ -1622,13 +1613,13 @@ dependencies = [ [[package]] name = "blake2b_simd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", "arrayvec 0.7.4", - "constant_time_eq 0.2.6", + "constant_time_eq 0.3.0", ] [[package]] @@ -2909,6 +2900,7 @@ dependencies = [ "pallet-message-queue", "pallet-treasury", "pallet-utility", + "pallet-whitelist", "pallet-xcm", "parachains-common", "parity-scale-codec", @@ -4256,12 +4248,8 @@ dependencies = [ name = "cumulus-primitives-aura" version = "0.7.0" dependencies = [ - "parity-scale-codec", - "polkadot-core-primitives", - "polkadot-primitives", "sp-api", "sp-consensus-aura", - "sp-runtime", ] [[package]] @@ -4289,8 +4277,6 @@ dependencies = [ "scale-info", "sp-core", "sp-inherents", - "sp-runtime", - "sp-state-machine", "sp-trie", ] @@ -4343,8 +4329,6 @@ dependencies = [ "pallet-asset-conversion", "parity-scale-codec", "polkadot-runtime-common", - "polkadot-runtime-parachains", - "sp-io", "sp-runtime", "staging-xcm", "staging-xcm-builder", @@ -5998,7 +5982,7 @@ dependencies = [ "sc-cli", "sp-runtime", "sp-statement-store", - "sp-tracing 16.0.0", + "tracing-subscriber 0.3.18", ] [[package]] @@ -6778,9 +6762,9 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac 0.12.1", ] @@ -7423,9 +7407,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b089779ad7f80768693755a031cc14a7766aba707cbe886674e3f79e9b7e47" +checksum = "5ec465b607a36dc5dd45d48b7689bc83f679f66a3ac6b6b21cc787a11e0f8685" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", @@ -7439,9 +7423,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08163edd8bcc466c33d79e10f695cdc98c00d1e6ddfb95cec41b6b0279dd5432" +checksum = "90f0977f9c15694371b8024c35ab58ca043dbbf4b51ccb03db8858a021241df1" dependencies = [ "base64 0.22.1", "futures-util", @@ -7462,13 +7446,11 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79712302e737d23ca0daa178e752c9334846b08321d439fd89af9a384f8c830b" +checksum = "e942c55635fbf5dc421938b8558a8141c7e773720640f4f1dbe1f4164ca4e221" dependencies = [ - "anyhow", "async-trait", - "beef", "bytes", "futures-timer", "futures-util", @@ -7479,7 +7461,7 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "rand", - "rustc-hash", + "rustc-hash 2.0.0", "serde", "serde_json", "thiserror", @@ -7490,9 +7472,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d90064e04fb9d7282b1c71044ea94d0bbc6eff5621c66f1a0bce9e9de7cf3ac" +checksum = "e33774602df12b68a2310b38a535733c477ca4a498751739f89fe8dbbb62ec4c" dependencies = [ "async-trait", "base64 0.22.1", @@ -7515,9 +7497,9 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7895f186d5921065d96e16bd795e5ca89ac8356ec423fafc6e3d7cf8ec11aee4" +checksum = "6b07a2daf52077ab1b197aea69a5c990c060143835bf04c77070e98903791715" dependencies = [ "heck 0.5.0", "proc-macro-crate 3.1.0", @@ -7528,11 +7510,10 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654afab2e92e5d88ebd8a39d6074483f3f2bfdf91c5ac57fe285e7127cdd4f51" +checksum = "038fb697a709bec7134e9ccbdbecfea0e2d15183f7140254afef7c5610a3f488" dependencies = [ - "anyhow", "futures-util", "http 1.1.0", "http-body 1.0.0", @@ -7556,11 +7537,10 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c465fbe385238e861fdc4d1c85e04ada6c1fd246161d26385c1b311724d2af" +checksum = "23b67d6e008164f027afbc2e7bb79662650158d26df200040282d2aa1cbb093b" dependencies = [ - "beef", "http 1.1.0", "serde", "serde_json", @@ -7569,9 +7549,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.23.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c28759775f5cb2f1ea9667672d3fe2b0e701d1f4b7b67954e60afe7fd058b5e" +checksum = "992bf67d1132f88edf4a4f8cff474cf01abb2be203004a2b8e11c2b20795b99e" dependencies = [ "http 1.1.0", "jsonrpsee-client-transport", @@ -7910,9 +7890,9 @@ dependencies = [ [[package]] name = "libp2p-identity" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999ec70441b2fb35355076726a6bc466c932e9bdc66f6a11c6c0aa17c7ab9be0" +checksum = "55cca1eb2bc1fd29f099f3daaab7effd01e1a54b7c577d0ed082521034d912e8" dependencies = [ "bs58 0.5.1", "ed25519-dalek", @@ -8736,19 +8716,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "minimal-template" -version = "0.0.0" -dependencies = [ - "docify", - "minimal-template-node", - "minimal-template-runtime", - "pallet-minimal-template", - "polkadot-sdk-docs", - "polkadot-sdk-frame", - "simple-mermaid 0.1.1", -] - [[package]] name = "minimal-template-node" version = "0.0.0" @@ -8759,48 +8726,18 @@ dependencies = [ "futures-timer", "jsonrpsee", "minimal-template-runtime", - "polkadot-sdk-frame", - "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", + "polkadot-sdk", "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 = [ - "pallet-balances", "pallet-minimal-template", - "pallet-sudo", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", - "polkadot-sdk-frame", + "polkadot-sdk", "scale-info", - "sp-genesis-builder", - "sp-runtime", - "substrate-wasm-builder", ] [[package]] @@ -9303,7 +9240,6 @@ dependencies = [ "sc-consensus-grandpa-rpc", "sc-mixnet", "sc-rpc", - "sc-rpc-api", "sc-sync-state-rpc", "sc-transaction-pool-api", "sp-api", @@ -9591,6 +9527,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.6.1" @@ -9882,7 +9827,6 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0", ] [[package]] @@ -10089,6 +10033,7 @@ version = "28.0.0" dependencies = [ "array-bytes", "binary-merkle-tree", + "frame-benchmarking", "frame-support", "frame-system", "log", @@ -10377,7 +10322,7 @@ dependencies = [ "anyhow", "frame-system", "parity-wasm", - "polkavm-linker", + "polkavm-linker 0.9.2", "sp-runtime", "tempfile", "toml 0.8.8", @@ -10437,7 +10382,7 @@ dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", - "polkavm-derive", + "polkavm-derive 0.9.1", "scale-info", ] @@ -10496,6 +10441,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", + "log", "pallet-balances", "pallet-nomination-pools", "pallet-staking", @@ -10982,7 +10928,7 @@ name = "pallet-minimal-template" version = "0.0.0" dependencies = [ "parity-scale-codec", - "polkadot-sdk-frame", + "polkadot-sdk", "scale-info", ] @@ -11431,6 +11377,115 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-revive" +version = "0.1.0" +dependencies = [ + "array-bytes", + "assert_matches", + "bitflags 1.3.2", + "environmental", + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-assets", + "pallet-balances", + "pallet-message-queue", + "pallet-proxy", + "pallet-revive-fixtures", + "pallet-revive-proc-macro", + "pallet-revive-uapi", + "pallet-timestamp", + "pallet-utility", + "parity-scale-codec", + "paste", + "polkavm 0.10.0", + "pretty_assertions", + "scale-info", + "serde", + "sp-api", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-std 14.0.0", + "sp-tracing 16.0.0", + "staging-xcm", + "staging-xcm-builder", + "wat", +] + +[[package]] +name = "pallet-revive-fixtures" +version = "0.1.0" +dependencies = [ + "anyhow", + "frame-system", + "parity-wasm", + "polkavm-linker 0.10.0", + "sp-runtime", + "tempfile", + "toml 0.8.8", +] + +[[package]] +name = "pallet-revive-mock-network" +version = "0.1.0" +dependencies = [ + "assert_matches", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "pallet-message-queue", + "pallet-proxy", + "pallet-revive", + "pallet-revive-fixtures", + "pallet-revive-proc-macro", + "pallet-revive-uapi", + "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-tracing 16.0.0", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "xcm-simulator", +] + +[[package]] +name = "pallet-revive-proc-macro" +version = "0.1.0" +dependencies = [ + "proc-macro2 1.0.82", + "quote 1.0.36", + "syn 2.0.61", +] + +[[package]] +name = "pallet-revive-uapi" +version = "0.1.0" +dependencies = [ + "bitflags 1.3.2", + "parity-scale-codec", + "paste", + "polkavm-derive 0.10.0", + "scale-info", +] + [[package]] name = "pallet-root-offences" version = "25.0.0" @@ -12641,6 +12696,7 @@ dependencies = [ "pallet-balances", "pallet-identity", "pallet-message-queue", + "pallet-xcm", "parachains-common", "parity-scale-codec", "polkadot-runtime-common", @@ -13862,19 +13918,48 @@ dependencies = [ name = "polkadot-parachain-bin" version = "4.0.0" dependencies = [ - "assert_cmd", "asset-hub-rococo-runtime", "asset-hub-westend-runtime", - "async-trait", "bridge-hub-rococo-runtime", "bridge-hub-westend-runtime", - "clap 4.5.11", "collectives-westend-runtime", "color-eyre", - "color-print", "contracts-rococo-runtime", "coretime-rococo-runtime", "coretime-westend-runtime", + "cumulus-primitives-core", + "glutton-westend-runtime", + "hex-literal", + "log", + "parachains-common", + "penpal-runtime", + "people-rococo-runtime", + "people-westend-runtime", + "polkadot-parachain-lib", + "polkadot-service", + "rococo-parachain-runtime", + "sc-chain-spec", + "sc-cli", + "sc-service", + "seedling-runtime", + "serde", + "serde_json", + "shell-runtime", + "sp-core", + "sp-runtime", + "staging-xcm", + "substrate-build-script-utils", + "testnet-parachains-constants", +] + +[[package]] +name = "polkadot-parachain-lib" +version = "0.1.0" +dependencies = [ + "assert_cmd", + "async-trait", + "clap 4.5.11", + "color-print", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", @@ -13893,8 +13978,6 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "futures", - "glutton-westend-runtime", - "hex-literal", "jsonrpsee", "log", "nix 0.28.0", @@ -13903,56 +13986,40 @@ dependencies = [ "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", - "rococo-parachain-runtime", "sc-basic-authorship", "sc-chain-spec", "sc-cli", "sc-client-api", + "sc-client-db", "sc-consensus", "sc-executor", "sc-network", - "sc-network-sync", "sc-rpc", "sc-service", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", - "sc-transaction-pool-api", - "seedling-runtime", "serde", "serde_json", - "shell-runtime", "sp-api", "sp-block-builder", - "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", + "sp-weights", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-state-trie-migration-rpc", - "tempfile", - "testnet-parachains-constants", "tokio", "wait-timeout", ] @@ -14312,6 +14379,11 @@ dependencies = [ "pallet-recovery", "pallet-referenda", "pallet-remark", + "pallet-revive", + "pallet-revive-fixtures", + "pallet-revive-mock-network", + "pallet-revive-proc-macro", + "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", @@ -14384,6 +14456,7 @@ dependencies = [ "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", + "polkadot-parachain-lib", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-rpc", @@ -14570,6 +14643,7 @@ dependencies = [ "pallet-balances", "pallet-broker", "pallet-collective", + "pallet-contracts", "pallet-default-config-example", "pallet-democracy", "pallet-example-offchain-worker", @@ -15099,9 +15173,22 @@ checksum = "8a3693e5efdb2bf74e449cd25fd777a28bd7ed87e41f5d5da75eb31b4de48b94" dependencies = [ "libc", "log", - "polkavm-assembler", - "polkavm-common", - "polkavm-linux-raw", + "polkavm-assembler 0.9.0", + "polkavm-common 0.9.0", + "polkavm-linux-raw 0.9.0", +] + +[[package]] +name = "polkavm" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ec0c5935f2eff23cfc4653002f4f8d12b37f87a720e0631282d188c32089d6" +dependencies = [ + "libc", + "log", + "polkavm-assembler 0.10.0", + "polkavm-common 0.10.0", + "polkavm-linux-raw 0.10.0", ] [[package]] @@ -15113,6 +15200,15 @@ dependencies = [ "log", ] +[[package]] +name = "polkavm-assembler" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e4fd5a43100bf1afe9727b8130d01f966f5cfc9144d5604b21e795c2bcd80e" +dependencies = [ + "log", +] + [[package]] name = "polkavm-common" version = "0.9.0" @@ -15122,13 +15218,32 @@ dependencies = [ "log", ] +[[package]] +name = "polkavm-common" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0097b48bc0bedf9f3f537ce8f37e8f1202d8d83f9b621bdb21ff2c59b9097c50" +dependencies = [ + "log", + "polkavm-assembler 0.10.0", +] + [[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", + "polkavm-derive-impl-macro 0.9.0", +] + +[[package]] +name = "polkavm-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dcc701385c08c31bdb0569f0c51a290c580d892fa77f1dd88a7352a62679ecf" +dependencies = [ + "polkavm-derive-impl-macro 0.10.0", ] [[package]] @@ -15137,7 +15252,19 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" dependencies = [ - "polkavm-common", + "polkavm-common 0.9.0", + "proc-macro2 1.0.82", + "quote 1.0.36", + "syn 2.0.61", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7855353a5a783dd5d09e3b915474bddf66575f5a3cf45dec8d1c5e051ba320dc" +dependencies = [ + "polkavm-common 0.10.0", "proc-macro2 1.0.82", "quote 1.0.36", "syn 2.0.61", @@ -15149,7 +15276,17 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ - "polkavm-derive-impl", + "polkavm-derive-impl 0.9.0", + "syn 2.0.61", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9324fe036de37c17829af233b46ef6b5562d4a0c09bb7fdb9f8378856dee30cf" +dependencies = [ + "polkavm-derive-impl 0.10.0", "syn 2.0.61", ] @@ -15163,7 +15300,22 @@ dependencies = [ "hashbrown 0.14.3", "log", "object 0.32.2", - "polkavm-common", + "polkavm-common 0.9.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + +[[package]] +name = "polkavm-linker" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d704edfe7bdcc876784f19436d53d515b65eb07bc9a0fae77085d552c2dbbb5" +dependencies = [ + "gimli 0.28.0", + "hashbrown 0.14.3", + "log", + "object 0.36.1", + "polkavm-common 0.10.0", "regalloc2 0.9.3", "rustc-demangle", ] @@ -15174,6 +15326,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" +[[package]] +name = "polkavm-linux-raw" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26e45fa59c7e1bb12ef5289080601e9ec9b31435f6e32800a5c90c132453d126" + [[package]] name = "polling" version = "2.8.0" @@ -15783,7 +15941,7 @@ dependencies = [ "pin-project-lite", "quinn-proto 0.9.6", "quinn-udp 0.3.2", - "rustc-hash", + "rustc-hash 1.1.0", "rustls 0.20.9", "thiserror", "tokio", @@ -15802,7 +15960,7 @@ dependencies = [ "pin-project-lite", "quinn-proto 0.10.6", "quinn-udp 0.4.1", - "rustc-hash", + "rustc-hash 1.1.0", "rustls 0.21.7", "thiserror", "tokio", @@ -15818,7 +15976,7 @@ dependencies = [ "bytes", "rand", "ring 0.16.20", - "rustc-hash", + "rustc-hash 1.1.0", "rustls 0.20.9", "slab", "thiserror", @@ -15836,7 +15994,7 @@ dependencies = [ "bytes", "rand", "ring 0.16.20", - "rustc-hash", + "rustc-hash 1.1.0", "rustls 0.21.7", "slab", "thiserror", @@ -16109,7 +16267,7 @@ checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" dependencies = [ "hashbrown 0.13.2", "log", - "rustc-hash", + "rustc-hash 1.1.0", "slice-group-by", "smallvec", ] @@ -16710,6 +16868,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -16931,9 +17095,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rusty-fork" @@ -17644,7 +17808,7 @@ dependencies = [ name = "sc-executor-common" version = "0.29.0" dependencies = [ - "polkavm", + "polkavm 0.9.3", "sc-allocator", "sp-maybe-compressed-blob", "sp-wasm-interface 20.0.0", @@ -17657,7 +17821,7 @@ name = "sc-executor-polkavm" version = "0.29.0" dependencies = [ "log", - "polkavm", + "polkavm 0.9.3", "sc-executor-common", "sp-wasm-interface 20.0.0", ] @@ -18080,11 +18244,9 @@ dependencies = [ "sp-runtime", "sp-session", "sp-statement-store", - "sp-tracing 16.0.0", "sp-version", "substrate-test-runtime-client", "tokio", - "tracing-subscriber 0.3.18", ] [[package]] @@ -18110,6 +18272,7 @@ dependencies = [ name = "sc-rpc-server" version = "11.0.0" dependencies = [ + "dyn-clone", "forwarded-header-value", "futures", "governor", @@ -18119,6 +18282,7 @@ dependencies = [ "ip_network", "jsonrpsee", "log", + "sc-rpc-api", "serde", "serde_json", "substrate-prometheus-endpoint", @@ -18396,7 +18560,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "sc-client-api", "sc-tracing-proc-macro", "serde", @@ -18838,9 +19002,9 @@ checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" dependencies = [ "serde_derive", ] @@ -18865,9 +19029,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" dependencies = [ "proc-macro2 1.0.82", "quote 1.0.36", @@ -18896,9 +19060,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.121" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" +checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" dependencies = [ "indexmap 2.2.3", "itoa", @@ -19756,7 +19920,6 @@ dependencies = [ "sc-executor", "sc-network", "sc-offchain", - "sc-rpc-api", "sc-service", "sc-telemetry", "sc-transaction-pool", @@ -20045,6 +20208,7 @@ dependencies = [ "sp-keystore", "sp-mmr-primitives", "sp-runtime", + "sp-weights", "strum 0.26.2", "w3f-bls", ] @@ -20317,7 +20481,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", - "polkavm-derive", + "polkavm-derive 0.9.1", "rustversion", "secp256k1", "sp-core", @@ -20444,7 +20608,7 @@ dependencies = [ name = "sp-rpc" version = "26.0.0" dependencies = [ - "rustc-hash", + "rustc-hash 1.1.0", "serde", "serde_json", "sp-core", @@ -20506,7 +20670,7 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive", + "polkavm-derive 0.9.1", "primitive-types", "rustversion", "sp-core", @@ -21551,7 +21715,7 @@ dependencies = [ "merkleized-metadata", "parity-scale-codec", "parity-wasm", - "polkavm-linker", + "polkavm-linker 0.9.2", "sc-executor", "sp-core", "sp-io", @@ -22544,9 +22708,9 @@ dependencies = [ [[package]] name = "trie-db" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ed83be775d85ebb0e272914fff6462c39b3ddd6dc67b5c1c41271aad280c69" +checksum = "0c992b4f40c234a074d48a757efeabb1a6be88af84c0c23f7ca158950cb0ae7f" dependencies = [ "hash-db", "log", @@ -22997,11 +23161,12 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "serde", "serde_json", "wasm-bindgen-macro", @@ -23009,9 +23174,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -23036,9 +23201,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote 1.0.36", "wasm-bindgen-macro-support", @@ -23046,9 +23211,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2 1.0.82", "quote 1.0.36", @@ -23059,9 +23224,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-bindgen-test" diff --git a/Cargo.toml b/Cargo.toml index 7ae7c3bd1811..f26a894960a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,6 +136,7 @@ members = [ "cumulus/parachains/runtimes/testing/penpal", "cumulus/parachains/runtimes/testing/rococo-parachain", "cumulus/polkadot-parachain", + "cumulus/polkadot-parachain/polkadot-parachain-lib", "cumulus/primitives/aura", "cumulus/primitives/core", "cumulus/primitives/parachain-inherent", @@ -394,6 +395,11 @@ members = [ "substrate/frame/recovery", "substrate/frame/referenda", "substrate/frame/remark", + "substrate/frame/revive", + "substrate/frame/revive/fixtures", + "substrate/frame/revive/mock-network", + "substrate/frame/revive/proc-macro", + "substrate/frame/revive/uapi", "substrate/frame/root-offences", "substrate/frame/root-testing", "substrate/frame/safe-mode", @@ -522,7 +528,6 @@ members = [ "substrate/utils/prometheus", "substrate/utils/substrate-bip39", "substrate/utils/wasm-builder", - "templates/minimal", "templates/minimal/node", "templates/minimal/pallets/template", "templates/minimal/runtime", @@ -536,6 +541,7 @@ members = [ ] default-members = [ + "cumulus/polkadot-parachain", "polkadot", "substrate/bin/node/cli", ] @@ -578,7 +584,7 @@ ahash = { version = "0.8.2" } alloy-primitives = { version = "0.4.2", default-features = false } alloy-sol-types = { version = "0.4.2", default-features = false } always-assert = { version = "0.1" } -anyhow = { version = "1.0.81" } +anyhow = { version = "1.0.81", default-features = false } aquamarine = { version = "0.5.0" } arbitrary = { version = "1.3.2" } ark-bls12-377 = { version = "0.4.0", default-features = false } @@ -615,7 +621,7 @@ bip39 = { version = "2.0.0" } bitflags = { version = "1.3.2" } bitvec = { version = "1.0.1", default-features = false } blake2 = { version = "0.10.4", default-features = false } -blake2b_simd = { version = "1.0.1", default-features = false } +blake2b_simd = { version = "1.0.2", default-features = false } blake3 = { version = "1.5" } bounded-collections = { version = "0.2.0", default-features = false } bounded-vec = { version = "0.7" } @@ -806,8 +812,8 @@ isahc = { version = "1.2" } itertools = { version = "0.11" } jobserver = { version = "0.1.26" } jsonpath_lib = { version = "0.3" } -jsonrpsee = { version = "0.23.2" } -jsonrpsee-core = { version = "0.23.2" } +jsonrpsee = { version = "0.24.3" } +jsonrpsee-core = { version = "0.24.3" } k256 = { version = "0.13.3", default-features = false } kitchensink-runtime = { path = "substrate/bin/node/runtime" } kvdb = { version = "0.13.0" } @@ -819,7 +825,7 @@ lazy_static = { version = "1.4.0" } libc = { version = "0.2.155" } libfuzzer-sys = { version = "0.4" } libp2p = { version = "0.52.4" } -libp2p-identity = { version = "0.2.3" } +libp2p-identity = { version = "0.2.9" } libsecp256k1 = { version = "0.7.0", default-features = false } linked-hash-map = { version = "0.5.4" } linked_hash_set = { version = "0.1.4" } @@ -893,7 +899,7 @@ pallet-collator-selection = { path = "cumulus/pallets/collator-selection", defau pallet-collective = { path = "substrate/frame/collective", default-features = false } pallet-collective-content = { path = "cumulus/parachains/pallets/collective-content", default-features = false } pallet-contracts = { path = "substrate/frame/contracts", default-features = false } -pallet-contracts-fixtures = { path = "substrate/frame/contracts/fixtures" } +pallet-contracts-fixtures = { path = "substrate/frame/contracts/fixtures", default-features = false } pallet-contracts-mock-network = { default-features = false, path = "substrate/frame/contracts/mock-network" } pallet-contracts-proc-macro = { path = "substrate/frame/contracts/proc-macro", default-features = false } pallet-contracts-uapi = { path = "substrate/frame/contracts/uapi", default-features = false } @@ -949,6 +955,11 @@ pallet-ranked-collective = { path = "substrate/frame/ranked-collective", default pallet-recovery = { path = "substrate/frame/recovery", default-features = false } pallet-referenda = { path = "substrate/frame/referenda", default-features = false } pallet-remark = { default-features = false, path = "substrate/frame/remark" } +pallet-revive = { path = "substrate/frame/revive", default-features = false } +pallet-revive-fixtures = { path = "substrate/frame/revive/fixtures", default-features = false } +pallet-revive-mock-network = { default-features = false, path = "substrate/frame/revive/mock-network" } +pallet-revive-proc-macro = { path = "substrate/frame/revive/proc-macro", default-features = false } +pallet-revive-uapi = { path = "substrate/frame/revive/uapi", default-features = false } pallet-root-offences = { default-features = false, path = "substrate/frame/root-offences" } pallet-root-testing = { path = "substrate/frame/root-testing", default-features = false } pallet-safe-mode = { default-features = false, path = "substrate/frame/safe-mode" } @@ -1042,6 +1053,7 @@ polkadot-node-subsystem-test-helpers = { path = "polkadot/node/subsystem-test-he polkadot-node-subsystem-types = { path = "polkadot/node/subsystem-types", default-features = false } polkadot-node-subsystem-util = { path = "polkadot/node/subsystem-util", default-features = false } polkadot-overseer = { path = "polkadot/node/overseer", default-features = false } +polkadot-parachain-lib = { path = "cumulus/polkadot-parachain/polkadot-parachain-lib", default-features = false } polkadot-parachain-primitives = { path = "polkadot/parachain", default-features = false } polkadot-primitives = { path = "polkadot/primitives", default-features = false } polkadot-primitives-test-helpers = { path = "polkadot/primitives/test-helpers" } @@ -1058,7 +1070,7 @@ polkadot-subsystem-bench = { path = "polkadot/node/subsystem-bench" } polkadot-test-client = { path = "polkadot/node/test/client" } polkadot-test-runtime = { path = "polkadot/runtime/test-runtime" } polkadot-test-service = { path = "polkadot/node/test/service" } -polkavm = "0.9.3" +polkavm = { version = "0.9.3", default-features = false } polkavm-derive = "0.9.1" polkavm-linker = "0.9.2" portpicker = { version = "0.1.1" } @@ -1103,7 +1115,7 @@ rstest = { version = "0.18.2" } rustc-hash = { version = "1.1.0" } rustc-hex = { version = "2.1.0", default-features = false } rustix = { version = "0.36.7", default-features = false } -rustversion = { version = "1.0.6" } +rustversion = { version = "1.0.17" } rusty-fork = { version = "0.3.0", default-features = false } safe-mix = { version = "1.0", default-features = false } sc-allocator = { path = "substrate/client/allocator", default-features = false } @@ -1172,10 +1184,10 @@ secp256k1 = { version = "0.28.0", default-features = false } secrecy = { version = "0.8.0", default-features = false } seedling-runtime = { path = "cumulus/parachains/runtimes/starters/seedling" } separator = { version = "0.4.1" } -serde = { version = "1.0.204", default-features = false } +serde = { version = "1.0.206", default-features = false } serde-big-array = { version = "0.3.2" } serde_derive = { version = "1.0.117" } -serde_json = { version = "1.0.121", default-features = false } +serde_json = { version = "1.0.124", default-features = false } serde_yaml = { version = "0.9" } serial_test = { version = "2.0.0" } sha1 = { version = "0.10.6" } @@ -1319,7 +1331,7 @@ tracing-log = { version = "0.2.0" } tracing-subscriber = { version = "0.3.18" } tracking-allocator = { path = "polkadot/node/tracking-allocator", default-features = false, package = "staging-tracking-allocator" } trie-bench = { version = "0.39.0" } -trie-db = { version = "0.29.0", default-features = false } +trie-db = { version = "0.29.1", default-features = false } trie-root = { version = "0.18.0", default-features = false } trie-standardmap = { version = "0.16.0" } trybuild = { version = "1.0.89" } diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index bf105b140401..c36313a14764 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -70,7 +70,6 @@ use bp_runtime::{ }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound}; -use sp_runtime::traits::UniqueSaturatedFrom; use sp_std::{marker::PhantomData, prelude::*}; mod inbound_lane; @@ -153,40 +152,6 @@ pub mod pallet { type OperatingModeStorage = PalletOperatingMode; } - #[pallet::hooks] - impl, I: 'static> Hooks> for Pallet - where - u32: TryFrom>, - { - fn on_idle(_block: BlockNumberFor, remaining_weight: Weight) -> Weight { - // we'll need at least to read outbound lane state, kill a message and update lane state - let db_weight = T::DbWeight::get(); - if !remaining_weight.all_gte(db_weight.reads_writes(1, 2)) { - return Weight::zero() - } - - // messages from lane with index `i` in `ActiveOutboundLanes` are pruned when - // `System::block_number() % lanes.len() == i`. Otherwise we need to read lane states on - // every block, wasting the whole `remaining_weight` for nothing and causing starvation - // of the last lane pruning - let active_lanes = T::ActiveOutboundLanes::get(); - let active_lanes_len = (active_lanes.len() as u32).into(); - let active_lane_index = u32::unique_saturated_from( - frame_system::Pallet::::block_number() % active_lanes_len, - ); - let active_lane_id = active_lanes[active_lane_index as usize]; - - // first db read - outbound lane state - let mut active_lane = outbound_lane::(active_lane_id); - let mut used_weight = db_weight.reads(1); - // and here we'll have writes - used_weight += active_lane.prune_messages(db_weight, remaining_weight - used_weight); - - // we already checked we have enough `remaining_weight` to cover this `used_weight` - used_weight - } - } - #[pallet::call] impl, I: 'static> Pallet { /// Change `PalletOwner`. @@ -610,6 +575,14 @@ pub mod pallet { } } + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state() + } + } + impl, I: 'static> Pallet { /// Get stored data of the outbound message with given nonce. pub fn outbound_message_data(lane: LaneId, nonce: MessageNonce) -> Option { @@ -644,6 +617,58 @@ pub mod pallet { } } + #[cfg(any(feature = "try-runtime", test))] + impl, I: 'static> Pallet { + /// Ensure the correctness of the state of this pallet. + pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state_for_outbound_lanes() + } + + /// Ensure the correctness of the state of outbound lanes. + pub fn do_try_state_for_outbound_lanes() -> Result<(), sp_runtime::TryRuntimeError> { + use sp_runtime::traits::One; + use sp_std::vec::Vec; + + // collect unpruned lanes + let mut unpruned_lanes = Vec::new(); + for (lane_id, lane_data) in OutboundLanes::::iter() { + let Some(expected_last_prunned_nonce) = + lane_data.oldest_unpruned_nonce.checked_sub(One::one()) + else { + continue; + }; + + // collect message_nonces that were supposed to be pruned + let mut unpruned_message_nonces = Vec::new(); + const MAX_MESSAGES_ITERATION: u64 = 16; + let start_nonce = + expected_last_prunned_nonce.checked_sub(MAX_MESSAGES_ITERATION).unwrap_or(0); + for current_nonce in start_nonce..=expected_last_prunned_nonce { + // check a message for current_nonce + if OutboundMessages::::contains_key(MessageKey { + lane_id, + nonce: current_nonce, + }) { + unpruned_message_nonces.push(current_nonce); + } + } + + if !unpruned_message_nonces.is_empty() { + log::warn!( + target: LOG_TARGET, + "do_try_state_for_outbound_lanes for lane_id: {lane_id:?} with lane_data: {lane_data:?} found unpruned_message_nonces: {unpruned_message_nonces:?}", + ); + unpruned_lanes.push((lane_id, lane_data, unpruned_message_nonces)); + } + } + + // ensure messages before `oldest_unpruned_nonce` are really pruned. + ensure!(unpruned_lanes.is_empty(), "Found unpruned lanes!"); + + Ok(()) + } + } + /// Get-parameter that returns number of active outbound lanes that the pallet maintains. pub struct MaybeOutboundLanesCount(PhantomData<(T, I)>); diff --git a/bridges/modules/messages/src/outbound_lane.rs b/bridges/modules/messages/src/outbound_lane.rs index fcdddf199dc6..788a13e82b1b 100644 --- a/bridges/modules/messages/src/outbound_lane.rs +++ b/bridges/modules/messages/src/outbound_lane.rs @@ -22,13 +22,9 @@ use bp_messages::{ ChainWithMessages, DeliveredMessages, LaneId, MessageNonce, OutboundLaneData, UnrewardedRelayer, }; use codec::{Decode, Encode}; -use frame_support::{ - traits::Get, - weights::{RuntimeDbWeight, Weight}, - BoundedVec, PalletError, -}; +use frame_support::{traits::Get, BoundedVec, PalletError}; use scale_info::TypeInfo; -use sp_runtime::{traits::Zero, RuntimeDebug}; +use sp_runtime::RuntimeDebug; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData}; /// Outbound lane storage. @@ -143,41 +139,17 @@ impl OutboundLane { ensure_unrewarded_relayers_are_correct(confirmed_messages.end, relayers)?; + // prune all confirmed messages + for nonce in confirmed_messages.begin..=confirmed_messages.end { + self.storage.remove_message(&nonce); + } + data.latest_received_nonce = confirmed_messages.end; + data.oldest_unpruned_nonce = data.latest_received_nonce.saturating_add(1); self.storage.set_data(data); Ok(Some(confirmed_messages)) } - - /// Prune at most `max_messages_to_prune` already received messages. - /// - /// Returns weight, consumed by messages pruning and lane state update. - pub fn prune_messages( - &mut self, - db_weight: RuntimeDbWeight, - mut remaining_weight: Weight, - ) -> Weight { - let write_weight = db_weight.writes(1); - let two_writes_weight = write_weight + write_weight; - let mut spent_weight = Weight::zero(); - let mut data = self.storage.data(); - while remaining_weight.all_gte(two_writes_weight) && - data.oldest_unpruned_nonce <= data.latest_received_nonce - { - self.storage.remove_message(&data.oldest_unpruned_nonce); - - spent_weight += write_weight; - remaining_weight -= write_weight; - data.oldest_unpruned_nonce += 1; - } - - if !spent_weight.is_zero() { - spent_weight += write_weight; - self.storage.set_data(data); - } - - spent_weight - } } /// Verifies unrewarded relayers vec. @@ -221,7 +193,6 @@ mod tests { REGULAR_PAYLOAD, TEST_LANE_ID, }, }; - use frame_support::weights::constants::RocksDbWeight; use sp_std::ops::RangeInclusive; fn unrewarded_relayers( @@ -281,7 +252,7 @@ mod tests { ); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); }); } @@ -302,7 +273,7 @@ mod tests { ); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 2); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3); assert_eq!( lane.confirm_delivery(3, 3, &unrewarded_relayers(3..=3)), @@ -310,7 +281,7 @@ mod tests { ); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); }); } @@ -331,12 +302,12 @@ mod tests { assert_eq!(lane.confirm_delivery(3, 3, &unrewarded_relayers(1..=3)), Ok(None),); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); assert_eq!(lane.confirm_delivery(1, 2, &unrewarded_relayers(1..=1)), Ok(None),); assert_eq!(lane.storage.data().latest_generated_nonce, 3); assert_eq!(lane.storage.data().latest_received_nonce, 3); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); + assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); }); } @@ -394,57 +365,6 @@ mod tests { ); } - #[test] - fn prune_messages_works() { - run_test(|| { - let mut lane = outbound_lane::(TEST_LANE_ID); - // when lane is empty, nothing is pruned - assert_eq!( - lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), - Weight::zero() - ); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); - // when nothing is confirmed, nothing is pruned - 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()); - assert_eq!( - lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), - Weight::zero() - ); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 1); - // after confirmation, some messages are received - assert_eq!( - lane.confirm_delivery(2, 2, &unrewarded_relayers(1..=2)), - Ok(Some(delivered_messages(1..=2))), - ); - assert_eq!( - lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), - RocksDbWeight::get().writes(3), - ); - assert!(lane.storage.message(&1).is_none()); - assert!(lane.storage.message(&2).is_none()); - assert!(lane.storage.message(&3).is_some()); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 3); - // after last message is confirmed, everything is pruned - assert_eq!( - lane.confirm_delivery(1, 3, &unrewarded_relayers(3..=3)), - Ok(Some(delivered_messages(3..=3))), - ); - assert_eq!( - lane.prune_messages(RocksDbWeight::get(), RocksDbWeight::get().writes(101)), - RocksDbWeight::get().writes(2), - ); - assert!(lane.storage.message(&1).is_none()); - assert!(lane.storage.message(&2).is_none()); - assert!(lane.storage.message(&3).is_none()); - assert_eq!(lane.storage.data().oldest_unpruned_nonce, 4); - }); - } - #[test] fn confirm_delivery_detects_when_more_than_expected_messages_are_confirmed() { run_test(|| { diff --git a/bridges/modules/messages/src/tests/pallet_tests.rs b/bridges/modules/messages/src/tests/pallet_tests.rs index 42e1042717de..f7a288d649a9 100644 --- a/bridges/modules/messages/src/tests/pallet_tests.rs +++ b/bridges/modules/messages/src/tests/pallet_tests.rs @@ -38,15 +38,14 @@ use bp_runtime::{BasicOperatingMode, PreComputedSize, RangeInclusiveExt, Size}; use bp_test_utils::generate_owned_bridge_module_tests; use codec::Encode; use frame_support::{ - assert_noop, assert_ok, + assert_err, assert_noop, assert_ok, dispatch::Pays, storage::generator::{StorageMap, StorageValue}, - traits::Hooks, weights::Weight, }; use frame_system::{EventRecord, Pallet as System, Phase}; use sp_core::Get; -use sp_runtime::DispatchError; +use sp_runtime::{BoundedVec, DispatchError}; fn get_ready_for_events() { System::::set_block_number(1); @@ -99,6 +98,7 @@ fn receive_messages_delivery_proof() { last_delivered_nonce: 1, }, )); + assert_ok!(Pallet::::do_try_state()); assert_eq!( System::::events(), @@ -160,6 +160,7 @@ fn pallet_rejects_transactions_if_halted() { ), Error::::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted), ); + assert_ok!(Pallet::::do_try_state()); }); } @@ -220,6 +221,7 @@ fn pallet_rejects_new_messages_in_rejecting_outbound_messages_operating_mode() { last_delivered_nonce: 1, }, )); + assert_ok!(Pallet::::do_try_state()); }); } @@ -395,10 +397,14 @@ fn receive_messages_proof_rejects_proof_with_too_many_messages() { #[test] fn receive_messages_delivery_proof_works() { run_test(|| { + assert_eq!(OutboundLanes::::get(TEST_LANE_ID).latest_received_nonce, 0); + assert_eq!(OutboundLanes::::get(TEST_LANE_ID).oldest_unpruned_nonce, 1); + send_regular_message(TEST_LANE_ID); receive_messages_delivery_proof(); - assert_eq!(OutboundLanes::::get(TEST_LANE_ID).latest_received_nonce, 1,); + assert_eq!(OutboundLanes::::get(TEST_LANE_ID).latest_received_nonce, 1); + assert_eq!(OutboundLanes::::get(TEST_LANE_ID).oldest_unpruned_nonce, 2); }); } @@ -428,6 +434,7 @@ fn receive_messages_delivery_proof_rewards_relayers() { }, ); assert_ok!(result); + assert_ok!(Pallet::::do_try_state()); assert_eq!( result.unwrap().actual_weight.unwrap(), TestWeightInfo::receive_messages_delivery_proof_weight( @@ -467,6 +474,7 @@ fn receive_messages_delivery_proof_rewards_relayers() { }, ); assert_ok!(result); + assert_ok!(Pallet::::do_try_state()); // even though the pre-dispatch weight was for two messages, the actual weight is // for single message only assert_eq!( @@ -852,129 +860,6 @@ fn inbound_message_details_works() { }); } -#[test] -fn on_idle_callback_respects_remaining_weight() { - run_test(|| { - 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), - prepare_messages_delivery_proof( - TEST_LANE_ID, - InboundLaneData { - last_confirmed_nonce: 4, - relayers: vec![unrewarded_relayer(1, 4, TEST_RELAYER_A)].into(), - }, - ), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 4, - total_messages: 4, - last_delivered_nonce: 4, - }, - )); - - // all 4 messages may be pruned now - assert_eq!(outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, 4); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 1); - System::::set_block_number(2); - - // if passed wight is too low to do anything - let dbw = DbWeight::get(); - assert_eq!(Pallet::::on_idle(0, dbw.reads_writes(1, 1)), Weight::zero(),); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 1); - - // if passed wight is enough to prune single message - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(1, 2)), - dbw.reads_writes(1, 2), - ); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 2); - - // if passed wight is enough to prune two more messages - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(1, 3)), - dbw.reads_writes(1, 3), - ); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 4); - - // if passed wight is enough to prune many messages - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(100, 100)), - dbw.reads_writes(1, 2), - ); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 5); - }); -} - -#[test] -fn on_idle_callback_is_rotating_lanes_to_prune() { - run_test(|| { - // send + receive confirmation for lane 1 - send_regular_message(TEST_LANE_ID); - receive_messages_delivery_proof(); - // send + receive confirmation for lane 2 - send_regular_message(TEST_LANE_ID_2); - assert_ok!(Pallet::::receive_messages_delivery_proof( - RuntimeOrigin::signed(1), - prepare_messages_delivery_proof( - TEST_LANE_ID_2, - InboundLaneData { - last_confirmed_nonce: 1, - relayers: vec![unrewarded_relayer(1, 1, TEST_RELAYER_A)].into(), - }, - ), - UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - messages_in_oldest_entry: 1, - total_messages: 1, - last_delivered_nonce: 1, - }, - )); - - // nothing is pruned yet - assert_eq!(outbound_lane::(TEST_LANE_ID).data().latest_received_nonce, 1); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 1); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().latest_received_nonce, - 1 - ); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, - 1 - ); - - // in block#2.on_idle lane messages of lane 1 are pruned - let dbw = DbWeight::get(); - System::::set_block_number(2); - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(100, 100)), - dbw.reads_writes(1, 2), - ); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 2); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, - 1 - ); - - // in block#3.on_idle lane messages of lane 2 are pruned - System::::set_block_number(3); - - assert_eq!( - Pallet::::on_idle(0, dbw.reads_writes(100, 100)), - dbw.reads_writes(1, 2), - ); - assert_eq!(outbound_lane::(TEST_LANE_ID).data().oldest_unpruned_nonce, 2); - assert_eq!( - outbound_lane::(TEST_LANE_ID_2).data().oldest_unpruned_nonce, - 2 - ); - }); -} - #[test] fn outbound_message_from_unconfigured_lane_is_rejected() { run_test(|| { @@ -1098,3 +983,33 @@ fn maybe_outbound_lanes_count_returns_correct_value() { Some(mock::ActiveOutboundLanes::get().len() as u32) ); } + +#[test] +fn do_try_state_for_outbound_lanes_works() { + run_test(|| { + let lane_id = TEST_LANE_ID; + + // setup delivered nonce 1 + OutboundLanes::::insert( + lane_id, + OutboundLaneData { + oldest_unpruned_nonce: 2, + latest_received_nonce: 1, + latest_generated_nonce: 0, + }, + ); + // store message for nonce 1 + OutboundMessages::::insert( + MessageKey { lane_id, nonce: 1 }, + BoundedVec::default(), + ); + assert_err!( + Pallet::::do_try_state(), + sp_runtime::TryRuntimeError::Other("Found unpruned lanes!") + ); + + // remove message for nonce 1 + OutboundMessages::::remove(MessageKey { lane_id, nonce: 1 }); + assert_ok!(Pallet::::do_try_state()); + }) +} diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml index beb03b9381d4..4c25566607dc 100644 --- a/bridges/relays/utils/Cargo.toml +++ b/bridges/relays/utils/Cargo.toml @@ -11,7 +11,7 @@ publish = false workspace = true [dependencies] -anyhow = { workspace = true } +anyhow = { workspace = true, default-features = true } async-std = { workspace = true } async-trait = { workspace = true } backoff = { workspace = true } diff --git a/bridges/testing/README.md b/bridges/testing/README.md index bd467a410d01..158dfd73b1ad 100644 --- a/bridges/testing/README.md +++ b/bridges/testing/README.md @@ -1,31 +1,29 @@ # 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. +onchain and offchain bridges code. -To start those tests, you need to: +Prerequisites for running the tests locally: - 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; + [`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; + [`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); + `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. + [`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`; +- copy the `substrate-relay` binary, built in the previous step, to `~/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, any test can be run using the `run-test.sh` command. +Example: `./run-new-test.sh 0001-asset-transfer` -After that, you could run tests with the `./run-tests.sh` command. Hopefully, it'll show the +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/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh index ef4a5597902f..54633449134b 100755 --- a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh +++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh @@ -270,7 +270,7 @@ case "$1" in "//Alice" \ 1000 \ "ws://127.0.0.1:9910" \ - "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Westend" } } }')" \ + "$(jq --null-input '{ "parents": 2, "interior": { "X1": [{ "GlobalConsensus": "Westend" }] } }')" \ "$GLOBAL_CONSENSUS_WESTEND_SOVEREIGN_ACCOUNT" \ 10000000000 \ true @@ -329,7 +329,7 @@ case "$1" in "//Alice" \ 1000 \ "ws://127.0.0.1:9010" \ - "$(jq --null-input '{ "parents": 2, "interior": { "X1": { "GlobalConsensus": "Rococo" } } }')" \ + "$(jq --null-input '{ "parents": 2, "interior": { "X1": [{ "GlobalConsensus": "Rococo" }] } }')" \ "$GLOBAL_CONSENSUS_ROCOCO_SOVEREIGN_ACCOUNT" \ 10000000000 \ true 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-active.js similarity index 94% rename from bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js rename to bridges/testing/framework/js-helpers/only-required-headers-synced-when-active.js index 8c3130e4fd96..61738a21e38e 100644 --- a/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js +++ b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-active.js @@ -65,8 +65,12 @@ async function run(nodeName, networkInfo, args) { // wait until we have received + delivered messages OR until timeout await utils.pollUntil( exitAfterSeconds, - () => { return atLeastOneMessageReceived && atLeastOneMessageDelivered; }, - () => { unsubscribe(); }, + () => { + return atLeastOneMessageReceived && atLeastOneMessageDelivered; + }, + () => { + unsubscribe(); + }, () => { if (!atLeastOneMessageReceived) { throw new Error("No messages received from bridged chain"); @@ -78,4 +82,4 @@ async function run(nodeName, networkInfo, args) { ); } -module.exports = { run } +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 index 27287118547f..7b343ed97a88 100644 --- a/bridges/testing/framework/js-helpers/wrapped-assets-balance.js +++ b/bridges/testing/framework/js-helpers/wrapped-assets-balance.js @@ -8,7 +8,7 @@ async function run(nodeName, networkInfo, args) { const bridgedNetworkName = args[2]; while (true) { const foreignAssetAccount = await api.query.foreignAssets.account( - { parents: 2, interior: { X1: { GlobalConsensus: bridgedNetworkName } } }, + { parents: 2, interior: { X1: [{ GlobalConsensus: bridgedNetworkName }] } }, accountAddress ); if (foreignAssetAccount.isSome) { diff --git a/bridges/testing/run-new-test.sh b/bridges/testing/run-test.sh similarity index 100% rename from bridges/testing/run-new-test.sh rename to bridges/testing/run-test.sh diff --git a/bridges/testing/run-tests.sh b/bridges/testing/run-tests.sh deleted file mode 100755 index fd12b57f5334..000000000000 --- a/bridges/testing/run-tests.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/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 path 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/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 deleted file mode 100644 index 07b91481dc7c..000000000000 --- a/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index a6b11fc24052..000000000000 --- a/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl +++ /dev/null @@ -1,26 +0,0 @@ -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/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 000000000000..897b79eeff23 --- /dev/null +++ b/bridges/testing/tests/0003-required-headers-synced-while-active/rococo-to-westend.zndsl @@ -0,0 +1,7 @@ +Description: While relayer is active, we only sync mandatory and required Rococo (and Rococo BH) headers to Westend BH. +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# ensure that relayer won't sync any extra headers while delivering messages and confirmations +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds + diff --git a/bridges/testing/tests/0003-required-headers-synced-while-active/run.sh b/bridges/testing/tests/0003-required-headers-synced-while-active/run.sh new file mode 100755 index 000000000000..8fad38f22052 --- /dev/null +++ b/bridges/testing/tests/0003-required-headers-synced-while-active/run.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e + +# TODO: This test doesn't work. It was added at a time when we couldn't run it because we didn't have the scafolding. +# It needs to be fixed. For the moment we keep it in the repo as it is since the idea has value. +# But we don't run it in the CI. + +source "${BASH_SOURCE%/*}/../../framework/utils/common.sh" +source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh" + +export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend` + +logs_dir=$TEST_DIR/logs + +$ENV_PATH/spawn.sh --init & +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 + +echo "Sending message from Rococo to Westend" +$ENV_PATH/helper.sh auto-log reserve-transfer-assets-from-asset-hub-rococo-local 5000000000000 +echo + +echo "Sending message from Westend to Rococo" +$ENV_PATH/helper.sh auto-log reserve-transfer-assets-from-asset-hub-westend-local 5000000000000 +echo + + +# Start the relayer with a 30s delay +# We want to be sure that the messages won't be relayed before starting the js script in `rococo-to-westend.zndsl` +start_relayer_log=$logs_dir/start_relayer.log +echo -e "The rococo-westend relayer will be started in 30s. Logs will be available at: $start_relayer_log\n" +(sleep 30 && $ENV_PATH/start_relayer.sh \ + $rococo_dir $westend_dir finality_relayer_pid parachains_relayer_pid messages_relayer_pid > $start_relayer_log)& + +run_zndsl ${BASH_SOURCE%/*}/rococo-to-westend.zndsl $westend_dir + diff --git a/cumulus/client/cli/src/lib.rs b/cumulus/client/cli/src/lib.rs index a7b2eb19de88..564d7b58c94d 100644 --- a/cumulus/client/cli/src/lib.rs +++ b/cumulus/client/cli/src/lib.rs @@ -21,13 +21,13 @@ use std::{ fs, io::{self, Write}, - net::SocketAddr, path::PathBuf, sync::Arc, }; use codec::Encode; use sc_chain_spec::ChainSpec; +use sc_cli::RpcEndpoint; use sc_client_api::HeaderBackend; use sc_service::{ config::{PrometheusConfig, RpcBatchRequestConfig, TelemetryEndpoints}, @@ -423,7 +423,7 @@ impl sc_cli::CliConfiguration for NormalizedRunCmd { self.base.rpc_cors(is_dev) } - fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result> { + fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result>> { self.base.rpc_addr(default_listen_port) } diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 01e07cb395a9..47e2d8572c3f 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -54,3 +54,7 @@ polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } + +[features] +# Allows collator to use full PoV size for block building +full-pov-size = [] diff --git a/cumulus/client/consensus/aura/src/collators/basic.rs b/cumulus/client/consensus/aura/src/collators/basic.rs index 4efd50a04ec6..d843483b79fa 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -138,6 +138,7 @@ where }; let mut last_processed_slot = 0; + let mut last_relay_chain_block = Default::default(); while let Some(request) = collation_requests.next().await { macro_rules! reject_with_error { @@ -215,11 +216,13 @@ where // // 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. + // this "hack" to only produce one block per slot per relay chain fork. // // 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() { + if last_processed_slot >= *claim.slot() && + last_relay_chain_block < *relay_parent_header.number() + { continue } @@ -234,6 +237,16 @@ where .await ); + let allowed_pov_size = if cfg!(feature = "full-pov-size") { + validation_data.max_pov_size + } else { + // Set the block limit to 50% of the maximum PoV size. + // + // TODO: If we got benchmarking that includes the proof size, + // we should be able to use the maximum pov size. + validation_data.max_pov_size / 2 + } as usize; + let maybe_collation = try_request!( collator .collate( @@ -242,11 +255,7 @@ where None, (parachain_inherent_data, other_inherent_data), params.authoring_duration, - // Set the block limit to 50% of the maximum PoV size. - // - // TODO: If we got benchmarking that includes the proof size, - // we should be able to use the maximum pov size. - (validation_data.max_pov_size / 2) as usize, + allowed_pov_size, ) .await ); @@ -261,6 +270,7 @@ where } last_processed_slot = *claim.slot(); + last_relay_chain_block = *relay_parent_header.number(); } } } diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 02d60538a732..0be1e0a23ca5 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -412,6 +412,16 @@ where ) .await; + let allowed_pov_size = if cfg!(feature = "full-pov-size") { + validation_data.max_pov_size + } else { + // Set the block limit to 50% of the maximum PoV size. + // + // TODO: If we got benchmarking that includes the proof size, + // we should be able to use the maximum pov size. + validation_data.max_pov_size / 2 + } as usize; + match collator .collate( &parent_header, @@ -419,11 +429,7 @@ where None, (parachain_inherent_data, other_inherent_data), params.authoring_duration, - // Set the block limit to 50% of the maximum PoV size. - // - // TODO: If we got benchmarking that includes the proof size, - // we should be able to use the maximum pov size. - (validation_data.max_pov_size / 2) as usize, + allowed_pov_size, ) .await { diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs index 1fbc0689da86..b70cfe3841b7 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs @@ -350,6 +350,16 @@ where ) .await; + let allowed_pov_size = if cfg!(feature = "full-pov-size") { + validation_data.max_pov_size + } else { + // Set the block limit to 50% of the maximum PoV size. + // + // TODO: If we got benchmarking that includes the proof size, + // we should be able to use the maximum pov size. + validation_data.max_pov_size / 2 + } as usize; + let Ok(Some(candidate)) = collator .build_block_and_import( &parent_header, @@ -357,11 +367,7 @@ where None, (parachain_inherent_data, other_inherent_data), authoring_duration, - // Set the block limit to 50% of the maximum PoV size. - // - // TODO: If we got benchmarking that includes the proof size, - // we should be able to use the maximum pov size. - (validation_data.max_pov_size / 2) as usize, + allowed_pov_size, ) .await else { diff --git a/cumulus/client/consensus/common/src/lib.rs b/cumulus/client/consensus/common/src/lib.rs index e12750dcc553..6766c2409c38 100644 --- a/cumulus/client/consensus/common/src/lib.rs +++ b/cumulus/client/consensus/common/src/lib.rs @@ -185,7 +185,7 @@ where } async fn import_block( - &mut self, + &self, mut params: sc_consensus::BlockImportParams, ) -> Result { // Blocks are stored within the backend by using POST hash. diff --git a/cumulus/client/consensus/common/src/parachain_consensus.rs b/cumulus/client/consensus/common/src/parachain_consensus.rs index 944917673b11..861354ed63c3 100644 --- a/cumulus/client/consensus/common/src/parachain_consensus.rs +++ b/cumulus/client/consensus/common/src/parachain_consensus.rs @@ -433,11 +433,8 @@ async fn handle_new_best_parachain_head( } } -async fn import_block_as_new_best( - hash: Block::Hash, - header: Block::Header, - mut parachain: &P, -) where +async fn import_block_as_new_best(hash: Block::Hash, header: Block::Header, parachain: &P) +where Block: BlockT, P: UsageProvider + Send + Sync + BlockBackend, for<'a> &'a P: BlockImport, diff --git a/cumulus/client/consensus/common/src/tests.rs b/cumulus/client/consensus/common/src/tests.rs index 284fa39ed1e7..06f90330d474 100644 --- a/cumulus/client/consensus/common/src/tests.rs +++ b/cumulus/client/consensus/common/src/tests.rs @@ -321,7 +321,7 @@ fn build_block( } async fn import_block>( - importer: &mut I, + importer: &I, block: Block, origin: BlockOrigin, import_as_best: bool, @@ -568,7 +568,7 @@ fn follow_finalized_does_not_stop_on_unknown_block() { fn follow_new_best_sets_best_after_it_is_imported() { sp_tracing::try_init_simple(); - let mut client = Arc::new(TestClientBuilder::default().build()); + let client = Arc::new(TestClientBuilder::default().build()); let block = build_and_import_block(client.clone(), false); diff --git a/cumulus/client/consensus/proposer/Cargo.toml b/cumulus/client/consensus/proposer/Cargo.toml index ce91d48bf589..bb760ae03f4d 100644 --- a/cumulus/client/consensus/proposer/Cargo.toml +++ b/cumulus/client/consensus/proposer/Cargo.toml @@ -10,7 +10,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" workspace = true [dependencies] -anyhow = { workspace = true } +anyhow = { workspace = true, default-features = true } async-trait = { workspace = true } thiserror = { workspace = true } diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs index 18d121c41d16..cde73c4c5180 100644 --- a/cumulus/client/network/src/tests.rs +++ b/cumulus/client/network/src/tests.rs @@ -612,7 +612,7 @@ fn relay_parent_not_imported_when_block_announce_is_processed() { block_on(async move { let (mut validator, api) = make_validator_and_api(); - let mut client = api.relay_client.clone(); + let client = api.relay_client.clone(); let block = client.init_polkadot_block_builder().build().expect("Build new block").block; let (signal, header) = make_gossip_message_and_header(api, block.hash(), 0).await; diff --git a/cumulus/client/pov-recovery/Cargo.toml b/cumulus/client/pov-recovery/Cargo.toml index a95b24bc2933..3127dd26fcaa 100644 --- a/cumulus/client/pov-recovery/Cargo.toml +++ b/cumulus/client/pov-recovery/Cargo.toml @@ -2,7 +2,7 @@ name = "cumulus-client-pov-recovery" version = "0.7.0" authors.workspace = true -description = "Cumulus-specific networking protocol" +description = "Parachain PoV recovery" edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 38ba84748c1e..c796dc5f7c38 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -423,7 +423,7 @@ mod tests { #[test] fn returns_directly_for_available_block() { - let (mut client, block, relay_chain_interface) = build_client_backend_and_block(); + let (client, block, relay_chain_interface) = build_client_backend_and_block(); let hash = block.hash(); block_on(client.import(BlockOrigin::Own, block)).expect("Imports the block"); @@ -439,7 +439,7 @@ mod tests { #[test] fn resolve_after_block_import_notification_was_received() { - let (mut client, block, relay_chain_interface) = build_client_backend_and_block(); + let (client, block, relay_chain_interface) = build_client_backend_and_block(); let hash = block.hash(); block_on(async move { @@ -468,7 +468,7 @@ mod tests { #[test] fn do_not_resolve_after_different_block_import_notification_was_received() { - let (mut client, block, relay_chain_interface) = build_client_backend_and_block(); + let (client, block, relay_chain_interface) = build_client_backend_and_block(); let hash = block.hash(); let ext = construct_transfer_extrinsic( diff --git a/cumulus/client/service/src/lib.rs b/cumulus/client/service/src/lib.rs index 9b5f0bec5387..7f656aabca7a 100644 --- a/cumulus/client/service/src/lib.rs +++ b/cumulus/client/service/src/lib.rs @@ -28,10 +28,7 @@ use cumulus_relay_chain_interface::{RelayChainInterface, RelayChainResult}; use cumulus_relay_chain_minimal_node::{ build_minimal_relay_chain_node_light_client, build_minimal_relay_chain_node_with_rpc, }; -use futures::{ - channel::{mpsc, oneshot}, - FutureExt, StreamExt, -}; +use futures::{channel::mpsc, StreamExt}; use polkadot_primitives::{CollatorPair, OccupiedCoreAssumption}; use sc_client_api::{ Backend as BackendT, BlockBackend, BlockchainEvents, Finalizer, ProofProvider, UsageProvider, @@ -43,7 +40,7 @@ use sc_consensus::{ use sc_network::{config::SyncMode, service::traits::NetworkService, NetworkBackend}; use sc_network_sync::SyncingService; use sc_network_transactions::TransactionsHandlerController; -use sc_service::{Configuration, NetworkStarter, SpawnTaskHandle, TaskManager, WarpSyncParams}; +use sc_service::{Configuration, NetworkStarter, SpawnTaskHandle, TaskManager, WarpSyncConfig}; use sc_telemetry::{log, TelemetryWorkerHandle}; use sc_utils::mpsc::TracingUnboundedSender; use sp_api::ProvideRuntimeApi; @@ -467,12 +464,19 @@ where { let warp_sync_params = match parachain_config.network.sync_mode { SyncMode::Warp => { - let target_block = warp_sync_get::( - para_id, - relay_chain_interface.clone(), - spawn_handle.clone(), - ); - Some(WarpSyncParams::WaitForTarget(target_block)) + log::debug!(target: LOG_TARGET_SYNC, "waiting for announce block..."); + + let target_block = + wait_for_finalized_para_head::(para_id, relay_chain_interface.clone()) + .await + .inspect_err(|e| { + log::error!( + target: LOG_TARGET_SYNC, + "Unable to determine parachain target block {:?}", + e + ); + })?; + Some(WarpSyncConfig::WithTarget(target_block)) }, _ => None, }; @@ -500,67 +504,37 @@ where spawn_handle, import_queue, block_announce_validator_builder: Some(Box::new(move |_| block_announce_validator)), - warp_sync_params, + warp_sync_config: warp_sync_params, block_relay: None, metrics, }) } -/// Creates a new background task to wait for the relay chain to sync up and retrieve the parachain -/// header -fn warp_sync_get( - para_id: ParaId, - relay_chain_interface: RCInterface, - spawner: SpawnTaskHandle, -) -> oneshot::Receiver<::Header> -where - B: BlockT + 'static, - RCInterface: RelayChainInterface + 'static, -{ - let (sender, receiver) = oneshot::channel::(); - spawner.spawn( - "cumulus-parachain-wait-for-target-block", - None, - async move { - log::debug!( - target: LOG_TARGET_SYNC, - "waiting for announce block in a background task...", - ); - - let _ = wait_for_finalized_para_head::(sender, para_id, relay_chain_interface) - .await - .map_err(|e| { - log::error!( - target: LOG_TARGET_SYNC, - "Unable to determine parachain target block {:?}", - e - ) - }); - } - .boxed(), - ); - - receiver -} - /// 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_finalized_para_head( - sender: oneshot::Sender<::Header>, para_id: ParaId, relay_chain_interface: RCInterface, -) -> Result<(), Box> +) -> sc_service::error::Result<::Header> where B: BlockT + 'static, RCInterface: RelayChainInterface + Send + 'static, { - let mut imported_blocks = relay_chain_interface.import_notification_stream().await?.fuse(); - while imported_blocks.next().await.is_some() { - let is_syncing = relay_chain_interface.is_major_syncing().await.map_err(|e| { - Box::::from(format!( - "Unable to determine sync status. {e}" + let mut imported_blocks = relay_chain_interface + .import_notification_stream() + .await + .map_err(|error| { + sc_service::Error::Other(format!( + "Relay chain import notification stream error when waiting for parachain head: \ + {error}" )) - })?; + })? + .fuse(); + while imported_blocks.next().await.is_some() { + let is_syncing = relay_chain_interface + .is_major_syncing() + .await + .map_err(|e| format!("Unable to determine sync status: {e}"))?; if !is_syncing { let relay_chain_best_hash = relay_chain_interface @@ -586,8 +560,7 @@ where finalized_header.number(), finalized_header.hash() ); - let _ = sender.send(finalized_header); - return Ok(()) + return Ok(finalized_header) } } diff --git a/cumulus/pallets/aura-ext/src/lib.rs b/cumulus/pallets/aura-ext/src/lib.rs index 4c9e61458a87..dc854eb82018 100644 --- a/cumulus/pallets/aura-ext/src/lib.rs +++ b/cumulus/pallets/aura-ext/src/lib.rs @@ -16,7 +16,7 @@ //! Cumulus extension pallet for AuRa //! -//! This pallets extends the Substrate AuRa pallet to make it compatible with parachains. It +//! This pallet extends the Substrate AuRa pallet to make it compatible with parachains. It //! provides the [`Pallet`], the [`Config`] and the [`GenesisConfig`]. //! //! It is also required that the parachain runtime uses the provided [`BlockExecutor`] to properly diff --git a/cumulus/pallets/collator-selection/src/lib.rs b/cumulus/pallets/collator-selection/src/lib.rs index 17dc1a552c2d..9d7e62af3c68 100644 --- a/cumulus/pallets/collator-selection/src/lib.rs +++ b/cumulus/pallets/collator-selection/src/lib.rs @@ -972,7 +972,7 @@ pub mod pallet { let result = Self::assemble_collators(); frame_system::Pallet::::register_extra_weight_unchecked( - T::WeightInfo::new_session(candidates_len_before, removed), + T::WeightInfo::new_session(removed, candidates_len_before), DispatchClass::Mandatory, ); Some(result) diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 9e0a68d09a14..bf136dc0644c 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -53,9 +53,6 @@ use polkadot_runtime_parachains::FeeTracker; use scale_info::TypeInfo; use sp_runtime::{ traits::{Block as BlockT, BlockNumberProvider, Hash}, - transaction_validity::{ - InvalidTransaction, TransactionSource, TransactionValidity, ValidTransaction, - }, BoundedSlice, FixedU128, RuntimeDebug, Saturating, }; use xcm::{latest::XcmHash, VersionedLocation, VersionedXcm}; @@ -193,7 +190,7 @@ pub mod ump_constants { pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::{pallet_prelude::*, WeightInfo as SystemWeightInfo}; + use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::storage_version(migration::STORAGE_VERSION)] @@ -653,52 +650,8 @@ pub mod pallet { Ok(()) } - /// Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be supplied - /// later. - /// - /// The `check_version` parameter sets a boolean flag for whether or not the runtime's spec - /// version and name should be verified on upgrade. Since the authorization only has a hash, - /// it cannot actually perform the verification. - /// - /// This call requires Root origin. - #[pallet::call_index(2)] - #[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)?; - frame_system::Pallet::::do_authorize_upgrade(code_hash, check_version); - Ok(()) - } - - /// Provide the preimage (runtime binary) `code` for an upgrade that has been authorized. - /// - /// If the authorization required a version check, this call will ensure the spec name - /// remains unchanged and that the spec version has increased. - /// - /// Note that this function will not apply the new `code`, but only attempt to schedule the - /// upgrade with the Relay Chain. - /// - /// All origins are allowed. - #[pallet::call_index(3)] - #[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 { - let post = frame_system::Pallet::::do_apply_authorize_upgrade(code)?; - Ok(post) - } + // WARNING: call indices 2 and 3 were used in a former version of this pallet. Using them + // again will require to bump the transaction version of runtimes using this pallet. } #[pallet::event] @@ -951,30 +904,6 @@ pub mod pallet { sp_io::storage::set(b":c", &[]); } } - - #[pallet::validate_unsigned] - impl sp_runtime::traits::ValidateUnsigned for Pallet { - type Call = Call; - - fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - if let Call::enact_authorized_upgrade { ref code } = call { - if let Ok(hash) = frame_system::Pallet::::validate_authorized_upgrade(&code[..]) - { - return Ok(ValidTransaction { - priority: 100, - requires: Vec::new(), - provides: vec![hash.as_ref().to_vec()], - longevity: TransactionLongevity::max_value(), - propagate: true, - }) - } - } - if let Call::set_validation_data { .. } = call { - return Ok(Default::default()) - } - Err(InvalidTransaction::Call.into()) - } - } } impl Pallet { diff --git a/cumulus/pallets/parachain-system/src/mock.rs b/cumulus/pallets/parachain-system/src/mock.rs index 7bea72224b8b..b4d118aadf04 100644 --- a/cumulus/pallets/parachain-system/src/mock.rs +++ b/cumulus/pallets/parachain-system/src/mock.rs @@ -49,9 +49,9 @@ 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}, + System: frame_system, + ParachainSystem: parachain_system, + MessageQueue: pallet_message_queue, } ); diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs index 51c6e83c1131..548231966e42 100755 --- a/cumulus/pallets/parachain-system/src/tests.rs +++ b/cumulus/pallets/parachain-system/src/tests.rs @@ -1127,10 +1127,8 @@ fn upgrade_version_checks_should_work() { let new_code = vec![1, 2, 3, 4]; let new_code_hash = H256(sp_crypto_hashing::blake2_256(&new_code)); - #[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); + let _authorize = System::authorize_upgrade(RawOrigin::Root.into(), new_code_hash); + let res = System::apply_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/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index 1527492f5784..a44d17507810 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -312,7 +312,7 @@ fn validation_params_and_memory_optimized_validation_params_encode_and_decode() fn validate_block_works_with_child_tries() { sp_tracing::try_init_simple(); - let (mut client, parent_head) = create_test_client(); + let (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 {})], diff --git a/cumulus/pallets/xcmp-queue/src/mock.rs b/cumulus/pallets/xcmp-queue/src/mock.rs index 7fb96de7a4ea..348939de1f14 100644 --- a/cumulus/pallets/xcmp-queue/src/mock.rs +++ b/cumulus/pallets/xcmp-queue/src/mock.rs @@ -45,7 +45,7 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, ParachainSystem: cumulus_pallet_parachain_system::{ - Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned, + Pallet, Call, Config, Storage, Inherent, Event, }, XcmpQueue: xcmp_queue::{Pallet, Call, Storage, Event}, } diff --git a/cumulus/parachains/chain-specs/coretime-polkadot.json b/cumulus/parachains/chain-specs/coretime-polkadot.json new file mode 100644 index 000000000000..806231db7646 --- /dev/null +++ b/cumulus/parachains/chain-specs/coretime-polkadot.json @@ -0,0 +1,94 @@ +{ + "name": "Polkadot Coretime", + "id": "coretime-polkadot", + "chainType": "Live", + "bootNodes": [ + "/dns/polkadot-coretime-connect-a-0.polkadot.io/tcp/30334/p2p/12D3KooWKjnixAHbKMsPTJwGx8SrBeGEJLHA8KmKcEDYMp3YmWgR", + "/dns/polkadot-coretime-connect-a-1.polkadot.io/tcp/30334/p2p/12D3KooWQ7B7p4DFv1jWqaKfhrZBcMmi5g8bWFnmskguLaGEmT6n", + "/dns/polkadot-coretime-connect-a-0.polkadot.io/tcp/443/wss/p2p/12D3KooWKjnixAHbKMsPTJwGx8SrBeGEJLHA8KmKcEDYMp3YmWgR", + "/dns/polkadot-coretime-connect-a-1.polkadot.io/tcp/443/wss/p2p/12D3KooWQ7B7p4DFv1jWqaKfhrZBcMmi5g8bWFnmskguLaGEmT6n", + "/dns4/coretime-polkadot.boot.stake.plus/tcp/30332/wss/p2p/12D3KooWFJ2yBTKFKYwgKUjfY3F7XfaxHV8hY6fbJu5oMkpP7wZ9", + "/dns4/coretime-polkadot.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWCy5pToLafcQzPHn5kadxAftmF6Eh8ZJGPXhSeXSUDfjv", + "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9", + "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/30361/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": { + "ss58Format": 0, + "tokenDecimals": 10, + "tokenSymbol": "DOT" + }, + "relay_chain": "polkadot", + "para_id": 1005, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xed030000", + "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", + "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0200", + "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x1c00f379b621bd73c45c7d155d2a1fe6a04649e3ece7c7e03b70b3a6242bc7c127049bec59fb5fe6adea4578250578e89dd7e51ad88c7c92493d6f451c6680925c20d8c795eef2620fba2bde74dbc36461c07998ebf600ed265b746c1e05c706064c0aa0240b2d7485675e52cdb283a87973652f6acb42c830a5a5faa80f7a707e6610a5024c2a5db3d02056d4344d120ec7be283100d71a6715f09275167e4f38689e1a66fa33b75f66415021aacc4fa23f49306a3c21407748b8b2d39b4abf6380b6f570f356fef7b891afa2e1c30fca89bc7a2cddd545fd8a173106fce3a11f", + "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x00a0acb9030000000000000000000000", + "0x1809d78346727a0ef58c0fa03bafa3234e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x000000008200e17579c4", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9381ca18820b278a00faeab03d52440be00f379b621bd73c45c7d155d2a1fe6a04649e3ece7c7e03b70b3a6242bc7c127": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da944098499b5de4f5677804569aeadeb5e4c0aa0240b2d7485675e52cdb283a87973652f6acb42c830a5a5faa80f7a707e": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94c4f030742ff8899655335ad1e54ca6a6610a5024c2a5db3d02056d4344d120ec7be283100d71a6715f09275167e4f38": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94d2dbb242ff048066ba7c14ef24678cf20d8c795eef2620fba2bde74dbc36461c07998ebf600ed265b746c1e05c70606": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da956f8d5eba063b801102d640867bbb26f689e1a66fa33b75f66415021aacc4fa23f49306a3c21407748b8b2d39b4abf63": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da96684268ab336f4623df9eab07c482056049bec59fb5fe6adea4578250578e89dd7e51ad88c7c92493d6f451c6680925c": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9784e05d1b3afe91a143e23fe5983f63080b6f570f356fef7b891afa2e1c30fca89bc7a2cddd545fd8a173106fce3a11f": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da994eb9f87cb79eefab94a8a6ffcb94bfe6d6f646c70792f62726f6b650000000000000000000000000000000000000000": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0xe2373d0044636f726574696d652d706f6c6b61646f74", + "0x3a63": "0x", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058b40805be8e467714531068689474e824c10a8cc53774726667883ec05d802d901643e234ed8170f89bac708c7155821d498f97282e09dca01e2231f2d04dc75ed1ff328c8cb9dcb66ec66356fc03a1ff37d21a2184104208d95b4a1924164e147013536e28ea9568e6424433bf39eaf5749ad799e3bcdebcc62baa8b99b5bb1045972edcdc7cca0dbd79253a5d88e8f499df68e0ac0d450d80e0c5cccccce93399579a75cf70040f84703a9d4e9c877dc8db66a56206344470c4ebf6d36be937afe0675e636cb07b17ba3e84ddf3bc4fb941bd77b35289620b6264b15d8868f36695b98e5d08fcd0750cc3b06bb3b292e88214cc28954adb4bb362b39aaeebbaae67b312f1043020c8b26d9bf66dd66bd68e9b758823c8308533689aa6bdc6bce6953bf6aa8136b36623406307660c4066d66b8a2aca0004ed42441ac9346bb340081ff881f681ddacdc0408706084a669da6bf6ee35e632afdc6d5eb52360e9436091598b385201129cc82e44943de63b940e5e68fb50e920087eca0d0a7e00b366a08b177c8004ee4244dcb3c7cc0ace5a647be942f143db4ba552e9a4598950e20c56a2c8b24fb9a1d92b51cc858862ce3d9bb534eb005e8784e2b70b19f950fcf66dfb941b74fb37eb65842d5ce0058efb941bcabd12811722021f73eeb574d22bf8ef35e6da2b37eb362bf74a348593a1f1536ed0f821a17823171a8a3772a01f993576d1842a4d88898979252a5d88a874f031b3c6f9b3c3ac54be50420e5e0082e02b51bc10517ce9e0ac46e6008accaa811970600c4628954aaf443ac49766059a3b746a3f805933249c80841234c64fb9a1f195687521221dbe7a7ca5df5e578f79d5e1e06b9c5500b300b3ca26a6808213b412d10be970a2d5f4a1537b3a1df03325403425a33a9c68ca0cd5e1536ea80eb3874eedb9979edd74efa8db78ce737cc77b5ec3879cc873780000000000e490430e38e0800311229f52a2446eb8e186214386d860c3a794a80daf3caf397676765e6978d5c183078fd71faf3b7af4e8f11ae435e5c3c7a7d8501faf425e7b7878785e55af4068a0e1536c280daf3e2a954a88102141827c4a8906f9f1e3534af4c76bcfab4e8e1c395e81bceee8d0f1293654c76b0daf3c76ecf8141bbae3d5861a6af89412ade1d5c7eb75743ec586eabcf648a53ec586a65e6b000204484f4f4f2af529259a7add79b51186e12b8fd71b737e8a0d9daf3d5e7370e0f8141b8ae3c78f4fb1a13f68a081061e1e1e1f3e7cf4e8f12925da83070f1e3b3b3b3a3a9f52a23aaf375e4f373737af39af332814eaf5af34383838af395e6bd8b0f12936d4c6ab8ed7f0c68d1baf3b5e674ecea7d8d09cd7d48e1d3b74e8d09123c7a794688eff534af4afe1ab773a9d5ee7ebcdcccca7d8d099571caf281a9a4fb1a134af3a38707c4a89e278b5f17a79dea7d850ef15a7468d4fb1a1355e71cc39c330ac51e3534ab4c6ebcd6b272323f38a7ab531993ec5869a5e715e6b30ec536c28f67f8a0d7d4e4ece8d1b376cd8b08183f329258a8342a16e6e6e3cef534ad47bb5798d0141f0b5e6b54422915eafd74fd33ec5866aafa7d7aceb3ec58676af33af323636363e685e4d35359f62436b82d4a0a1a1999999399d3ea5444fd7f529257abdcaa4b6989818cdd4037aa552097c259dbeefabf1662592c251ec44533e8a7d4a8962af9d0eb7dbb6490d27cb3ec58666efd4be62433ab5279212e975a229247a7d8a0dbde6d090941a1a2f45a3f143451a158a6730a2f1453534ce1c3ab5423a2869559c555e49a756eefbe361b3a0d7acf172a28e3489534807aae488fec4fd80ba6584034006197ce101767c8c8f315a41638c310e75cb03dc219ce009fa56a55a5525d8aabece91952b91369fb6f67ebf47888e2e185594d8efd71d63e4d8dcddc789ccd7284f1412be3cc7b99ddaca34320fa15bda7133771cf6d1d7de2e11ad5595697c10301a8953ebdae350526f890c31182a94512931a52e7a1c46fa49c13c2f548a475a22436c448574a0fcfe7e3c4823fe8e804694ef19517e77e956a79eee00cf5f91218c8c8c8ca0fcae3dd92afe7e5bd55aa7e27f9a43af53f1a534c07aa2af1a8def4ead929d5aa6f138ade2d7b44a86527228514ba37d8df64421e9cbf7e52c62ca73bf23fc9eb5cf8fcd4c846ea9d6aa7a138302b0cfaffb2e863580fdf8fd7e3cf49bc4a9756a7fbac53c80eea23b861457e809a75b1ea07c4fabaa36a455157cabae076995a4d76b5ab5472230835ec76955d38b568fd61e7a7ddba757d5a3b5865e075bd5bf7e6a55fcf5eb5eabf8d757e79a4df6882e8e163357e9f8ab537bc2a51d7b0c243bc5f2ff7890cfe6d675ef598b22e5733fb5ca7b7c4dab344fc3b059b173c734edf160abb0c7775ac8b4c3c2edd476ea3b7f469aca4e7db37ee7ae854bbfb0e35e8e24be162ef5c2a55898aaa1fc5d9e781c90f2acd9afc77791b90b9772a1a6037f0b8b2eca7ca2fd6ebbf6f633829d6798859569f6f82d66e2578fc677f2db68f23aa5d3a978eddba9fd3ba549baffe99f98854b37dc4ef56558990a71983061c2d0daa27b2cac4591ee796a9deaf307b4b3bfdf35c14ef57fda3bb56a697ca438ad3a8df7b144228ddf56f1ab0e8d5f9dd8f1b5836e752ab89daa1aed578deeb77db677fafb6e7780f1ddee80ddfe00f7a84a15ba471ca8426b89f66da07bb4012eb43f201a407e7f5b15df97adcaded75ab5b4dff50794cd05e36f7cc8c204cbb394bf02b4687cc75e0c976621d38ebff82e86452c04243b257f04a4f1fcedb0347e3fa0ed54f5285fb3b6e8a5c52cac9256f9a248b1ef0774fda7bb6e3f23d8ac9262d79e85f5347b0c6bad350611a50e41b36fa7b2ff442d1462d1f82cac3854be630f0b97c670e97e3c481affd33f7d854291c6cb703bc5df0f284ed929fe4faf4ecf26205d1cad8ce311dd2a00ac3894afd32a3e1f877f7eaa557bb48123caf7c1382ce56ff4d9ded9f337fa6cef548deeab46f91b7db6772a48f715a4fcd5e1d9a4872e8e167393ae01209fe9b690fd7edd2a00ac1eed7badda31a4b842fb60abf628044dd0fea955fb7e4dabf87d1cc681697f757a36395d1c2de60074cb03dc232392a055a37bb0553b861457e85e6bd516e10c69d0bdd72aa6fb6d9f5dd513ddafcece26a78ba3c5ac43b73ec00a527eaa55d5a37c7e4dab6a8af2f93e5ac5940fb66a8f36b085f24fad6acadff6e155c5a17caf557bfeeaf06cd24317478b3901dda6c0fd662632b0108e500617f381180fc424110302d317d3144c523069c1e405d3174c603099610a83490ca6334c6598ac600263c2820906a618805e00c1805d00b9006e01d4029805100be015c032402b805500a9007e01a7004601f40242017c02e8049009e0124025805d4030c02e4022804500bd008f401a802f00a500a3003950c2822904252494c62889514a4249097104b189c8445c222a21c120bf20bd20af20cb9056c854f0edc0fb80678477a5234357061212242c4860484f208d411283940512164a2328355162a2a444294b498a52074a564a1c286da064440a433a43898a120cb2116062c40f805f605cba33b82cdc141c0c382a382da62b60570057909d202b41f480d701ef08cf8a57858b02e704ce0bc90892d117e63bc367862f8daf0c1f1adf19df183e33be2f7c5df8b4f095f155e1a3c2f7e59bc227858f8c8f095f123e247c637c627c617c5c3e30be2ebe217c41f8b4f88e3e2cbe1f7c3e284df11df15df1e5e0bbc167056605d007de06b21490c440fa02c90b243390d22095814406121aa5284a2e2895a04482af0ae94ae9090f09508bd881d295120a4a554a272819513241c909ae0a8c0bac0adc0c38293a2e7038e06e509904611a830f0438b2831103140009d000563a20a0071e0ea0eae9b620c4062031213075c1c405d3164c593055c14405d3134c64988c60fac2e485a90b93104c5b987a60fa816907a61b987060b2c26403530d4c4f98a83081c0944412300c60124024805c4023804060238a1802b3b01168a574855219252b94aa50a242490a2528949e506a428909a52e19a00286211d7d3af870f075c00be39dc13383978657068f0c1e1ade19de183c317861f0ccf0c0e07dc1f38207c6eb82c7056f0b9e16bc2c7858f0aee09501c513418706f883ce0c100b308b6e091d153a31b8343833381b7035e068c0c9808b017704b701d315a61c987860aac2e403930e38a32dcc7686cd0c5b1adb184c5a9882602282a908262eb6306c666c60d8beb0796103b371c17464c2c2b4840d0b26304c4d30753129c1940413124c6398c43085613ac27685ad8c8d0adb976d0a5b14362f1b14b62c6c676c4dd898b029614bc286846d09db185b97ed099b13363236317046701ce0aa806880677056402c26266c5ad8d0d8ac6072c256854d0aa52fa52980660087008e014c03dc007785f30087049704f7010e045c08382c9c08b62d6c64e0942879e19600c5e098e046c091a0b404ae049c13601940326c626c616c5c4a51d8bed8c0d88cb079b11561eb6223c2c6c536844d08db165b10b62e6c653071310d6103c2a6c576b465b161b1fd60f30138c6c6836d071b0e361d6c576c39d86c104fb055b16dd968b0cd60abc1a6657bc116c5e682ad051b0b3628b6156c2ad864b0c560a36283c136c5966593627b6243c19682cd041b093627b612704d804c804b804a6c37d846b035b131b12db129b161d944b0856003c196c4f6810d89edcad681ed88cdca56058462e3c0b681cd88cd480ba39d413383968656068d0c1a1ada19da1834316861d0ccd0c0a07d41f3820646ebc276028d0bda164018949c5022c3e4014d0b5a16342c68656856d0aaa051c1f401b00a78c47684cd0a6d0a9a1440316851d0bc6850d09ea0394123436b82c6046d095a174d095a1234246863686268616847d0b86846d0c0d0bed0bcd08aa075a11141e3421b8226042d08da161a10342db42cb4230d0bed079a0fb41e683cd076a0e940bb42cb818603ed069a159a0db41a6855685b341a6833d0b46832d062a051a1c1409b42cba249a1bd408b427381d6028d051a14da0a34156829d09ed050a09d403381e68456028d04da08b4263426b4253425342c9a08b4106820d092d03ea079404342bba275403b42b3a255d138a06d40334233cac26467c8cc90a591952123438646764636864c0c591832333230645fc8bc9081c9ba907121db42a6852c0b1916b22b64656456c8aa905121fb924d2193421685cc4b0685ec0999133232b226644cc896104b9075c99490252143423646164676848c4b66840c8cec8bcc8bac0859171911322e32216441c8b6c88090699165911d6558643fc87c90f520db41a683ec8a2c07190eb21b64566455645b321a6433c8b46432c8a8c8609065c95e904591b52063014845e60476042c0ccc081818d81798175811b02e30226c1e8826882158828d114110932062880afc4001100c139040027a6021e8982e7765d83a06112da94092284b82824892a4e308c493a0244b8a9600912409b7fc44080b2429fa21688809104f98244962c3c3e38141f2be58a2200c2c799204e575b14541444143519214055dc0c623c206152d292222624214f424c8c88dc7c53e81c22449d15090942750a0f8f0039402e4f086f0431228167872c48913280228fa21c889132802e8e1096131f0c31033de16fb4488a124440c0191844914fa448804103134001a0f084b1414648124440c054961e2240888d0d36231f0c31037bc2c7678582c51511011d1922451920439a9c092a228413ffcb004033f3880c7fb418fe703274e96fce0c4c91201f42088a20b3c0972c2830d0ab240122845498a82b80093a0293b9e0e96c809932545414024090ab2c00e0f070bc5024f98242162284808212296f470ef061bf4c313284f922c49c2240a0dfae1099403143d1102ca0f3f501b9e154040f92108881caf064b44445093244444440421790285091151900242af8a9dc2240a1d0a6a3204a5013c841e0d36a86809104c9c2c9192244888202110e003104117b8c0121f7a783358a2263f0405f1430c96a8e849d05050104f98dca3e209130ba0bc29f60726444f980c150d119484082751a22c4932140484132642ac9afc00c5c90ac7cbb244371e0a7649121caf048b81244b8a9600b1a428c800266f041960e264099322264f98cc784a6c931fa038a94092a022217e081a2228c6c3b2435004e07921d82750a208910408274c84a84092274b7e48c2240a2d7a1284810b0429008820208470f2c313264f16b0644808bac303c1120531296232042588222196c87849ec13284c8a960c0901449222284e8892308942970c09416776b82c64508c91c509e8b6ba3d3232a2dc8a361826024c041c8533194d269349472772e7f04aac9725b618cbd5b48e3b0cc330896dcbddc532d9d92663b2acf3e425fb9ad998b16ba6b3968cad5c6c33de666e8cbb71601b316c3199652d7baf2b36ef4a6ed6b869b028773dde6ed9516a51ca28b9939c3126637794d815802d46665994b21bcb64dc96b11bebed4d4a2fcb9a59669eecade5caedbd5a7a5df3769671b3646c25d69c71b327bd6cf3322fdb0df3b2de4dee26b965b7e4665e996198a6692c97a564c9383100c830acbd2ddb4cca2e4ace64b66d9994524acebef96158672da5a669dd1d19a3699949afcbe416935dd7c59c75632cb7cdba97e52ec771dc0d77d31c731c37c735c735f372dccd35d7dd1c87711c87450cc3b0b8ad35866198d61ac69dd5d4608be55053d3ac6158776b58478e8d615ad43a4c6b0de3d8bba4dda8f576b6bcddbc196fafc65d7fe0f72d7f4c62de355d5fd738607d35efb5d7455a6c1bc3b0edd2f676b6d862bdbd18d68c3586ed2ecf485ec6969b799b33ce564ac66878b398e5ee6e5ead35c91863dddd758c75cb53e3586ed981b26327b9376652c6ccbddb2dbb9b4863cdccdb3277866a790373044b33bbccccdd729bbb872cb36c1f8c5b4a660c5be665d912c3b65b8569cd9259322f7393b26e6ec9429825f7aeec65ac7136eb1966ce5a0bb2dd9d79ddbd4d6ad2c7ddbb4c026d905a2f5fabf52e6fb3a6691a336bdcdcecedeed7bccd1d57f62eb8bbcc58770d40b85bdbd9bc2137694d2b9b9b7bbbbb9731eed6b8a75b62dbccdc65bdcddbadc9dde5eeeeeee6edd696f43573dfd8b0bb4b18c61896358661d933eeb0ffc062e4c5b088e25d6c8b281a78987b31295993925993f2da24fbc8616e6e0c6b8c2396c55eacaf5d13194c69449a6d2286a06020080318b8402e00c549920b3c6172810b40716253fa698820209e3049f284c992207eb08005a04c1b9c3861d2a4e7c68e1e291ea91a3e6aecd8e191ea2901e18449103f24297222c4120b0401b133b4a488684789c8e6e6a6c90f4b2ae0c40994211e3b96040dddd8043549523444d0931b4117b091212a2262324410ce55f4c312a2274b868a921431c10006a0a46a6c0d4c2c103414659e86960439f901555a204f82a2445982a3a368c950d093a00be4a009ca0013a22416800265284a92a00b2c1982524464430d434436160811166032142425c90d408408a90188201c1c104144444143513000e58724434152a0fc103494640911103f042501d24425a486283f0411258152148588a11f7c726c0d4550a00c2501226828088821284954426a08623254c46388a020704e1628122249d1132643516c16080698143d116228c90f444c303083a86ca884d450435093254e2a7099213500493204942945492ef0844910444b82b8b141414316c091f224286a0b24c913284c888682a424c140922128454b7e80e2c4c99224454304dd90f22428a63c347a5bad8e6b492a545aad8b4a0b53b254a850a14285a92c152a58cc946cabd5a2125b5953914a5a54b845a5d52d956c8b0a152aad16a6645b54a82c95562b462a9b9216b75a2d564285a950a1c2545aad64a9b0921653e196a664a950a142a57545254b854a2b69f12a592a545a2dd9ca94b4980a634a5adce2d6a5a4c5db924a5adc6a25db6ab5a2926db1121d6217de5dc600d1920ddf100320f6eb1800e5e3aba4d965ab9666ff96c8d22cbbb744b2af4ea60525b8501cba474a58417b5a2d2fe52f2a345e5fa89c42314c18d9c4c97cb26060a971f2b1fd785830b0506c66e9a8d07a85d79305030bbd5e3340b10b7b02802a47f49a598aa0a2cd1d82c6f3bb9661fc7e10c8520495fdb2dabe598c6c80e0054d403194030a1a7061541465bfd02c463f94a08591140054391aa266805e1bd82fab6d1aed97153f3e8b11cd13aae0c0158c8c563b8ba0a24d23a992b346a1f1a6283c418a335431325aed6700197a3546458785f6f73b68dd0d97062d91a11e9a45beb55578fcca79cd2a47f0acae5f915925d1aa13eddfc43001562774df43eb89d6a5420f55ae9093e8543f0a583909daaf4b05da67c98371270c4fe4600539d8e2c5c868753dbe82b4573134923108810b36d0021facaa1cc1b38abf22b392558ee815f62b0158c55fe19500ac583dc49d7ee442fbd7595a7cc07e4dd64fdce96393a5c587eb71b2aed953f7e828be2e971543078890650b4ca0220a61c49000dc556b045ef840052ac8a24b0dc0b0da957ceda1fdfd7cb891d12a26d1aa1db42fc3186edca91a5dc642170715d9e2965aa294e162a6fe755ecc945d57765d97c4e9d4f5fe98ee11161afc34d04693d7d725901087a1f2d8c4e9d4256594dd46d86b4be4fabebb2247ed117bc41eb3c76b587cc41e63ec3019c82b5c0c3b6347c9336d8a9231c07e858c4dd9a9d5ae90a90cf7a882343e4a1ae34f1187c66b5e04de83ee11087e4097d4d100f23113bf084799e97e401d3776330f1df6aee30f887b77b94be4fbf5ee0b5bb476d9d724fbbe0b6b768e2649f708cb13542ecfd51bf6bd30fb1632ed6fa8dd0bb56f46f8fb9fd666dd6bef36acddf75df4b4b0bff336d1dea47b9f0bbbb8bd3380f7ae3bef0a11b5e8f73aa50c6608438996cb144d78807eb3765d77795ad8db6587795ad8dfcf08126fd621e8f66e67fdf515e1cefd276eb8943fa06e36e18a70dfc97dbf231add6613edfdaa21e96bfc6de152196eab6379846ebf3a759708f6fdd56161904e5d58a4a03d9dbabe5fc5a1d7e37e41781e4efb70bf9ec312f9f268bfeea35560a7b4edd7b9707b47bb4efb6828af7d4ebda3fd3a962ab406a1d7514b84e9d56d214ea7b447ba47585cc0613093bcf678ba472200436bf8d38eca1e6953543685380ccdbe4758a0a0eb859722d053c8dbc4e9949685d7f2ecf79358c83466a155a35d341267effa88ef3886952976f6a9c2bd8a96f8edfbaedbc23deaeeda8bde16ae17a3eb2b80fc7888349e0b77d5ddb54de309d01201d578e8593007f8a1dae387a44b347bc717a952c1c25e7f3bfc78f1dbe1876aefcfc8132a448566dfef885773bae8f54780d60aa8f62e7af22bd282820a29a1dabb6612a952818262eba3cb3ceccb348fd3b67057dde671e1aeb67057fd1de963b372f34652a5427779ea1e31018662e742700b6f64646464b43a221f67fc8486a8b201238ce810366bec8f07f9489bc45fdf6f87a5d914a28616d5506ceef2d4788e315c7a854df8324421a944d0fe522addc2fa3a43b3c200af5efd41595575283f3b020116aea1062075e128892f1b6e0bd1c12e00dc2325a2a06c44e3bbd2bedff9b0b7a103c0266c04bdde74bf08630565caef0f091b51ec1b4a914740ba8fdf2552e55147382aa75089ee5709b467caf786a15d7b1b3600e433d5b2e74fe84220bf7571c3a05302761d0de0fe24d341009460b798c9eb94ecd4e93bf4c0456263c8cb6bda469fed9d0ad27a8abd5b1ee0f59a9f56550d54d22aec396077c01314bb045a152916b70bc586506cbda0d8e57f3a0465500cc3300cfbea60b3c982692d7447b2cb77320c33f9eca7a5b6fda765c8543bf6edb5b5615a97855298ca500ad3ec1dcb307bd7fde5914d320364efe4857064b75df4649885cf5a9b5552edd8b330b69c553b3ffb4fcc8458b4b3ce5ad3a6d655764a7b7f45987a97a1d4e67e40deb3b06697025227d2c8085ab50b65691811864ad1a87619d6eff157d884fbf5ae935393f2425a950d184187a46074bb948c12b5e83665bc56d49474d214e23428f785454db97ba150a4dc441de1237a5d7e40d76328ff13b95fa194a6f15d37af8fad08d3eb5c28b3599966dfc2ca5b58316fdbb66f53fb4fc4b0482b518bf2e5bd59449ecf854b67cd6613398558b446cae72320edff741796f61451f27cf90e51f2fdec9d0cf91d7f3bfc64e7ae3dfe8af4b3cb23fd2c13e2304345de7e47a21c5142b56bfb26f2d93b2dac92cac76d0e092d1823235a1443b5c758e4518d87285c3824a403ede5912fc228f7da547e0b87b817614892d0ed3fad854239743b170ae9407979b65088c3d0ed5a5899ca6deefa90fd15b92ecf1f10366b8b6297a1a685fc2c5cbabfc2daf23fbd1fd050f4d86b4257bade5de152fe7e15d92d565de948f7ee1d7bdcd73be77d3bc785a74eedb7100cf7fcae0ba530e5500ad3aeebde4912cf9f6e827d035bd53dfb9ebf12d36e3fa0c54c60a7b8f750efdca2cc6165da04fbf625c2340bbf3c7bb0553d74df8560a756a31912ecdb9425eed7e5bdf3f700ed9c374fdba9c5ceefbafdb6595b74bb1772e8859503dab85925e5de9dc3d895a9f74a80161674d787372bf7c8854bb970296fdb429266ffe99feeb0b05ee76b218aa710d36ce26c6164447bf0d02cc4e9d41e0b2bd3ab95f175854ecad0016013f91db4d225527be8d5ba8e70f4baae57392b152a5f3703ec14e835b368df50fb1532e08712b208b3dacbb0b2a86ce264bfd0ecd82b157a7d1787dd1f75bd908215b48869369fec172a14c384a1d8cc5204950503cb4a9b46fbcaa2d7acf275a3f13f51863b68c79e16eef783c0828165b53b28156e5ddd61a45a15822ebd8415d1678b3085078cc4d88285175a582d1653d8820bbe700215472bf00ba0d7297e46f78b1294419982668015a4fc2a2f30360086b749d7add78a37dbddd29552ca90e97ef111e89aa0fb0dbf743c067f04408100927c61f99f96539b3af1025d11a69a00c04e953aa6e07e04685129678b7900b5a606275e468fdec8973162dcedb7c7e0bebf02b468ed37d92944857e40a0141dbfe3b008534d88e9326d31e340e20bdd2324b6a0b544e57bd0058303576849b62abe6390cfa0fc0ad0a22dcaef3006055dfc80aef6b655d86352707dcf20ff0a43698429d62a5411a6d8b1a951fe15325d1d399b8067e804c0df37e93387312960fee92dd26d11708f3e7005bde81e79a00b5d53b7d4a35d8941ecfd9ef1f2c2faf13fbdad4215e9c747ec7cd9aa7abd49bc3c865d6b556373418ffef44fb7981907dd52b02b6dabf6080933e85eb6aa1fbff75a250bd09297ef4a3f607c5f880a95424ce5dc4e710598b8c2bfde6d00c03dba72855e6f19d1aeb43a958de8f68d3e33ef6bd1e7f43e187d64deaf893e31ef3ffa98de4f451ff0fd9ee8537a7f48f421bddfed67e4faf718ce6be17599d04acdadc8dce6a5d0ca8e5be9f1d249a115d4adcc845670260bc7716e65e63a3f8556e2ad9c7ee331a1951e9365e33d6e25e63c0e86566a704c968ee3e078cd5938af99ac9b9db3fe1cb7021e09d641d51c7556ea289db350a7e1a6d04a8f1ebf11dec664ed4c560daff1798f9cf738abe63d262bdcb9cd64dd787896cd7bdc4a9cacf81d13891e1b3e1e274b67b2748e03c77be84c568fe388c771968de398ac9b9be3384be73ddfb1b3f31b938563b2789ee361f81b67e1f88dc9da11722067ed1cc864f9d031593a9e9343c3754c968fe700392be740268b861e61781aceea1144c77bfe23c744a2e7e6e63c39729c75e33926eb664890e823c654dca9e9a18303651386378e0327b573a363e306c7a62627e2c49a540f1d14ea46b8f31e386e727470d8f4b07163c3c6070e2a95f358b373836373a34688eac60e0e540cdf23654388901e38efb92aea5c9523448810214370e8e4f4dce73e3754aab35257a96a50a9bfe7435043767c7cceb2711f213e373670543aaa3864c859361f32e4c68e1021393df7b9a9b90f0e214284a8ceb25109d1c9410d49c50ff90d67d9f80d3e417cceba711f06e28343a5bad979cf85842772567822ac1a7216ea43264b85f31f67fd370001c2731f67d99ca7e73e349ce73c7ef4f80df7394be73e1c44c864d960c3834c96902067d97890c9b221c8814c960d40ee73a387c764d5c0e3ac9af398ac1d3bcee32c9bf75cc5c39323070f1ee7e1392b751e9eb350579dc859f144b8e786b3707e03d7f01f379cc859ff8fb370dcc786c9e2992c9efbf0c133593df7e1e3369c65e33e72dcc7593ae739101e3cfe83061a0ee43c7e9cc75938cee3ac1bdf719fb376eec34254931524c8854c968f90b3722e64b2820879cf6405f98f1f579d15bee7ac1e2732594386bc86c9f231593eae8367b278f0b88fc9e2b90e1f677d3ac0c764f1f0711e67d5f09cd9c3ee60bf01488ed330593c268bc769f86fcc1d720079cf59396e63feec0ef69cb36e9e3359436cdcc7861bdf31593676fc860dbf7156fc8dc9b291739ec9baf1dbf8cfe2796a2a893388ddc1ae9a2c21a90799ac9ec9eab90df1354c16909e68454e96161fe2e7e57bceda99353d93553341f0a7ffa0a1e63c93e5a3741e353c5e73568f99e231593af3a5cb7ce72c1c73889dc90a6794d48ec9c2711da1e938c239595ab2c78767a1a6b63bd89a3ef34f560ee9376c5841dde62c9c396477b0a326eb66f6ec0e7616e93137bdb3097795856c446bc25d6161d5be8f1f12fef5fe8ab4f62ba29dfb4fdb84355e13d23c0bbd6361f74bfb4ffce99fae3184fd3a4dc86aadb460ffc2ca46947bd785312800bd77dfdeb1e7856c44f733c086a1de6c929dbbb066137584cf9dcf4da196114521e167cfe6d2fd16c63001b2115d9acd841b77faa7508b3b7d99108c3bfd98b026eef44de1e34e1f0c5371a75f0a7be24e9f140e893bfdd5c9661336a28b830a4f2beb1e1974d9e9a56fdce1711e7ff4b1e2dd0a127c1bdfbe951adfb8d3e33d48b782047fe38e8ffb784df4b1529aac997ba5cbdccab7d1c74a8dc9024faab9951be7bed7a28f153959d29bac182f274b3ef3cef2262b4e24f8ac789c936e4566b24ea7cf4c96cc4b353e73d6cc649dc29fce9a79f7d269be07a38f956db2b6779f3b4d56ceb35fe79dfd7456ff345918763959dbe559a773b772bd66b262620e4e16a74d96f61adc5935ce4d96e79dc5dde62629df4d16867d9b2c6fb250df2ebdcbb3bacbc9c24c6761d7f17996fc9cac9898cb4c96ccb3ec3893158373994ce6f3acec73b270340fe72c2d9c48a44c9365ba8ccc6d4c323239ffb61d07b56ddca6d59039cd903c99b5b6cd789193c14a35b0aee38954ea5823939edc32190eeb664e33b14689846d9ae671329321757246eb4ea4ac636fa51a988c87c99cbca8b343725daac13d459a9b8e8ee9c4799a0e1d67f575e8d06666729e43c6e7e876ec386bfb8e1d35b2acf49cebe8b6ebc072e438abc673e8e4882519f99cef3891bee3a9b3669ed2a1e32cee3aa68ea7ced2301d1daec6739e0393798ed6d1d1d97156f61d93a5a343f29e9a1375567794f69cebc039ea33a9eb38eb741d3972e0f89cac1cffbc8eb3b6ff2c9913de64ead373ae8342fd3838b739ea479d453a2a5ee73b6795bec33d374f9d557a8ac33ff59db366feb3e275e4b8b9c9f98d8edf9c55e3377c16f79b4f1b9b1f67b2509385fa6f83739bb3badbf059d8e7739c259f8371e89c955d67b270e83c67b2701c07e739cef29e7396f69dc9dab1e3e164d94c96cd4da6df4c564dcd6d26ebe626d36dce92b9cd64d5d8bce6ac6efeec0e3f7cea1d78d464d54c56cd51efbae79cb53d67b2c03fe72c6e46d91d3ef8d459db1c6277f8a9c9da3191601ddce7646ddb63262be6dc3c7796361dc04dd6c6fd66b2b677dabbb36acc1e76877f7396ccdc6177f839264b47e638262b67b2723ec3c9ba8de74cd662a10a1a5e8449011896b8b2aa71840e3461043274e104568eb0b252e33f716767e6de981b77f636e6e3cefe34b5dde1e3a06afc66b26c665e33592d1ed8200b5cba484105183c58d568a160034c50428b25a0d16505ab1a67d598acd20469a61677f63373e38e8f5953744f9a3567793388dde19f264b6622c12b2bde6326ab74d344a26765857470b23cefa5c96abd208b19b2bce00c33b6e08515699b00c6062e500205676881c68a741669b2b2d9f3cd9ab8b3f7e6c69d1eb30ea17b3987f454b23bfc6eb2b889446a65457e9bac1c72d0032ed0c00c31c0c460a54d24beb2929385186e8005138e2043165656fad959d96461f3bbc3675d138c3b7b3937eef098b587eee34ced0e7fdf8a5476340d569618a37100f7a8034dd03da2d7bb52d7edd7b3897c9fc32acfef7e5a55bdd7530fadaadeac2c1b04c10b5fa0a1a19939cd6b8de73064c8a7d8d0210ff2aac3bdef07d02ad4c9fbd0c9c8f73eb4aad2ccaa813154610863989999b9f9cc2bcd0310804f29d1000409f2293634c86df8eafb1d5a753a9d4e9f72839e0ef4bd035a556766cd400bb25842166e6e6e6e7070705e7178fd61830d9f62436d38fdbe00adaaa7596fcc6a63d69c594f02f85e02adaa3fbfb98761181156ca10860c4354e1892a57686868664e632367d621a46001982666666666724eb36641188110ba70f33a7300bcd21ce7e6e6e6f586571e20403ec586027925f24a430d357c8a0dade1b5c6cd67cda2f881152e6059e8000a60b8c800431346b09133ab148314c6e8c2e775e638bcd21ce5e3f32925eaf33ae4d5474fcfa7d8d09ed71a910b3026b0417c811668dca077c0022e78c15e7042972b425e677ec32bcd55af35f80b26364002e6001547605a505c91052b4558f12589287ebccedc86579a0779ad914411b898800755b8c005ac25664002301938ba821745e0799d3990579ad3f05a6304618ec0821b4c70b1042b368608c1079698012ab3c11423e0418fd799a75e69eee3b5c60d36050caa3006ce8d592f1378390113376ccc2aaf504513d2b0819ab5c168418b2b765e67aee395e63c5e6ba06e666d1f9031022b3738b3320ba208c30a38376655c1400847b86163d622b4a8a00543c0f13a739dd71a3650b3b27260540230a06e66d50019a8106608352e4454e30667560d182561042e682e4444f31a3fe1dc983503590c21892dd4a85183e6355e4f0f5f677ee395e6f3b5468c8d5989c8028d16844143434303a266cd725005259880ba1011aa7433eb45052dc420e3e642443747fdb4e1cc2ac50803092a50a807b36e7c0e6d12af181380fc0e65b819cee36beb23dfaa1ed0277bbcb63e1848e54bb489d4b48d535eceae7b4ddcd9cf78a74f8614533281a0a9c42499efe4cdd474345c8dad46b3e96e3cd40eb331d485236dc41b43e89e3be71eb30004691f7b5f0b6bc299934c0c0de7794df7bcd7f7d5805180fc282429b6e4c5f8f3c2e5d947c918ed77248f4179d207a44df990ee65e903925749d27e077acc02f0b49abc8e718194f1b805209f590032ed9f32d239d4d8ebb88d448aa0ec1f5ca02520ae54d00db50908e07edfa23b3b2edc503498782419cbb55efa6dda02d61ed31630f338346d01f79d966197b69178a28ed42c4c983060a0cd8567ad00955b96678f81d1f1bbcaef1863eceed27ed1554923a9a7d7a94d498a558c44bbc676483c24a993215462a49ea82318ed37ed591b40b999c1d0550659431de1788e4b6cb4eb4e91fa3ffd9a668e523ecaa945d49148e399c6591d407bc62f1d073a1f6c418b9aee97a6ab4d2b33874da4449a8588f88deee783343292743f1fd8c868157dd8a35564cadd7c44b7cd1de5f515d9281460f7bbddb849b102945fb5731f907702570056f97d4072c81335453d6ed2768a9f002b48bfafc84655003e01568dc178f003aae914cfdaaadb85f6390560c7531c7e02dc7726d9bc34a4daa225dab326a13c23674be8b8b9e71d671619a453fbfdca7d3ddd479f3502172d608841088ee86203ab960cb2084307300823085dc458ed77afed5767990af2dbaaf5a972c490d5f72bdacac6156df5a556df90d5b76df4d91a4a3acf2c365876007440944b250eb42fbf114c7c05b1a73a25df31a805c9a153f2da1cf2ab06a1527e87e873a3321690afa05ef70c9c5736ca795d2e355eb74bcdeb7ab1f9cdbb9c7057376ae074d1475ed69a6cf6f84851799d3fa01f930f3f30a3534b771840a7e45113fcf560ca4cf2594e68fa8d10ec42a6a4504b02765525be5ff31af7a9c2010eb3d272995eaf0054b9326465fa11da07802b35ab7e35e57025b5ea5719ba27ba974bc4333d8677e46fdc50f96e3f900b901fbd9c30fe46d8ef8f3b49889b282e5b6a0a57bc23df7dddf7eb81748f14fec41df92e092aeff110407e9523be5ab6a1925848dd2ea83c7f4548d49b3fcb23cf4238faaeeb9dd0f56e1a59aadd0ac84771b38718fb442d1be18ffc15f650a5cacd6b5ec5e635defd2c111235bd43f05bd843a7e4390860a4df6ddc01df6d4cd4751bdf950348475d138500d36d9c01a6db984568f1a10fbe8878d31900dec66dcc22fae077c5540b13c05f0230540fa6276057a6332092910526c2acc0778d0a777513eeca26dc55cd529cb0872a564aa7f94fcffc446b84bb628a13968e0a697e13cedc263cbd2694798d30e608d8551599c7fca7ab980e1e043b05863bc41d7913172a1fd385cacb78a1f2a732a8fc4c284f235fa209773513eeea14ee4a26dc554cb82b53b82b30dc55693fa01e7ee2ce07c09a4ec95ff3d429296f780b904f0abf7023030164f9d59132adad413174bdd3d78670911a5293e242d767879f9ff5e9e9599f7f7dc0b8c33e1bb92c17364207762af521945f7be82e1794b726fa3c157d7a7627faf04a5e5876500bb3d0db141f0bb54df16e6a8f7a0e523985a2d0a6422cda5452497b66d14e9d8a07a3cfceed9d187dbeeddf88c7c0aeb417464ed017e10c69d0d6a1fdcb8b5d941d5f6a916793f86b97085f765758f9174f2fc6eedd66e5cb63b3f2bbff44166251ec5d58af677c226c3691e7cbee0ab3103402b7576fbbbe23d7b759afebfc26f1dafb03e250865ea7fae0e9c28e615dfc8e5cc7666d127f3589c7c2ebfcc94be95de1528fbff8fd64a8e9343685aee346ad1a7b9f017b9e45f0f73d8bd0e2037f5f441ffb2e86edeefbf143e23e48a7624ff4c1e6b6f67c1f96274b8b9c3e60efc9e26367f5f7ad558c31f6742ad61c687c87ed35b360e1ae7aa2cf7b273ec615f64ec518638ce7af872a7dceb2dd8aad65e956a77650809c179de2a253bc2700b32fe6f109c05eed0392df2ecf66f24c2f995139ab112aaf581e1180d93bcd63f00848fbae2bb26b03c3261f7e00a6534b6b878503137d382f5e3a2c4a683c83cbdb6b8df60a5eafdabebf84d829f61cda0493df66ad99d7dc63215746c879f980f627c6b0e8a2fb9f6613801d16caefb0b46a27c7a5537cce0bca6727c0ca7165744de5120959d27ddd735f3ac595d1297e0da0a4cc95d12aa6bc4b642f2bd60bf7852ba3550b24e7822b2f8b2e8e0bea88fcbe45a7e818a31d739e0eeda2f43af46877791d82b4c332afc3e5423bcdeb10b59d0242370bda6d9cd7610ced3aaf438d769ed76110da7d5c90b492d761d30ef43a3cd1cee4755843bb18afc31edac9781d5626cc5021b43b1d7199f13a441da992ded09d4e4e5cd0781dee16b4ab41337392f9bc6e71482b04dac52d9400b1631d1663f35ea428b70cba6fd16e9500fbfb152151210eb357a01b05da655236c56615a22dbd2deaaeaf0889c6d9b4c31d830a2b68c75e1a3486cd9b65596324d4918b625f5a99627388c6b963c0600b7a6df4421de92f94cf437768d77c7901134b00d6ab0cca472161322edac933817d479ad605640c4517e535d498b761f568ad4e964b0c64408bbacb341280f53adab8f008c0ec4318edba21d40b8c7a43177512832668516fd12d8d76a4a312155aa045ed05e5578e565e02ac35949700b377266fc39ddc0498bd45bb186fc3218e3a592e743b25e36d78ba1375648f1d9bb54b17cadf39246975b25c9c2c177a5d415bb43b791b321360f66e4f323126b044fabc991939139bb7a6a644b15993d0788e76dae66d181e81769db7610884769fb7a18976246fc38b76256fc39076a0b7e1d0458bae6b0bcaef4c197649199b7939a61de76db859d0ae43e980f29986b4e32524ed62f3c68d98e7edbbd3aa04d803ca8c05acd751dc8c745db11db76919767d32b6c74b2261cf2836ab111a3730941f86340beb75c5185378d962147d960a36c8a205540053061818adf8dd95e8d3802f64687125064720d183159f03c39d714d214febb5652907a655fb6ef3580460bfe33c066b803e7079b64f28005da0457ba6fd21f1e8a28e906846775623928b9d95e9a6e4d265309de2caa541292643ce8c4ef13930d187f3e2a5537c2f94bb48010d318a3006316cf1c0727922066788810355cc2007f2ccbfa0883e205f532cbdb0d0959f9148e5e3fb9d0c9b76ec5d9beaf777e4a2f1f19abb3c1d69ccc2451f0e4ceff059a3fc7160f895034399268c2e625839c216621066c53736c0c10978f0840778c0c48a7f6d893e4a9c188113a2f04215c69062c5bfae883e53ac408225b850410758d2587108409e4d3a28b6ced1d25ddfae55f2ab5119ca4e5dcfc2eb1d83bbda70a9167d50323e3b03fada2c427b3f9b456829227bfcae2a48f7deaf0f117db65fddb3702fead41525fa74930352d2a90bb5dabc7767c0766e16c17d7b378bd0e203f7ed4574f7beab2bcaa641af779bc733cbf79ff68a3a7545893e4a7ae7faf5558dae36259dbaaed7f542af83d76baeef75edeae6c61d6ef6f0d3fd6da7ae3308c0783d7bbc15eda81ee244618f6fad260a9b18d65a5d7d45320eb20374df1f10aa87f8a53e4432b2d084d1aabf1f0a01d9e37dc81e274b8b3c2b3ebb9c3e68efc9ea6bf228ecda779585286cf6b03dfbae3a06a374ead2422dee5cfc0170884e5dc76651a7ae5fe72a2067ad6e0d092f71c7b8c89857974ef59300ab768d41fb3fd18757fd1da28f0682758fc03d0273c6425fb909aabdee19f4958d288adfa21753ee0048f7678944da97672c911b34228047a0bd4b18da5f5768a72a3741fb5a7806ed676c4477883b7db0067c4d8ad6d30a2e18daaf295ac1d5864baf56cd533db45775480276f5d3fc0150eeaba45e645f5d74bb037c0fa8d3a9f6b946755e63e4c737897faa5539dedb6bc9e635877b25ddbcdee85e3fd4ab0defd5c379c5a1b176365e51a457eec6eb4de975cb79b5a1f1da12913fbdd680afd95f6b985eb1f09526e6f5a2f375e6facceb89cabc461caf3234be9708f61aaf269ad79825923d1e5c22358fb3e6b88e7739421da18e07c0d3a5f5f48892a8e3fd94fc8074cc05e373844be3db07eca7663de5a9133218cf488055d2530d4e3f6abbc3338255a4517c6b1a08d61a1abf712e781c337cce0d1b38a81b9b9a1a3433273041d0a20994c17899988bc69bc08cc697481c8dff3a1aef751f8de748346ae12e4f7c16f2f2c463612f4ffc15cae58997a1b63c71365865a56587f0451834076b3fe8fabb3ed8ab56419f8d3bd7e3f9d8ac5acdac3571474e1fedb3abde14cf9a03ed07a17db93ebb3b2a6c0a4a3c610b347ebfb84759e856a75e5ca4c9072c9ce8d452f0b47edfbfb8883efc2cd8b35cbf8ea20f36af2d9d42c9ef77c521763e03aeef2c6216a1c587d5b2c9ebd47574f97a79417b77e4b150c69925fb4a86d7169dea5fe1aeaea3e8736de99d7e36b777b6d07a1dd1feaeae6bcbb2c9072caa746ae969bdb6d0e5020a33a071061728018b12562d23295e70851a58e183207c59b5ac88c00a3a18c2162ebed062f5630632988110c2a001129c58f51510255f8496f822f8d88bd0e2c3f57d11d8f928f922f6d78bb8be47c98942001fbb0f7cec71fab0bfde2b5ef1d743152b7cec56f6d7b903b42fbb2c111c687f485fd78e402e68efce461f6c6edc915798face47804bb9de0ad8ef83f7010ba34e2d4d75aa1fa74e15b0cf1c00b9a00327c01082097cf00530abbeb8d0c54125b662635858406bd52f22de4a9c1dd061d5ef49a539e7480916340b31dd5fe978656d55271c8d97ef8f87ebd7cc825d7e469666b9f21536a9702b2df1f143b251396b0f749767a3cdb42b03f5ac2dda1b497501b4b4bc44965bb1bb894e1a808da8fc78002936a38c51ce1899896ebf2d54de83824a1ce4371218d1edda470086ca7f24c042e5b577a520745f73a05bfaf7fd698f739d5679dfa75ab57ddf33a4555b28af2b5e19c7854b334dbbb6b4a4fda75148ba63f25d47fa7e06e83a8ed43d7b77795f7a79b348f6eb17f72c647a61bf6665da79def6f120efcd26d8ab3c518b6ed73e1e885a947b93edf2ddacdbb97961e16e2d8a85dfbdb0854272fdfbf5efdcac2dca9542ed5c93eff27a29ac52a54a95d28fd805c2bc3a6217488d749b75f5138558b414ee4a8b40315080dfe387447e7bfc8e7cd7266a7b77ecfd1df9266a9b48bc67f79e5dbef3ba269e01daf3985eb36ed7b8a3ae2fd5be85a86b16c1ae5d9b4bb96f94bbb62659dd34edf1d714e230d7230ff2daacd9b773ccf40a9952e116d75d13ddf200f967a2cbe93266022100aea49f47921253fa987a4b647777dfef8f07c97b0f6848eb2cfbae04eea0b27779b0cb7398bd1fbf22fc3db744b259cf5f11d64214926cf239ca57b77d0e65a71aeb637beee3a19bdcf2748685bb3cdd46d0bd26d8a91d54ce5367d1c5c75009c897348055a30d264106ed7bad0a999ee8eaf450b74248132441b147cd738012127ca12c7b70801355a22423a65a85c27ebd87eb574a4910adaa35cb45015ac57b54822de84539cac546eb92f1855eb4ae167cae559534824d2fc61eb1eff28b30ba4c755a25c46186f64518c5ce1f9224b48ff5977beefae059e3377e408c7d05387f45623c671aa0d85945144145a342b16fd75601fa4db459b122d823927e9c457018df71732389676c22e19a5665c78400e5d90c74c790e20a78da23121c512cbc28d8aac57ebdebaf006630c319ce40f70bb33ef658c861e87e46eba38d8ca0fc8e60b6a34e88697bbfde2111d36d36d926e01e9dc0095a9b5ee7708897e77a914685360cedbe5f11ef1cd7f164cad40b2bf6eb5bb8ef160779ef0453d0f8ed1c56bebcdeafebf6f8fe9020f1bebd931e7f46b2ee5be85d7e48b433cf5d1efe4f7b61edc9cbc3dfde85b529ef372945dcc2b8a5d0ed94fe16f45202bd8cfa039202360de81eb9c00aed15d03d82a20a3445f7080a1250be14306a41f7e88931286f41f7080558a80fba4727b8822e9126c2d1e51ff2a525b266a0f1bb3e18efc4ef972fb19f2c41589ea59fa43f5980b03f96ca597b562a544ad30b0c31e56f270eec3c6f897811660b2a54a25234ca8fc71887a54b6bd38ad1f82e7ad777a44b030bad46d2c07204a355082584f085172aa48432181550212a245a8ba4f0849a15d05a34c50a3a29d05af401295cb4122de1063d76c08556a22bb8503e7f473423a323f13bb26128fffa8e9c0ac93093b3327f48b0f7ac539041bf22f2d77f62d716cd7e8528249712a83c1636a9442d2a9f5d68c3840913d6566764d04b0a2b6898859712688739943f23973273cda24672da6fec57d8a427ea88c4260a897c76ecd7f92b229fcda26e8fb274a1454daf59fb52985ec7c2905e6148bba8eb2f186db45b000949a3b85ee4151719d897651cb80c32e8bed967577b8e71904674df655efc8a2c1999a6691ad69a16f94a91f8dd06bc507e7696efae70d2e68ec326d9190bef59c8f7c2f819c99064e76c8500f7480930947b7606807b94850bca51ef5d173dfe8c68dfce85757b932cacdcb37759883a228d8ce8774923ddef59d8f4fb4f67deb5cb537a3f0b492f8535935f91eca4f9fe8a6407ff13c11085243be9d949334421e168768e66f3667924f6ecfd0e8c3ebb92cfc253a7e44b27854b97a77acf3e1e38eafd6689c85f1f0fa8e591f7e60d8964848db298fed360f4314dd93bf2b234ab462bd8243bf79a7d0bd0c282e338ee3fdd498fbf22fc2c7e47b2734d48cb235f5a1e6944e5f2c822501a1941bf775bf7cdd27779b6d2495b5823ed4eea485bf72f1c2295bcff74c93ba9fbbc6f9d37777d74738b4bb7709f55c626d8a9f849fb5f674974100025dd2ebcd003088017dd2eb0e0b28b030cbd48e38c8e7b06fdc5599efe7ecb9375a8752afb7e211b592ac461b8436f79b23678cd04c8b7e1ebe127ee28a105f17a044dd4e8f084ecd091daf1543c767af8f0783ea0e5f14103f7784380f4d4e06dab96006ff07a891e01d84d803878bd440e1e5fd71580dd8d2b375e54c6b031a65778ed27c3489b12f1647843d3ed54878327c3cadb4c805adc690078f4030a80b700d84e074ff6122baf97e077dd36bd62bce2526fa3cf156d65ba00bc5d8e3691595d1922d33d31a1cc4d61ccafa456a6772bcf145ea9599962c22b5f99a200bc6d55bda83c472a4d8fa609c61d090454a4c876465a09902f3b231f50b7aaa28e30462fda1929b51031babbbb5952be3c27a1db567169c17ed797ec90bf5f4f2f457a7a227f8b60ef0e0143e2cebe93f13ad13ae40332a100ac1a2d827d46ba52115e1e3ef750c58a152deef0b5b8b3d967245c1e7eb83cdae61929e245a97d4652d54477fb8c34a9b668f61901c112028c64549efb1250028a81023cad1be5d74dbef3daf33ccfe3d548f233323b6e99e348d78d60d7e4b8a87531e684ae6390b9232e605bb6a0b11730745b3524c461e8f5a2ea615f2e362e269b9fb8d3e7ec0bc57e855c2836b177517007c0ca5eb25ca959d59cbf3000aebc0a9bc3959e554d006adec79c58223ad03e77617d86883b6d85cdc2aab2f79530b4c71006c861800c76ffe9293a05c451fca7b1f307744269bf796b85d2260a02a7d3dc8799df4c9616ed33d7a60fa7d39c66b248442081163354118591d1eaf4d6cae635be5dab11da68356cb69985e63472b217d8642e9a006aa7993045fbac0570573c051a60418a131819ad6ae24ebfa6565aa47094bb0d7f0620bdca3bf98e42827dfb36bfb40343a69c05b072144d640e1e0be0b90ae076eda04c184b60e908768511b40b77f9e6de9dc7e85477612edc9956e6c2bdb668696639459fd3642f4c31ee421b3b33c51807f012fc7ea56f21c63f402b3ab585bf74aa04ca3016c0989b2e2ff34eb62783cd98b0142eedb88c96973926fb32217fb902286f643f24580ec250d97d0b3924dfabdce59199594ea7ff347f699fd399b29732b07bf9f60e63ef1432175d006cf2165c0698430fb6a043ac0056ee01ed730f5ab54dfec247ad9233cbe9cce5c45cb8742ac634d90ae642fb5c05b0b62a6f3180e41fe0b789f10ff03f9d7959e802dd5465300c1581fc05da5ffee104dc20a47105ad816e10d2a842bbebc364507ad3aa51f02038ab138f96bebd2b854c05b0f4d27ffafa906c185aba0c398a5a44b753b262dfce5f11ec204fd1a97ee8c4a3a5c932f8029242294ccba852e588af6afc889e15cd678e7d48be893a72ec0a23ba4d2d601f909c58d8b4cb3cf6d2a92980dacc72ba17b297900bb90c4de3273ad59741969a3396e85333c14dc35e23dcb5a14a8a9d26dc0552318a7d26dc1582fd0ab34d61cfc26e53d887883ea5ddc1de39b406c5b6fe115f71192da4f70cf6c204b0b29721d9672ffc85cb686c0e5d14c3a6932b68515320ae24fa9ca6d73bfc3e51ee891989548a46af7759286f062ac312fdc2892050195eb46b8f0bb48bcd72e345e2282c4f3f93f40af7480b2a50196e943f20f6b23c7d9602c8edf39e2e189898a69142b717181adfedb903208ee46289a868d4a18c5dd7f43aa5752adedb6d8b02185324452f2e62183f01d62b8aab4ba7e239f6025690c61d68768602786d893e9ad6f5da7271e9543c3f01ac5714dcd4c22149ebc585c667a193219e13c02b0a7a45d1aa292e192c918b0c709b9a36a5eccbc3302ffb0a804d6d79f81718ba385a1a6333e856a7f6118d59560aab68d1ce812846469da6bde2d414ddda1339566d671fdef2f58972795d5829e42f5b488fa46b738b9785f21d06d87506f4fda7b332b4d02aee114c77d2ac6c4629beebbc2e5cfa5d1e180ea1ed79dbaa4b7a5fb8d48bb4fbbc2a5ab0ebddf60171374b3cbb92cc5b2b3093097b86947ac0cbbcf498d71e1ac19005ded458b47e22a61453d2669652696eefc4f09129e4426687b8137f455bd1296e02c8c597c25d31f37eddd43e231ed596479b594aef48dbaacde3baed9afb8e64b452a117cd4259a54aefc467a11666291d0c3910a503bd2c0c3fa0befc8c645d85c637d14e740acb10cc04b0b17413ed44abd603c35d31b89d9f006b8a6eef3a9494c3002b1ff5fbcbe6823d872edadb7492e5ca57e065408b9a9700563e3ae22d5ad54d742a3e9eb9681f70b613bd1323cd6663e954a4dc05acbda50a169476f98bc6b715adbaa255d763bfb5025f41baf5cc627a6b25f36ddbdeb34418689b0b0ea1a6180f864a007775d3650ebe6a7cd4a9e8058defc0705706a4d9cc02fea7b7e8546f017b4bdc2213ee0a0c3f305a92b0031e6031325ac5842c2d3e80374d168be0082118adc0f9b813bfb1b140117d7480b1c413a8d84017aa28c22abea7883ead2b27d8828c315c21892844b08a6f2cd1a7f3c011d030820eccf81284557c3b117d648ea0c51738c8c1152f3a58450c0450ac76a5a55fdf583a159f04b036960fe87a636955636915ea08a38ec8eb1795b47f5a9b1d5e94bf04801cc801c35fbfaf74953e475930609cb4ea9a710a1b28687c355176627db867c5de5da1931088209000ca782f07b1d0782fdb7f9ac13e7be9a69798b197382558458bf623bed2c279692c8d25eec4cf2e0bc3ec95bdd098cd6db2974ec52c0bf6ec622f5b687c6f69d5953d62b13bab5a6519c2a6965670952507bbb3b467cdaebdcb422ea3535f3a159756f6824486f6b90cec03d2e6827d7969d597f6e19e75086d2cd1878fe24efcf6aa55b0b1d0f8dd4c3e609144a7962ebd0cdd2cca2004ca47d16757dbac9a362b98cd5a53c407dde5c92af6ce527a5f33cb95af4a0fa27d4a8f07a255f251be14cadee93ef21a0707f1f25774b25d286bb428d223dd1176e004dd2eb14bd7ef4adc71bb6c978e728a2962949794f2c2aeebbab0cc0927300cc3b0acc46280555e41a356068dd189e84473d15c9c71064fc1537c402c0658645bee282f2cd3da48ff3243b75ce8f5eeddf655127db6f904d81f10a63d110618bf22910e5db60aa37b04b09e5e45cbc3053c296995f6eb40b42af67b5a257ffdd72f2d642b3a753d867c45c83de8d4f520edc3577ae73a0e8e8e8e0f1f4182d07a7ae55073a04aa24f91195ca553d79f002b1f15d9281f75ea7a0a4099a53a75e974ea323202785d7c446b7096c8d2ebd94d4c0a4027db856acf320986d43ad558a67d05c83e02b46896cdfe46f90bf0fad2bec75c343094baebd18bd3fb77a20158a54a46795d525e5ce3a4e0a6454c8b3bad6920586be4ac97a7f5751daba157cc302c4a958c1dd6a6dcb1f907df6bcda355d2965dd0ebb2555e17c31a68b73b207f0468d1386b53d411d4a95371468ec6e9c4a318ed22f746499273f9075fd2ad0bddea7801d6ddc20ccae72f652d11a6fca201ec50805635dde81e7400e380d11dc309a860b4aaeb85aed12a0dbae7228055a3bcdf42f7dbb9206fa13fad310e4bf91ce3b037ea8263947c052b41c9b7e8e932de3fee9d967da34fcc4ddf56954efab68ae633608dd79834e1766a669ec2ed94cc8c09b753a659190adaaf0937ee80b33296d2dc4e91e6372be8cdaa75b3d670b382dbd46636b179cdca5762649a965abad5a998118df724d8718859d4a9c84980ad9ae8c3abf8ee925f30345ee8519688a4f1d7144be4061abf68b0118c048def2e4f8692f607e4a453f1d704a2531109d35873e22a98518bb185f2bd4ef1a3e814cb80a7e8147f871ea24f4ddce16b20585373e626aef3bb2be4c7104bbcbc9cfca535d1a787b8c30719aca9a172156b987987b8c357cfd3043be5f116f9a55b9dca47b177bfa0fdbad13daf0fd0eef449b46390d90aee014fbea20b5b41fb0e50d2a54b972e50548e227b6527683f9301ed33145814b4cf2146af39056d255dba74e972510774613ec25a4aa1db1d20d32d94f842f7880763d022d03d02c215f4d42a795d0646347e0c1a675153216a688d626464448b6a68896e35d157120ea6fd01528feebe93024aa9a191db88a47156ee382bf6f80b9b4491325d244d99c65f7d9dafd984a9a44d98cacb4ed12cdb3cc516e5ac63986e01add575d40110c865194b0407caef592224cabc5950fe0d2c753ac53e66aa873f24fa706736aa7b86f7ba657caf0ba63b17db99e3f4b42fac72754fa77848904ef554894d0470cf85a81eba8ee3b8b31570bfef3f1dbfb08d283f66c7b66b0ad8d5f59fe6217187b570e3c6c7f819df8b3bdc1377f8d42906afad253b2343b73aa8888a138580aee38ecd22b073d8b91791bdfbae503d6447256057d9510cc09030e5f30714cf3d0695e7eece19f97ad0b4b823b9bf1eaaa0e25157ccbe9d0118f60d7b11bb02a2533204e533b95a65ab3909a253f2555b3058ab5b3ea149d9b2a38c12884f424698f68f70b4af0fa827d8298961b119d3bcd075dbdcac814eb6684a7e77e557bec96a9dc2a4bc24b6cda579b7638c314a23ae48238d2964eca8826ed332ec92dd732f2d24c76e13f06eac751631795d526ea8231cd1104646464654cb628c1293cbb3f20732fea033eff107209f774b8392aa745c4515ddbd85eebdd07d16b1d03dd647747f498682ee631555d41a74bc03adbba1a0fb0c0cdd6347747fc9b80390df3be8eeee65461dc962d0e303b0d6d0aec1b90eaa87ed4b391966e9ae459f6c563962c86a7b37af682beedac66959b2c7b04641c5b3f66845447622b4db405552c6fee3513b7bd01e670276a5cd8d3b54aed6d5b20835357fce05768c60496a598c327694524629a5945276dd1d27eb40ca6b0aba456e74a1fb9fde6020a594584dcd9f7345b024a59645197be680b178bea49432a3712e632159aba2e3362dc32e191be452bf7bb98e9445a125c718399a2064292e2fcb962766d98c52caaba3c4f68a987ca472d629f4c2a494bb9ed4564a0c0bddd6031ec41863dcdd0eb14b6eea749219c2dddb4397ad30d22d4785384c9187836e23006e172b7082f2104ae005c5423b078f2ee3a0bd32f39bbffc83999959fb3565a7b450d3348c997972b70cab138ff2f7044ea8bc2655da2bd6d81bc3e666929b434d36cfaa512c9b55a37b4d8ef237da447372a2ccfdead108832eee8d60dd492f46da5d9ef42eec2b125b88c3502c749ad4c115af00f9db8fd1687958723442b743478eeeeec631c3e7dcb08183bab1a9a941e313dfadc59deeee934c8c092c913eafe3362dc32e199b773745e29629a3eb9a9a3fe7aa31bea5e6c3bba3659137b597b1670eb8d431e66031dafdcebae85676062e1c5cacc5c8e242f706710bddb7e418e3c61d743600f9d5bbae5953f4baa6739c13d637819cf5ba9ca4e44f1928a59432c6958f534a19e5253962e15eefa43c662485b194f29252ca9304a594524a29a5c41a40352c98c1b8f3836988717968dddd8d31c618d90795e7e5e971f1c076b218638cd2a36207b77d40fb8d3f56441db4c618235701f273ac0eadbbbb1e0e5a638c11cbaef30cab31418c314649f25eb34ff580f1d7ab94f132be898c570c698d31c692c736883146d0e318802c00a5c98b33c714734326c6c649e603da2fcea9eeeeced418639451d49035357577f7c68606b5df18638ca8babb8bc3653d2a34191b5eea3d436a62726e989e03dee3e5893528cd50c707489a5e4d0d628c31e2f0a200efe9c41863ccb1bb3ab61d5ed4525e94d7b3dd2ab56e671fbf977f932e72470febe1edb7fb9865f73c228eced5c393d2c9102aa5f4e1c57bcce36d517c8a5404290d3cfd83068eb1eb8931fef062ecf941038f8f1e3c76523b74e4d0c131c3e7dcb0d183f10d45e8e41594af9541f9597482f2b1e682f2af33285ff214941ff9cd5580ddbb5beae6f746da6734e838065ac7e035dc5dea591d40af6c01bb58c115f0d43ebb6aa395f66ebf78455b69ef2cdaeb1e7bb7a1b61b66435079bdc6631b632c6a2a677619aa9232760c130687f8babb3bc474c3845965f1454db364df8f013aacb20886ee63af7777777757f24a5e9eddaec678b5184ba525b26524d212d1b4ef5b22d9e6794b04e33ada44ae8ee39688f4b66d89c44fa34da44959b644b862d8d61863cc30adeeee6e7577376e948f31c618bbdddddddddddd5dcf051dc7cb7731c6b7a99b99c832b83c7bee2e5dc75a800d0583c1b83414608e188b16ac05b8ef36d334d214ba8d5b687c17312ae74ffb905080f23b823e8325730bba8e3bc5f1f3427e02e41418b12661186317097504a3fca63c6b03688ba0eb4a0cca2143b03d3a4ea738ceeb3e20f69a243bef034be0694f25197e42e63473024f383520cd0724754ee009a706ac7902d47a7b0a8d67d0a371caeee606f50181db25053538fb01d960158035752367088de727c0585394b753e15764a39f73e6dcb081839af3c6a6a6c6a49939cd3965668c69ce39e70f92cc966eb9a3bc4ad811987d4698728ba7d4345273e9f8d7f9ea5587417a85317f25999999f97bc45fe83a0f4863ade307f40179401a26b1c87b43aa2dca24e8765552f6ac3586ab9232760c1306876eda67da6488e9860943fbfbda6f1d68cf4ea384a24d831b9b58236bd060dc4c76d2643692d79c3c1a1263eac0d247921fd8938a3bab79606792dca6653206f4309a8b47eaa6e6c46954e2b099cbc33e6c17cb058d33905cfcf40c9a511cada4e51fd5a3aba570503736353835b01c40a7628c1d37e3b0e21f5a403e2f4f63f2e2d131fe3419390c39b5dc02884ef1b5598498944de5ac4234ce9d2849544a4fc666179ca44c494f7a5a4aa7c747a7f698c73d390c69d6bc2836b627658aea746acfdf2ecfbc649451c618638c35151be3378d6f1a675d003d5623f368366fbf5d2743e87e67b61327d3d5c4783aa9ef271cd307926400722906356f92d7a9cf84b3c3102a869c6a3ae5c100e48335344a1c9a8edb7098ca79d2366db31a5814bb93ba3639c5234a6b0dc7d3a1fbd614345e869c05e44b27db257a9daa279602e44b8d5f00f2f735684a54e6641ef30dcdd356155ff7b2534d7669368f37aef338f3b08b548acddbc9784ae815a44fa8e4e956c863a19c441d63caccd08c268004731540403824180e48e371a249de0714801198a2484c9c0aa3b1200b72180521630c21c01003084286c018e10600791dc89d2509890a5cff6e97debd4071d54cd3e3cd9430493078d579552cc9a67f2718ef8071f81e77092537b35aab78c60d0a77b0014e52538ce35525e533b00c8754df5f03e9852e377e773bd9905ac41f28848402f14cf84335bf94e4608212b263c09eff2047502b2b894193fbbb47c596116c8ddb7f67b45398a242407eccd60b973c5dc294648e9ca080671e1adf5b0760b0b13f336e06e681819ad10c41ea831a9ecc3394a504310ee2b105185987f52b8879f98b82450385073df563387cc1ade3ef79d3e3532b8a74b616af185ced04b1628b4d7104d2db5dc4e7ba7d36ab9d9064072ee4fa1538ba191e27f0ab6f3778f48c477d532424444ea7579f4a73e562d5e24bf6d1509d1ced29dd4a1b4a688a01a4fb3b349b96e8bf674584ee93e4fc08f395e26b84816e917db2749fabdc13f35fa44ed13537a6ef564fee6063e921afd15de1bf32a1f17c3a1e30790d366425aeffc2f8e091929531ad42679c9471df5a00a8b97631cb46fa2382986dd4e9f3ef758e11f9fde940e32f60c68c632950dfb9281a3cd2cd288aeece0d5cecf0685357bbbf2d8d9269caf5ad057a455d7c3b86e329d10f26125a570a7fad0c0c4a9d776dd545ff1d83f51ecf9d51c0505970ef7b583a4b43c3177971f501669c4f5fae0b01a4e95b2131394f0731e725d371578c1e09ddc51958037e0ceb40e93ed11c43d98471092ea4eb0fd16acd502d3d08d42814c908c3a2f40491e7946d4a7b70075caa1e9c73a5e115404165edb883a45a22a6114d2a14af8a6edaae2096d57f7874d0ed88db8614d8735036e1a0eaa0a9f3fd334f643883f08ec353121005c92cfbc42d24774a4d281cd79a220f10b557932cb53f55d782b6e1a04d6cd34cd944a4d54ca1741500cc09ab09ae27e4aabda847571b5afd2e79b6ef3be082e06e4aab99da1f6698c4cfbfb3b074cf5dd1e556364259c157df24098747af98dc69bce17e32e01f562c5c5bcf477404756ac7b95c26da6f1ef3c008bdf5dd80c36eb1709018272ac06fc390b6f2466f29578d3c85ae0686922d3e68b448429b45a40a3004ccd7f54d1baed2fa5e035c99602dd73e67b9ce84b69b483dcd3b017444585390b9294e54904a6c99000b519f376ec1d4685684c265c8c5211effa9c6ec34b048a2ba10de4cd581c5cb7c7b49b822414f5d41f7a3f89ea5985bedfb9de4d76d8dd3a94c2b5be04e0981c0d6b1a71809d7650f6719127eac39b8b708da6dc63a26da5221a8a4a10955cb2d73734bd192485989a2c3d22b665a49461c3298dab3662c4fdb73cb4fbcdd80cf81404324bbd1793d826d5804783bb65ef891426c6b24a90990ab249c213dc48418b02f85bb130a748f3787031a970bc0c7a36b02a544c3485d834d6faf5461d1029aeff1365f1e193c1b6c4813f741a2f0dcd182c01e3c9d19da6b1e314cf37dc75a9629e813360d40570101772aef5954302f9358048d850c0e2952420e0742506c41a15ec1e5aa6a8d26588a739ca71dacd851b00c3f397dc086057716a27e461cc91bc4c30b5788251fa0858324b9f820f1f717ff8b6a6666fdb00dc2e0acd1926ee8f8fcc425985b52dd5a93682640885aebbf93293e07bb07f453f7a927b7fb5fdd60b4d09aa24f030ce91fa3c3831be59c71638ee70f6e7869931b0f21b9de597380e6bdfacfa84e13b39485a00842a1c7370f5cd064bfef54278dd993fee22dceee9298697f9059b4930ca3f4b650b9421596c5361dc5985c5c12ccb34070fd426bb04f929254988e0022830a2300e9a8c4c46074b46f5f43e7a27203e7d5b781c37230b749bf97b04c7d6e4c736575a64ac80dcf7a921161b5d2cb42c798e7c8d23d86bc8273016b48947a1d225d4508aa8ab84668fd07bc8353be35bd184f9ea1dc3918eb5947a67a52fc4ec789da931d42efbb1e3b54dc54f946e8d33e608989529533413045510261d2e890dbc1e22f89e01d04bf54229ce8f37f7e0b51c1fc114d13b510c351c80cda5a43c16a1304f24b1684702e60bf9186d884504131d0131741f2dad11bce06adffbd5c57fc3e26845a7c9bdd66a5a7ed48b31e8b57cd6b96427572d45fded05e44c866ce5575ddcc212936e8a675a94ecbf3572c8c6d53bfde23bbad71a5a6e781e541646b0551921b8be3fd6f1f7a2d03e8ef15afdc51cd310d6d514e9dff144e7cd4a6f80e9fe21fb99f5b92603feae1b5c802f615fe2fa8a30eab191138740ac02488e90f295ceb82eecf5b1b6bfcd15b1224a344f12c4cae34c493ff4241175d173c7864363789dc09da143544a61e9d595adf92451f99d2df886024dc03172dd9853a7ace9644ceeeba82f42a62508327975680f179a50bc168e8400d779a8792ea15f48c4fa52587f9914c1c64d3385f23dd5dfe60d19c838508321412f4cc0ab67c9fe297ba7e311eb7a081782e5ff77cb99c4c8ab403479ca136ac42276ac27de0fbd23f392e5e3f24185fb6cda3f05212cdb0a65246f108583051bf405f215c7d39426596eca0c6ab7d29572d08fe1a01f9af23e9536895ddff46e6b7baf73fe370aa317d8ff9058d9c1c4d1706bb782ca35c9e1c142745d85e368c4b50ef753c11955e6282012e0e10225308e4bdb846f2196906a6a2bce412ae3b8c85f219ccf14ce077aa9d1dab51cfddb17e44cc39861ee61c0edd45f9d4927b0e2edd13a22f44e005c59166096e34dc0f32d1af67db0c0c35711f5b88ba8a4c4b1bca259c4ec6a7facfe396d76d3d6383eccd9f06a3b8de6c1cfbf60bc7b301af1ee8fedcd9c01471155522f3389583c62768eaf8ff2cf8e08bf1bf886d7578f81afd1e2868a71bac6903b52b984f7aafc46db05407692973488d2a09e667a0174ac312def493cfffdd446fa11a5e3ce98a3a90302273e2128051ce1345ab2e3e42bb975c59eaf8dd4164090a24320a59d05d4b46ff7aef0a88c2e5b87c417fff48d2176f18abc4c08f32f091bdbd5c50e1a801bc4807bf9092457992acbc833f215fa744d116b69164e67d5688993cb7919ac12df8954887c31c1853d3ebbc418ace4108fc16e81d7490cd3078f3d567079134251bbc32c912107bc4b08fd15256bc04ca90ac92b147c15a84fb22f2588ff4319b695e5f233a331bfa63db43144619f4a8be87139de9fb94d747aebde1c7e1a96cca21275abda4756934c36e1601071c8e41b235b1080aa4c38e85eecffb930d4459be65ebfe760f6a77a8beaded5eff74e30b09d025cd97d07533acb25b6909f4518fca43341a32f0efb05e2cdefe178a75a2d01e4e27a2e64b3d7f9aa44adc4dd2f31c1c14b7744d2e3525cc2582ba4b44bdb496b64338486aab537b99749298f467423e79d5c04042528f01ac8ea7eaa502a2c0ee83d32742fd812788afa5ae6eb6753286beb8befafe328b349c107147e4b21d6d6950aad82775611a1a6d99f9b0c09fa235f9b0cccf833d1a706734d0934d964513c12b85f882aeb661e72a879fa6929fad2bdf908657e618e1fb900f9ce0d9c2585c0589ad1aa88d83387f94a0c753327b73c8196bd9fc6940e2773ed439ea4776c3e7c4f4602fe7e7eb7bac0a1203b204448e78e579d063061c1b27e02b7a61ff4b8853942d4f8936f1cdefbafcaeaa177b5d90f8adf1c3a3962c4a0c686b298a873c09c398658621e889baa6066f8b27a80a3a9ae6a0707aa34f2fb82b34fdd5717d34fb8925d1af1944c7cf90d2b7db53c66457b055473bf86a9b44e7bd7a1908aeb7b06fbd85fdad577acea0ca2eadf0673279f956bcc8dabc4ad67eb5c72acca3628444ae88174e9fb0343711a83995b7745aaea61b26d6b8f13b934a19180c12e8dcf25502ca1dac0c89335a59b4a19c4089ff329f88b9a46864f33d7b1056f21c8bb055b3bcea13214324d91c8ed854f33c3fd00561e91a7be3f5f61402957a425460892211dc74ea7114518b81bbd25e887b292b175c6f0d4532c24c005a1e0d6306805f90ad5dd263dc39bb64ad100d97d1eae8c09ef41dcf883c81c1033449cd8df3937d9c7d5ab21f67e4fd5885140b67add9d5972f0ba7b5f654ccaaacc2351293bd2ea044260615e3c75a03444e1bc024f9ae29d25a19564919cd91821474e52c79bb0eea29540b44ea9b127998b23b3ee024378173494b9744137831077539b9d2788af1401f74f1f01e3bd0a0127aba22f0b0827615970bdd49440d290e33dac44e5aac5e11727b7a4990e96e20d47822b41ba68daddca82e629a3bac20bdf518353330f846b2627e3251e8b1d00f0246d43f5750c63efe8b4614108285c249d37c5701d7e00f3d393880f4bffcd854e25dc8a9a0f380f018f240e286e1a196e0b301898182a94df40c3b3ccd9d26621d561ff1440af57a0c6f3e1c86ffb883d70f67e8ea164ba4dadb0dd56634738cb89c8f9f1fdaacb34cab97a14ce9407aab422e1510c3907d3d527d3811c23162a25f108ab0b3ce6f101bf6678a6569dd7b5859e1634641a5351990cd641496bdd85dd201aa8c05a6e3e52f4f70edd892141bedd9df0e45d9b02c491094d1862a878c818af03080973d347c389b321399f1bfdaca36c9899aed2c7844e45806f9d486d0be34f41156a0886c3d192229b66a7d0db8381108286ea91bca4038438e6e967c3e3a71fa0c1668a1f108ee9f233c0c8ff2227ff535de05f20d8a8a73aed649df16fce3bacda616e4ef9e850f8c798763bb5ce555fa50af7fd902dfa6dcd8c8671a5159bd1e40a2dba6b19fe351cfa7461f388b28c688a22898b453796b0409630035ff9c35b4685b10de730744e4ead1e79ff4630813b22a1c96039cdbf0d203589ed3829f2ff407ccb8997bf0b9efdc7406dd962267589467e6190613367d7355a604d01032bb0373ef0b75ceb67fccea1e51057b5980614bcb90b9442bd13eb1f22a0bc3efa60441c46de3a95463bdd12096cc413c027bc9dbc313fda3e5d4c16dda9e3ecec86f6534c9d5d152de6beee95193c5e7678290c4f7f72c5d8154186b693f0148a34a851054dd284d48e173c7b8c91106f1c76529b8a337b849826cfdf31598518a546af68fcd9809c1b99269616d10f12e8106a3f1c8c184be42e8cc82d4ea674e33294ac1f23e82418606f4faa4420949b839e0b1f11351bac82564cbf9fb24c5dfe9b0e5e7d53aeb2a06c154baf852e958b2545b1913ad590c3d9942b7de86acff8f652ff56ab86f59f525a707e97b41351447c54a7150aaddb958d3344ac2be97626aeb29d4f0c0c1d704601356c5d918eccc78208371cd93f0de7d51db060403efca47bc1f0b2fefa3f940e3886108ceb46c284028d8179a221cdfa7ce465256511a47dcf796ffd35e39a4c1eca114c405f455342f8b9ffd40b326e94d1cfa1d7e8896c23426134c9f79d478f9ecc241eeca4a886af66f7bdab898b8bf4bbcb66fdcc07a69425ebb60fbf3c3ab734f3b090464787ac1f607a4e0f6fd2d2c230e0b13d849ce04ddb6db8b4a06544271f8f42d5329e5033ec8c785a7f8199e23568bc0fb0d28d7f686a6cadfca326e68354eda74d9bb8843d0cc0c92f1d4023c0feb08dc56a74384c8ad6ec846397509c9ef7f3dc990d2a9e279bad4401e191fbf2c3c69f8f17c190164ece18cbca632f465997065a7643beeae1fc29b9b3404ee889beaf231800bc2d3f34ab89ae752912699e765582a53b03b4f697d2a79b9604ab3f011647a485c50c079bd1c943b3e9f7b0797e802f9334251accfb8fd9e0d703b6f4541b56b6e608c38b59c05dec585b990aa8f33ab3d49697fca8d908355fc4e5fdd6ef9911a079619a3882fd0bf1ca7e31bacf7023f945b3cd12794216ebe7f53fc95ff36627dcf7f4f4eabf5e389c6eda8165754d93f4aa1f1892dc2cf2baa3b0dccc7b3c5901b04f0100ab4dc3fa4ccc93c16324b9f9b8532e738a8cdae0a7c8b64e654be959de5715fcaea775ab894d1320a335de884eadd2c38ba0d0683dee422a4d9629996d2ededdee1e614232e9db0f5566a69548226140ade76a12c958f7f764467cf8b14a2711c31fd2fd9088e6a81adc96fe00844a5de21644cfcd2c317460509ccad2795712f473795a563c5d123c7e71173f075f37df0b75c4670fe46ccd6a8936c37141b2591ca0f136f651fba444ee333c496f094d1aebd8c05e3b73b93b57076d13737dfaad19365cbf9c7a0fccbf9ec74d2d53cee7ce0510643b2400b395c6ff0d9c96437321811b7b825bf25d926cc471fa225a802a08a969c19562dde88db321c92ee568df49ddae9b1e396aa9e6765f06a29504e5e9ba2fb0361bc245e9ba35d25c64f606bcb4237a19593bb63d034bdd68b23734f8c5332584917243ff3271fd4a69ce55c01232600d380059d24bb415fde00b0eafa7b5d0c9a8904e1d4ffc60d4e6ae393b514caf1b1a40c75a184455b113b078a40857f82f217b47828dcdf8273fe32abc397f4632e34af0eabd1a1d24af449c6955b605446f7b22fb19075c31b24ae52096fa2d623719aa65270504790e30086ef2baacc0fbbd5e4e57ad9ab156ad03b66503eb906ada4ea718f42139aa34c00f2afc5472f81f9317dc9cc919651750d751515971314f517f060eb84d67b1045f55fa1dd6fb91abd35b0016b0b76fc7014616fb4a1cf897a3fb4f67c5cb5f171ec6ca55226d0439c8f245fcf0ebe8d449e38f57a02de6d853d3fffbf22d969a4eb6af9a76552ea72d5a0cb47b4a35cbbf5a510ac0653098bf08b99729ad75200d59004ed6d287d12e1e3f2110734df6bb88e2e2ffaddb666a06a0e2bdd74e2c0de796562ecd3b96d89c5aa7a36b00732845c05019b3037285bbafa39a7dc25961b3734058896bbd9c21aff474ba076be2a01cc0981dfa224c5ca86feb11f90929a841ff12fa2de895c5cb4420143af693225053e62540bbb0a9fd3a2bdc29ee511461329e3dad933afac0a29dafe91f3b2dedfedd3abfad124cd36521992ebce8bcaed0f1a5e0cf2103998f92af73b2bd20496646f91de18a03d78873e090e005dae45db326d51099d2c6b6ad4ac78f6d5a391c00fd34320a3908461ed9b86a1cc0cf8dec400ea5f4f65b4bf3f91032c345782ca3b432bfd5e9c3708c4008148acd7de1281f98fd65880556e54a404ca1883795182999f6dff0a1d621e727d39514255a9edf5a6a06be0e1e6288d0496db677943db035b8e0cd4ef4c22da808c6f978903250338670e735874c79373ced9c661f25ed11b58a3863821ebadf7c8c0618856d2d26f95aacc81f37a7e4e9335f0d65891f9f989bd3f1e3daddf58017b35a5a0a544f295c1c6ed25e337a492608370d28ad6c089df94f306c1cd129c0d8813f9c8717214667edf73e8aec0a715afe279f492e64322bae3c066e6eb21ba19541d33d382a82934064096fff511889bfe969cebfeb9f4a897c32b4867533a18c76b253a38850aec251432576ff6dde0ee9549ae9041b23b5c2d00b00dceaa151e8410e46a45004b403cb4f526038032589e7846ee5b50b42a4897354bf9d968588ea8ac63f5bab12096588dbe9e1765fca76d534ee1f59f4a575db62bdd28405e9a744126a1ed28647e176628aadc770abaa6255f1f3f60ae1d659ab31a65d86d76dde97e17e9e67a0b16a0d92652c4d02407a82b626f7c9f50e73b1c5fba9565b6f72ce0e6d24a5d1628f59958e07ef48d90cd81dc1dd967501151a97e4ac25d69f5049a5669cf3f5f355b6f63a559e350321bfd0cfe33b4b529e76c40a1208d678be3b0b6b99a5f72f5c9c6e9664a8b52032e0376be136647b2785a0b7ee8009fe4509d910582d95c806b9a229961747d3498fdce57dcb7a18b9916797091384810208571a9b17049a4050adea81cc324f10853c68770be025bf91441c32a1fbc6572a19817bb722102818cf02ef2b88062ee46e8191c7f66a0715e08d5a582c3f234c992334233e298672534e7baa1364db69e817c5c15c76787aa21c0b3ca5e18ae14041e8ce9e2f2c9b8dd4bba858a96b175f55b6707663ab5fb771d05819f20340a13131c2c466b7b2981d04338d6dd4b66d562bf46da1778c42a70a2ad01627a300408db584fb35e14af3eb1c91d4326ee291964e5ecaba0d98591f30f15f93266a3a5bf6d5bfdf7f4637d0f79f3f093f86220dd11bbc9750868dbb2bde695902303aa69f8af827af1ebddda42c105505272d109fb1247d0f830534dafa429f534a27b760dab4aac5f42863bd23575ae302409ffdbe9f70acf57602f7316b66261f3d877ece80e13c999d61c5187ac216f46e73a5322a0be706125abea7fe2e087868eda3f804d9591c1f03afc6c311fd507dbb47246c35edd49fd72b80f0cfff383c8d01a53906721afbb5cc4c2afdec2182301ed818aa3520bbadbb344b3325712340875319cda665ecfcab8a310fbc1f25dfd5aa2cbad8e2fba01b2cbb661ef7e02d71f24a5b059bc2b37c81a6d8cccb712d3081ee64284cf56454514d8cf03e4b162796b9d9d6559960911fbc18ebb02e0be393f6e32d05eb60c23d4e2028cdc32b4af08da922578fbfad6c8717d00de6a7a963b1c777e7725e81b6bebf9fba6646885bbe143ec15a6ffbcc516d03fdeb5f40b07996b5adac4b781a3a03b987931e21a529953526211870a7019378dd4fc0f42ab9db2d0fd270ab72db3dd4455193677625f2eb7b6bd123267ad2233f5c39adfbad0d88538fdc94bbc660ffd184d3d3ec08efee158bfa53e4238992301c5bd5224471f7ce28e51430885986d112b6ad4f91a1265d042dab6de76444b7d26515e55f2d8a5a2abe24cb6a810450ddec558957de7f89ebe5553883662a8425fc180f8f922f7bd5992a7574c2755982749626ca7e13a3dd26120ead3bcb2f8df17d707d29408a022e4a8b89cd3d53cb7ab7786c5968b59f12b252c7fe2fcf120cbaa2aa9fc441cd2007e1e18459e664b202ec9fb863b701a10ca1b03ee0779c19903d323e15c02cd52fd12ac28c1e62f197992265e0c4f6d7645d35c055a39b70b9a43e6d6acf85799d584d99c02a6fb43c9055c4d81a59a106373562dd435af8200a09d7a4884ae64a0a98903d95cc7aa7a077eabf82f45ec0ea40a477ecabde0aa66c8a68c3fc71df5ea54a8cffbeadb6ebc1b7e3a30fdc21788066e121c25bc2cb77daf43a5312cdd2ce0e8bd7f80dba97e83df0ceed5309a4e8f4a8b6a506f3b979a8fd0db78a8c77e4baa769c0f58d6c44f550cb6501f3678aea9cc97d115110c3a0d358ceb48330f1716d47e6db1ddd7dc7cd9575dacfd709729b7b58f65ff8820da9958d4570a155a3566ddec186b3c05a477f3a831d7a3b46b8be13ee6eecbb6e662edcbbbbcd4ad6c6cff18172bfb358b71f9a8a324700491f63d760f7ac064a2b1e10807d038eecf488007d2f5a8b0de6bb6b549632ba7f464d16c886d6bf37f87f0f9d20029e560c45a831588dc767d141291648cd40045ecae6b22e98a4966891531fc862e4c2062bcffe26a8ef4cc18bb069563da40e02026e3d960140ab0386e2342d86f27835ad399bf34b54e02012fc46f570a5ab2c5db6a8a53b7e07ecb230b71237227d065340d778c6795130644ca5be124a93c5d2d51f0db09729ba67a497e08fbb7a56aac1998f27ffc37f22495d24bec96b985851038f9f33a167c0a5d16f68cba3c2c2272a7f8fd15510048cb683f1ed910abc379dafa50c8662486ab405de058039c68463d55af338ea1bace733d8271648435751528ddd05e6f40f8cd532568e1842cbc32491b71daf796bc896bb2d2b8212fd8d4366afa2d4e800a2744c02b9e8158b1091c154924ca96935d02e301649793f0fc532fcbe99fcae1ac38424c215902e1767f04a714620e669847cd8e785f03b9dbbd31be501cc03c4c3ccc01b6700702424fa70c982c6c2d8990251c62381312c50543166b49b4c909d02831ba8aa52979d16974dcc00cd29438622fd146efbcaae2e6031cec15a09739f3cfbb1d9f7a4c5291a821e9894455da09ee884d7783e2055c210a70091c8136ca895ef2c856f148c0c6e08b1a7b63b1a5917626d246781e32482cc6da833d06726f0200036c870470a0c0a857e49675edcc808d4eef7162a93d7ce2c47136c84c731841c844ce42d1909d2ed6e1c89049fb1ecf3ee80d62874e05e1bfc4e856b428ebc36d11eab21cee5c79464390537d10141545c82e888dbd127cc903ffccab16f1f615af0b21ac56541b8b7397f26159b842977a52cb1a7d07043b0c71794ba02f6f249644a395c058f8a67c6ed19378506878daefdf486dd78f1dc1efa550eed31b046d8a7e260f9599d46589af0ba12b33efa1ec1f0ac17eaff0255fc3b1d5b702aecedacd9f1dc809289572b73079098d84376a4f4e6a39cc9a56c546350329827dcfcfdf232988d7aafc35e3ab0df7e591d87ed8b02706e69968f8a6b41a0b2091d8f8a3db1f517ff35d45765fbc46804e9297092f47f36069947d59e8ff556ef0980dbd1cfe361a834932e444b32d0e0a4e5349e9816f85bba527ccb400a6b2f03bea379095392d38994656810e5b318f0be587e2c9a17f46b21437d2ba839829bf6c666ca10cdd8fdbfac9fe4673a3ecfb21fa22d7e4cc0fa371e8b3a2eb05d527d1636907f3f6a5aa51746cdfb8680f4fcbd2e854fa77e7a21a0ef5a11b02383f318471caca8539285b1f877f6bed1c5b6ef7ea682cd9308c14265e4b53a82bd4c19fd1eefca0612832e14b0b466177e7a9824dffd676a15d799b317ebf9f8d593b80200d28ddc828a30c30aa7a7165287d67a9de311b79f06a456a84c663da78fb3e3690b0e15a34461480422bc8ccacfbc00633007e7321151c1195acbd7956af0b0cb7a68aa80ecce7550d7087c0899ab5ee507d08edb35d4c5cc7d3dd1f55b601544de78018277743123ee1dd440c2076d8982b6ca07cd47ffd23fd15440b889b8547cfdf3017a26ea61d7b7ec583e00679cc578ef2ba426632bd229eba307b43a5d825b08f0cb444c893141087024088a154ca2b73dbc1444d6f962e879629e340d0746e15723fbe5f1932360bc080dad440687f81630d54a88417b8652442a05c685b6d16f3f3d9394ad86824f4ead8bc2e8d2dd9e45f247cdcd303fb3281d483cfacdaca01fa6aa7f177669a75930c74f068c385ef30d4f1b8d60ed28ba6cbd8c588015b43e208d585a5e65f19a96100cc6abe263e1eb31879e7a049cbc43942a07d2a312c8ca8d414be886c807934f31e32f184348477a0e0950cd171b9a186d50b4ac399d3ec37eaf2b123d4585f8782b0a379aa105d88a171c911bcaaca77555c149fb2dbf8d5c2d233dfb2b8270626662fa28b14a9092f3259c7609ebf55cf89d8114a487df7bdee816416e8d551017bae1f57da28a08ca677b056b291ad96913b7c8387849d9a0015c4875e93d4851eb904b038b60f563e44e96f1483acde7f4098d085288b4d3493b499edda59f3017ac930d3055133353cce8a7aa8a7c6132b4d60fcf18e39b5817be7960a6a52c4568650909a21639ffa248e0c27efcb1af87e28b82994af3bc8f91a05623cbb9ebc2e4dfe89af3200ebce2d2da5fd78aa0f0e4bbead85d80ad340c52931b317007fc3430381a4913608e15d8ef24f71ce0b28fb2b9139f3979fac26c2d82fad785f62e505a5c6c626565ec0868763c7c20942c16295121ebbb7f7702b0378fe5a5e6f1f8c045fec95b20a848845b5e4a05221660e587bc73a445037d9885d23c670b1c88fb28c563bf90225fe57247350c111d66527d47bae6d999b804d2c02f7b870879124e8cf3604d42d50e0a60a87026edb566a28c074382167ef8784fa69287e30ae96eafc0edb8c2ebd63503cccb1e6e29c12b56c220cf4ed762b3263721bb164b803b2964232a2f52b2f21b0aed6270b571bcb99f1b910d27c7b26496039fd94c8f444e7908a1594d63331857a4f5e729499bc1789e97c909c23caa98070941bcd52470ac2fa9f78b224311b70f4b7fa9ceb2d56749612ac5d472749dd8dc04537e019a5423e8952c85f35c784beaeab7a0dd9090b08ab5be435e49efeaa7bca8147f209131145a211eb214b01158681efa6aa4a169baf569d9430a3f408cc6b8f37e8249b3b2b51012f7fe9a073426ebab5c92a88536b9b3bb03209d5567b9ff4fbb8a76f2798e8cfc8f74f4463995a51dda75717f8db63f0a996f18046ff660436870ed779519802ee5011fdfac3feca749cc913aab4cb1ada6cefed2b7e6777ca7a886d865565dccfc41a4808b3f9f1a8d5de4b3e7bfb4736085ce9dd187da1b4eafd10adb225c14f43cb476b19d2d3339a073279e48c33fc5bc8cb8faa15b4072628d10e45e8c68fddbed540e024bbd6e5cc1b03f89e5072190ae3434145fb80c4aec8fdd5e87de21bb90d8fb8bec35d86610b3e6504194c7e4a05c3a4ff195966e9c0588d562e2dfe3c1101dad6e00e5c18f2a136a663cb32a2d715021f7c72218028c3dd797595dc6461e43a857ff4340c2981ebc0e47d0dbba03de46822d26f14fc4adebf4ee1be92c33ddcc3adb80adca29fc7251913b4a7f50825f6d1856c7e50d897d0df0b5ae7a0b6b2d014904203d23d058fc03f012afecc95cdc9013b326aa17ee0e897301685a50e7ae751408a948b8d875a237cc711cb89c23482949a8173cc882c628a81e19727c6f9b1d2e9bc77782b3cf1c91e1115dc3d4603cc3ee6ee5f4b5088dae1a09d7f7390e5a9e8d1e71d5ccec47d2f5005b311da91245f2883b446373eda118150c3579765d7c3124c4d49449f022f487c592c517685550c63028a00e92dedab1e98129b67a9656ca4180cf1e0a42a86fc051aecac1c19c6c763732064832ae1ac0df25946b3b1494a083597534855e953c546c41f0e4e7d74da677bcc87ae715479611fa089311bd7d3f9f719bdf7189aa9360be91d9189bf1eedfd15ae6f2de4eb596cd5a71ee7008a10087f35ff5133eaed32c29135e75e2ca2cd773843bf69bb12670afe36e903ae76b117f6ae04aa97aa3e5810c9a7fe0fa55f519cc18e8a30f9e2aeae9995b13d55891f91abff5e68c2dbcc9162b4a1143c214af587a88ec983f75b001ef8f5c3d3d3f38202329c713174fbc5b90f3a99ef6707b87f8c6d540167e905c176939309b916fb0d584c1d99fd06b5098f83434fd1d0f2a1876c7651debd3bee5b77c12f5888f2ff481947b80ba0ef3bcc8be5e02f67b8b552901a5a13de4900488d58dae6b483e6318d51e5c4e55b545fc3b20886d105e12397fcffe65c27d3e0b685b080e2ffa5743aba5c56c2e2d88c3a6be0804766867eafc3724a38b65cf3e1f6cae395c7a9a7f7f3c383e90eb8bbc18e1ae45aab100987b053090ac03e35b658e3933ff94aa7165462c46520faf5fdea372cdd8a111e31662a3bf42121d27dcd38caf086f4b79a7b008175fd51a939ff4e77642c562e07a6515120bfa83fc44a43ca98ef2468d949421df21fb25342fed2cea45b1e5b992272ec506cdce8b4d43c30f2d09b5330350bcd7988fce8d1246d607411ee5b00cf58f2304a363739a9fdc832d6f837d42ef9a632594cc0d47a6bfca2bc4c98a9a9f1ed814b7d637cdc287433ba8f04afffa3e59af3db9cb8219219da3cfcb02efc2b3aeb8f19167cb6fda90d60d9de1b270c7796404f23715ef164185e4a89da3f2d86a580732008e2aed741d7b52231c088597678ace3f6511ef6238ba48e6010076ad8da2fdbe20d130f903fcda5e1c9484934cd11009f89686615ec24520b8242196ac6044e516c7f59164f2b6d3823fae5db8609d3a3fc92b8c5b8d8d4462f3998f1ed621436564567a2a2f9f065ffadd7b8304df993051cb631bedcaff116cd418eb2e322bebea100abb72a86248986cb3321c22356518dc4fd9cd01e7031c18a09c167d5dfd1ad530302760d15a05378d2805f36412ca0746cafd4f9458c706dd67cdd980c819ea8f1193d3577fa71692493e71ba6172e60066d7c5d283637a3d1289c63c8406969156179a1801d29e103f582d5bc64a2778b952080d00a81a536117e8286115e81a19b0d0a14160e7bda929522c879aa39479de1564555c93d3f8958c5a59566f88c46980e42d1233f5d962fa2210f7c62092ecf70f175d8bc94021e97a59a85fd34717fe4ebd6d6cd2a634e62e0583ba4e7d237c07ee257750f63a9f525e3e60defcc9f745da059506b04fe42569b349b239921f9903cfdd45f17154a4b58fb00045cb3483c69070291184b245ede9a1b026a5a5191cef620398921241343375badf112c87ad5ae0994f474353cbdbf6c9f1b3c71edee95b42d905b62c79a46369b2347f6b3bb19c3c435368a1d7dc01d4aa7243fd027558dde8068e9695ec1de7621ea7d57982622db2b5c5dfabe49aae72cc633f72bd19e8c867cfc5cb96d08021ce0dcdb19c11054801bf296893d80372e7e829072462def57682179a217ee9bf10c8efee0ec141c393faf6178816a798af1f2841af3d60533af31ba7c19a278f16d40c28ceb60e9b206c33df44fddf50dda114f804559728ab0debbaab2e6a6a75b30dee2a9f455b1242add26edf310caf8cd11a16cd1d4d98db1e7594ca5827351dfdd65d101b40b36995c6ddc3f3c0d9e5020c8c3f184847115b79af821f200cd7fd142a83dcc0478b8674891239e003480091a16e3f7cb006d35503626dacfc691e64a15f368601affab3d7544ac8f88d491c89589b3ee30d5ece8004605f439e83ad8e0100cb9452477229c60a512de919071f6e2fbf268133239b50367a6b0efa8701812ca6aa25df542edf8c1587cd40c6c3947f43e59b52b85ce4a850376a12dc32d747bdc8f60f1fee9a1a2239cd3ad5dba247a1ff010b0ed56a970e438e8308d29ade283512ba0dbaa516f0dc26b494087a8f323dfdb27ea3ff09991250a8cc6c89fb383746dc3891a4f8f6bab964ce593cb96c9b47b971c867b14c2b669dadb71ad9c6f095ba4a42950446e14afac00dc45dbc2ef199ed8e44f8b0df72986b4974138bbd4a52c21a00d9e5d1f6a613e6606526342f92484003cc056c0fcaa746d47b7cd0220d6afb0e24cca68fe076528b064cc0465e108cfe18c29e96293e883042275be39108666c6f2e4b209eeccbd8053e095c8fcfdeab518043ea3a10eb87422f024cd08da258dbbd7ba5c474e0df5994e9805c2c351a65d2095d8ff0abb416e9e007fef9318e924129bba580793d4ff04596e061d2d7eeeaf7e78dbf312e2a777366b25726a9696eea9ea65d67abb5568537b100ab542e90a247d3c30d7bdb56ed7ac84d9f2983698965034bae3627b902030d4dfe51368e98f53ae57adea90df63cbbc727efe9d27783cfac332f9f2a4e5f3f71ea88606bb2564f0b5b03735f366998c0fb65b13bd638822d27ef54cc909215fdc40e23e6f2084eeb0bd0aae8145889ca2f4bfaa07ddb5000a304f1915fbca6b1c27a2e6089a48621a1ccf5f21a09db7f30e625da52b88a1b5e694bd17ed4a75ea2c083a41904bda2982cb812d678394227e14f6ecca142022e5e1d70e0890071a304ab860db43dd28b77c983e58020ffb2238955055ee30d9809f8bd058249a232f4e3c3d7da44b098d70431e4171f5419d9aecbbc3b69a2a9104840729233d939e71c2164d0a2fdcdbc29b79336a97aba41308c3f5380741b01423b5b266e93d37e9566052ae15b08369e630106a0826c7e4135f1bf25901136553bcacd23ab5522305163c3e042ee534e5c2fd95902419820db5593476cdb2c7b530d891e3f0cca2a6941b930b47f0715e795c50be7f019da5d185b153336276dd05bbd0b4bf391585d9eac5b04d54129810d8962d16517ad862688b990a6a7c0f54eb15bbb0f02be44bea0bb5dd634706e7a9e54c229d33126592c0fe8f4820fb9a02d3cd81cf3badcbe91e5c9029e31415b5d5a8c3cc8c188150f28aebf27f43b49f42c2cee0c0c243e19bf207641c34ec1b16162b4b2793f39e2f6f2aaf14c38818905e914716955fc95064930bab293eb24d2962e7871953a2a015e6482da92fe54f71eb07136fe617f9ce44a378840ca50ba45bb5462449e8a682f1e57da15775cacf36f73218eba3884551971ea1ff9c2af707d388f4195431ec6c5203ca08ecaef0a5098034d925dcf47467d42cbb821465adda7be0cb76d9f83e0c3a87326a53cf91b30fdaebde11f9647356c444d0baccd495c315f2be44e77aed1848fb98b0804c791fd8ed17968cc41f423317ca34ab858b304ef6f2cc9a8306e59e185708d05a8e822225be8d4ba83191cee382fd935a00079e2cee3a648c8033bff83308143256baf32c0fd17b60e60300487b00dc18dee3e70a0c7cc8f959c441a4a8a2418ed88edacdad9bdab6c033546b8371dabbb0a696e83e5b18a9406803b90246271561cce043d74be022505e3f350637ca4f4230046b425d916081bcb563bcd05f84a85904876b288adea78902cac325c75fc900a10c5c9d13068f0c4249c68c23758ab4ba563c4d06f39959490ddf2ff939b99ac30d586c4b5391a62f8e1420bcb16bff8843d748df8877230bfc4216ac257e0e1600d7538e564c0b66042d0fa6dc05a0543ef5d396e70fe6fd5de19afb4ae641009bbb8295188662d277c951fde8087b6ec027c2f952a5167c3546e2528e103ba4ec94697620200dfc8941ac8456ee136f4c60d993ab7c294735f8de09c09858bd901f3f45015c7226ce8b4c7b05b8fdb7545bfe106c0bbdbb63afd5b46da4b9b6328fe4fa72a9a04144c2f1962cde85a323f7225dcbfc5a29d269db35767f20062a735bb6377287799ab062f08c7954bde7ff2ec6cf96bd52fed6a5ac984619f3c50f01c38ae45dad4499fb68e1031b8aa7f4b82f27456753e415f1165f2aaf54bbc349d6720a57bdcaf46f83d8904bfdd4a24466c6f0196b1a255cc67200ffd1893b54421d3194aaae12fcba8fd5fa8ce5b84f86a9c9c2b7d56f9f07b3356324db95dca42ddebbfd96c72a7f8813f7b158f859c65948557056b89f19d48ab762d278145f8c5a0e68ee996a224811fab5bc815def380d9b4a7106ec3306e71bc616cfc5b8f6fdc3e9361448eb0b300a652890b94fc1fc81b9d80514ef91548e65a9ba0c252067b711175971cef44ea42598b479822522e768ca7ba3ede02f32cb02dff888aaca28283394508ef94645e1f7bf6c76980d9c1c454f758afd145f2e7c945a4771565ceaefe70274170ffdd5f5f38a2ddb1f341b1d34fb0403dfc9528ed5d53edafe15f0984c7fcc25a007606b44d6017a295bf83d3d0ce219ff430cc944ba901fe1cdfbc6226e001f7a7ff5aa25b15dd6ec7aabe96d46a606943c714458d80c4f14e32b5dd69c0a4490b114b578ae3cb63b90e61de9a642fbd71f3f9ab2fff44fcaeb78d3aa3b36e2cc2d5081b6eeeb98c4f549797b369648b2b021b3447cf89b63235ef28f80d352b4366b3f54825ba15deaac858f572647546e58436e8c64cb4ac2066b7594f8d6ae700825a19fb30c0d5f2ab8278a0de516f6c65f21a38365de12fdeb2c742bc4e67857203e4b0ae745fd4c66f80da9b5c03608c35db04048d620debfe2a583c6d5e6a946b0875636701a8c0c0ee8e7abab2d6ba78c0b6d8c835f8a7c3893249c272c85e87847e3653d587f2225de4339fd21144f8ddd45a3a8890c476a81b13b72f395ca5b24e9689e52fc53c6250b00170c6dd5b00b35b82a201f89628782093d9d6455a268381365e04a765816f6358d4d372a342059f6b901e86de072a2ea91fd127a35123499cac026f15e4612dc7f7114d5859b4142876c3b8107e8be9f79e19c46ac2c8cbb21c01b2e95ca85fd852986a1827d2f6160d7b6ea4534e6d84c79b52a6c4c69ebad19ce1365c607d045f490a8a29cb0e94d02eaa70bff4340bca9860d389db9dc05fdcbc972eda3d6031c5f6bf0993e8222548b114e05ca76517abf424b9e09110af726bca6bf83696726ae854351f783aabb657949ff7e93c0676a272bdb496da1c70f4efd8c97e12607ef6b1d423489f11e8fdd6e92f56bdfd347bb40f7b121ff2dfbd7f1fbea4e1a0a2e6ea82ca4c4959bafbe251399146f818cc7e08008a2a9b38dfe95f586fb28837876bb5573cb21875d433f261d02494789b18849d5b331551dbb57e4a3cff5818fb7ba7cab44e1a4f42347e6d699b0535057140e1c565e0abf385ad263d61b49e3fd8613fa24c424d27d8a06c925401673b45c3258ddb207da6e954a93bf485c0e6f4b63145609cd54b88c223ccfc1785861f2929a3cda462fc067e0eacad4f95647b9311bdba2d0d8d73747907679c274a8e6c3573bc94ee61639fdb874de2599abf0af3055705b13863cc633e7b21e7d6340518269d0a51c9ebc7477e9cc2aa303b152fce1617c2c5509d4ac023d2fdaa05a29fa415abb244140a87247d46f82fcb563fd44a3247ac5ec893dff908a2c630be725f03c99d24d19a9239c91700309abb3d982f1edaca6b683b140aeab7110f0d5b668f6da6224755fdb84db348fc47e5a75e0e360ed4c94fe4effd1fbf05b3083cd927bb932a80e8eb7c153f07c8a5753a8011725a93a4f514a854bf555a2090e6bb008aae0b4755df20dbead8b6046be886dbef05f38e95b8b5e3f9220f2acc1bcefb515b34e8bdd0c8e48fc9835a323ede4a59099701be98fef4c44f2a8c51f57a30db6cae0e2f878c51e089e1404aa5e9c13de110982354352de33b4c153a095680083773c1186fa2c0f642a71cf56b8fc6394fe93a9a8939a865d3d309c2ac0210dc42987c6a6c7a5eaab03029a281f956b0ced7738f383a04a05780101804a2e8ac9336d9cc8c020098cb823af74aa3f7deb2f401084a0c50922633d629e900c321071e19b79a9801d4680c6aaaec5c4563a2c6e235b9e3b942367385e19517564be11e408bea8948c0ae1158b5543587fa4d090ebadeafdd1b1e99aa50b99d0dc5f6e9ed9ab3247abb8e81b33a3238e8a97f053558314ba8887359a81856313948ef471e5d0ff3a2e7a5dd30f489213d95c263513fcb174e08ca5c0eeaaa18d7c90c992a183e76044f51d988bd7a9c7bd48321f4a8439a3164412752078e740f63d02ecd38f290db995978ccda1031835758a1c9d9713dcb5dffa8a8ff9c2bada3a7480b76570f50d7016a1fb335b482f82e1d0ab09cdfb84f009c77bcebfc8757d5498e3d6e8e41b84e2f1c0d666cecfb6b43a8a245156b52f0c3c0e0b023febab92c5f669ce5b4e9d8abaf182a0e58514876700b403d14ad2eca456e73185e536a8b2ad49f150a0be24da251c4813ee4f518f49c00bb35882c58ab64cf15d3a99d478430db02dc5cbb6496f7697f5f628415f1d8cb4c43a8b9788e4085254b28b82aee5090a5708fba060734c899f336e96fc382318f2b4a44f1828ec03fd31b7471103c87d13ebf2f5f9c6cfcd6aaa92bd83c6c1d17180a62850907207d482c5bf883310cbc039b173e45bed0742931896ba90585f685cc3dda8604477f0c615f893b1a51ab801400e42e0012291b9c5475d1d05f2028a5171c3b916f87cd98758531e8c3152be6d99bc15469e8ffa79826363017ecbd2fed0c3954fdddcd1614c692e2226503487abd706da76d85043d7ae62d87fb1b22285c90c9d2658aa23aabd62c2a19632b21aafab8f87de9dd78d030401d61d0e0717c017e4c9034b0c88249b44019ba521fc38498eae10325da06ab0bf2b0a54a58244ab0a0f5accf8bdd7cb7252cf51039e02b9673d45c8ea9cb09f68951aa534806996acd0330cd1b7780b95a05bc99758bcdf13ba94560f25f7f880e2dc76e882efec2b93c1dff16b59340799214e0b1f34b58eb06e2166a07a26175d7bad7ce8bc633a922656a400cb1ae556ec13ba5c90c01f7a3995825233db3e117c7b5556c76095e46a620d36ebb63f303cddc46ce454236a6b81b96854804c0994f27e4a0ef9566e8409f9993c7db2737f4cb589a70d8b1c2f2f26870e65cae6169765ed9bf607c7257c005ba671a00d328411960bf1955ba1c2d1dd776650ddfe9aba72f77438f279fd7c116a6ce17dd68273306ad3325c0b88c3ceda3a07763d9c5d133ddd9a6e48628b02c4488e978d15914ea1704bcab88d6b523db15c4a182ea78bc09715cd9893980c0e813ac5f820aa307ad0d6e51138c88f092c5a958aa04964a421593b2c3621190c817b11ceb088f65d02cdb1fe07881b826719154c46379751d171a34d9fe741b56b83b59a9fa58e0f26207ba68cce6ac54e8f4da84a0cb914baf6eeb1edf0cfe0ee601b1fa077f7c626943d7e501e20a6dfd6ab3bd8ab89142819096efea790d23b141991ba7bf1c067701c2cfa0ba20c75a32d01af89ca4509b781970b359b7731a91aba2374a10bb012336217d5418752785130768d028d07426f0518b7e65801fcc7da955eeaf3329050e4271ac27425d1530e9f281afb2ab6555531de9cd6a8a342a2a802205115db1812ed31d75a35ee8b112a7a184de69f4a1ab779ce2d558a8886fe8dbf08480e3a1b60cd5b8a8b17127c75d6272d9f5a141d7a52b241fb64c0917bb02f7c0d81acb607b7c054ae32ed77b1498fed3ec2d565a142e0e12dc1338de0a38cc8ec10d9e29fb1617a315029b55e271dc8d9406e5c8719e51b54d45cb2f66db06ae5196ad46676b71abec620d40bad2ecb0883e3f08bd1e5fc43a17a522c699784582ce9f33d674659880183b0cb7fee56930e62c49173c87407f1856d9ad94420521857a8445d8e41f0931f81dcc30b934a8848d1afc47b387ffb9ac8d2201d7d88ae0d7b4e0e709426ea685603d10851ff95cf562824dc380a5cbc558e5e466f0450a510de1631db533a127a0b12c2e031e2f2f52c6202ea120f971c768333294f10fdc11b0ac8350971d2b40576aec716c216bc91530beb5eab61a58c98821cd8395e4b6fe5bcefce826c85e0da278297ef566c2b2007009f72d96264ef664b482ce73bf05ac1351e643b7c334a74bac0f40df02b18230f993b4684216ef8e8952b25ce90ad6080e612a8bec8c9a9462e305d67309a84013f2a1bb80e38471c0a7f9005dbf31181ad8836dd71cfea8e504be8ab9b75c49f1752fc6a2178e8c8e9770dbc2342e796be5644c662076af425358dad93432f217ac2b1a76847e36e5b26c0f084ca8347799bd8c1f69db1ff6bf8377c49e2b98513ff11f2bd04d8e00557355cd6045e933eea799dcb804aebebaedf7f0d5502e4dd7259ba170585cfdd82cf571c70e0cbab187c41f699e00094188f47ef75b79d6af8aadbe10fb9b1a544e6058d83cfc3fb50c863093d1ac62850919afac705805786e2636ae51d0b14629a3fa5acf80a1321b539753f1bf8beaf84f2111dc50596a8b719939465c18b01a76abdf9d2926695d73cf45ec7c577d3dc58d3e8de453161bb1ae6c10108ba28d443869e2a2e61017ded30ff00a455015658e37d66f4601f826b388559e016d362e2e9ad927b269491972552349bcc217805e2e30e6d1fef6b84eedc18dce1560220c48c5cbfab8f4da917e15403b3e9dff94cebd6a4051cc5283f1f37fd8d8987ce4c71b5113f2e8081be470b4567cdacd6072207964646fa0b35722d259765c89f37005b8f7e9e7af447eeb796b5e59a7c8f4a63f9136ded3d266c8063625104bebd1d6742caeaa550f3a3b77b2d8a4ce24080046e5f2b2e93d35deb194b370b6ed14141da38bd3f1cdb2569ceb063dea5d9958b5f20a44e78c6848ac1a2c34c77766218a44ca410f0ae31381c322563d63602065f43c63df1721ba2b6e4da113b0b54ee5af8ea61ab251154af68b6967d444000cbe20c919f1ad9afedb3e9e1fdd0793f0ae11be578a57c73d279ec3aad1f109395d55534edb07f1be7129c8613ddfb02d974816da6f8e1b66f92e5e08f9b483d415010b31c84525b27ee00beba4f12bb633bbeffc21f089bdb9210a44e3399227ffb34583182997657026f6612f89c85aacb27fafd92b4335bd66afed6a31b038743cc912b932c6487f7d9e038116cbfdb259d4a0d2437b748a7e1e606c65ceb872c3a5584767d17db45e91fe9b07260ba230c2c40619b22a92f91995231eb019941424cd53399d96839451e134b9773f3d8bcdcc3f06e83458a5a0f01f270d42b4d00fa0e69b30b5bb52666e660d70543f6388a79a9a4606d10d3467615385574aa8b07b9b0486c54ebf3593792d49f1408b12c652c2e981db68d9aec07d8f710027f0a53cafb419c96453f5a2188cb7307ff68af6e56fe9c2038c94168c816ce4af15eda7380122486d060895119f3a21de520a4472091c12c5c54f59ad15472161a85800c6ff639b1cc098cf082e980d2631f30ddf77536524a720d3766ed3f4318ee086d0ef42cd66a32fc7c99b4c667e915f110f7bbff05ae9f000c3d0a18b93b738381f85f9d56b0ae49d76fc97e6381b38d6a15661b816d5935237d262d688b4528b5b204a89a4eab826ecac202187e266b7bf0b0d71398855996f8c3ba3d2ce2d21cd99af2c6f6d12c668394d5cf826b31f7a622ed617280b2539c4c35f3fd9c0ca11cd2c083cdee47a5aae141d19bc1be5ff98e17a34bef1c4efbe6d3c2205f0844ae86c9c02bf493a3c78036b90d7463f65685986e1070daccee9b853fc876937b68cbd055ac62d8fd70f7a64a36cb130733f4c9e54beb4519a00f7eed35c30072fdb5c1b28087f608ecdcf6c275c4ad09179cd8a2906e23551ca027165930c00c0950121038432dcc04d4231517aa8095d487bc740ba4d97af534a1a93ebc3fd8a30237c18768375c4e10980dd1454292a190e612772f7a7a632a66a139bba921f67aaf75b62a364a3eea4297a24ebcebbd8b65726b994234db8c74313ef4db43c0ec3b8b46d3b7e76a355475751f51b2603567fb6e9cd16537b85b99b88c643d53569be8ecf5b46271d288f8edf231efa4bd790e0be0168063e399d1014f297621ab7aeb14d679ddc92bc0dded2bcec286fd3d5aa9d3636ee0b25c849d02f2a63623a27e2e7467397a3f05bd01a55107a4185c4cca004c228c0f905e7e2f939c98192f7ebe1343b3e9d9f2dd6933c26a505ba78bd2e41d389d22abe7c99e0c3abf901563a1fa32de144d0b5491d584bf71c094a8321a6212c28232e9d4e89b06282c0b64fffc4459d996ce35f38236ec449dee19e26c94ef52c6b4e4482919ea694162a9c1c2e850e048fbf201b4a4df764890cc6e815309bcee496c1daec89dbd62f082c8a202dbc7fd0a7046a7554725161c7b78a40036d4be46f40eea149c52dd5b440845d653664e5a5c04b5459c5a2a9111641fb87f2a3140ec58c0e7e1e20460cc4eb4e2d9efe603bcf41e7a07ceebb9d809e59532d450afecc514da3fd6f1cdf16919cacd52491808c8b79439090e3eb8eb03e68848ff58929689862ea5b84be053b5b8f8c462550c18020d1c7250d681f7e703d93dbe5589fce6e26f38873fece57d3c05286220d22718d4f28abcd69b797d11976944dd44c1d95d19424ca7190183e14b7148882418c50d7eda529de5a3ff33a3c26ecf8708c29fe785fd4b622627489f02833ecc42e4e0101cf84781f6306d5c4f51f45e9cd529f357ff7a4617510b1fea8a85088ae019b8dbdc3c50f88454674e013afaebf0fd3c489758255e32646256cb13e6b0156e83293c62dd01ee76a280b9604e4eaddec52b0448c42eed8c54a010d4a1c44b267674780567cb31bcf636e2860d294a0bdea7b0cba7c5d29f88f77c5b9e94ea136ba4d94ba254d609eb659e78feb071e5d3ead02c4febe85c34e01368c8832857e2faff087e599064be6a1110209bbffbb7f2daaf76a0ef6e5315787291f8667eb5d58376e7c062bca17b9836e334fe70c171d25908ecebf2af2da6fc332cbcc5f1c452814c917ab162a0c2ba9f6168048000a10ef7ef31573c3a8b3da00ab78f4dbf592e72f306ee39b79e51ceacb61873dae2b115592792f41a4b6e7e4c365f1d26b9eb54b187270528eaf07ae41a5dbfb287c78f62ad8a0d21d06b53f9c18e35cdc5e13a316536b0a95a5d61c9ebe97ed6f18982468195c762a5894a468bfa5051fef1c5d0c404f448ee2dec17366164ec16fafbec94ed4828345d8c69ca0e14d3bdcd74112dc3c9620dfb38f726bbd7e9e3298176d8949f14bee1816028e230059a5156ba884cc2385d8d4addd56515551dd95bcea47c517d0b57ddea723321c4649a5f8961071a19b4899958726d25617e4f9c8414f3538a26f0aea0761400e451e02b2a786c6ebffac84bd0e0e67fe2f1c9af3232248bbb4c0dc4cadfea33ecbaee15287e1170d90858b4bed8c3a49154929d7186b56f6a38e409382cd031873fc928945461f7395266f64f628ef3bed0a4f6791cfcadc4716de80ed10faf860d32738153a6304e810b149206be3f4a52359571a417d4290067f6b3db79faa3d60be8a92bc36c532c586131dc4743fa9495e5642e63367f847646e53999db3b931fb1bb73e6f4253884cb9a1d3fa23b51d91ce26e66e68faf0e3f71da129cca65cf880f114ebc248771b833c207c42741611be4cf690e6e76fe98ea444b730a873f3b7e5cc4703efb78d4369c7716e368b8cd7b7447dacc44ceab4281cc75794943b68024fdffa39da848fa3243f08eec301429e1af92d812d1f9bfc9a230ead069b08747085a165f23e74de61cc6d8b2c358d49cab6a6d948642e9f7209df3e8afefa6090134bdded7d123b3a9aba227645a8e5d23f76680e34ba17e1aa86b20788df1b4f20db233cd51e0f43b532b71efbbedba4fa7026682cad493e670ba9e0219c062edfa1317a383333d3d66afceb42505a7b187c2c1aebad3da07bd469c5da3c2e91d8222c7ce138a09e3f7ab16efeb27e314b3256cfba1129ff6303ab7b0066ebe740d65d11fa18c8a0a9b9d07b0293341bd1dea74928b0f7dc5b12d063af5d69a0907d8ed3b11e280fe5242ced362cd2ae16fe53d919f18be3fc54565a874247551cdadceeaa7128eead45125a41a4c6bdd57cee39f8655b5079ed7548ef57d7184f8b24d45c458c1e32e62c8192e12ff92663f75c1090cb9ce0672009755ebe68a1bb7442e069f880bcfe8dc49efedb7d093a42ee5baffb82be8b179955969ed16a866acf6027d4d5fee26ffb420408bd1764ceeddbef71c8f2703df5dc7254136d9a1210a7b88a2b25c3f6f9f808f6f52fb07e6711ed33a1a41279a3999fa829acce9daa87d504eb9368daca2b0c665068116a3fc00f9ed0e60eb976c3f13c563adffa63a11dd9711f47fb4ec457c90a5473de3321cc5fdcda40fd0cfb81c47e51e66e2e3a93356c6c939b799f871c20995e7244e63267f9074424b3908c73c131f449cf0520ec671ce840f904fa81407719e33e903c91352c2c1395049cc621f8dd2ffa362637b86d8459d4e667166199973354c9353fd7dcedea608347db2649fabfc20ac99b06d3e10edf2ea49878522e96c3592ce13fd60085a41dbe5d02a2acfa9323111e1679f3aa24359c0889f90acac385ce18b5691b14834c87890ae038e79d36949a9c60d2cd3190136bdb2d0306d6af2d4f0171104e9188b0c7513e01e7e8b9363f32999fe51da560b0977884de2bd6493f129fd427ee9a1f232dda09d9d0a0dc47d8d2eab141b35952dac590cae870899ea172b57bc3785fe33de8cb7bb9d0a680e3d587d573743a41e9f9724188511ecbe12c480bdd68575f2904646f6b107e7eab03e6422e85ef6a7c103299ff2b9e7f792bcbc712936c20ce78578d872d062b0d86e4bf45aec7fe77de0546e7897de3870f1a4861b0df07b037dc2c07be1010d402577bb96c686d90e6ddfd6c3c7f62e11c9f5755216e45892b2548ef523bc2bfea9cfd8e170ad97ba560285e93816af74abf64bfd5a6d0fc7e2898c9bfcadfd8336ea70c4ea84581d17bb8f4bf818dfdc5110e5cc3e695bbd8c46b9d11892a24d3e2f3a46e154dd76f96711c199109bf5932e60c96e020c9a915b8ed332a69ea681def7082c41fb3d071ade4387af4d3f39dad7f8c3835a5efcc3cd4b811428ec0c810a9475666014f43bf381cd495337f392d8d7159d416c76d5ce4cbcba4a6784889274de935d4e70d0a399265050b5990329a8d90c050627696dec1274013c1ccdf4d9f680df8cfdee0ce4cdc43e38e92514bacbb8e17818f96b72d29c01cccf3ae901cc0fd63292232824cdc4cb03777fac225d1ab8fb599919c07cca42497cbc60d466ab96f6900798bf69cd935b4e7ea415491e1d313e7aca5af249d24dab3add2bb4933afdd1fca65f32c522f0352d97e4ee28f153390445dd081a7549ffbc319cfef3e3b3e0664aa376d12e9fbcdad28ae07ac1fe88c05f75073474ab1f3f6740dcca787b08c83a5ac8760894b141d26d05d05276f593f508102bf56fc0faeb3a08f311a40418871eb4264ea27c6dc2bf4b847a62db541df9b876c61d32b69c3a325f011960d291b74c3e93e186ed8f471090e76ba63c4f81bf63ffe7b4a168b135bae257bceaeb7f71a724c4da88a4bf01a46edf5e1feeb2f4e8a70eb4ef7873c1cfa81b774730c67af9acf1f661b6d1af16394404261cfc5c1be1f8ba4417f73f7536725c7f8e5aa1fbed70ecf91eed7c375617f8cfec6e97eca18341a01287c25fa49455dd4a3f7ce0165ec7bf9751c34bd1d8928491bba706f2ab2079759cd8f1d12b70dec16398306c2bb37491ec5377a1c379faa817badf0f61cfebd54ef6b1bae1e986be78c91e2a18052a7528fc472a4965bc76e507e9d14dfd7919053895ed214b4864fcd463b29af86cb5aef61aa44b55cc01fe844ddfa7ddc2bfd188fe1fb814b77a6616b1debc7934effbe5b42c2f25b7e12f321f28fe776b965a401752d93fb264ab81e272c9b83f6f36ea70d383bd857be8601071d887b63229c5380abba249128cbb31cc8e2766d182b080d2e464eccfa52055331b5cae30dcb8e95b14542658a316387790e50868f4eb69dc3290901ee1608067ff58587eb92e35eb471a8ab87c81e0a15e2a631264262afaaca72e3242b0079958f12a01f0a7280eb8af5cf201102ceb909a520b3ad8f92e933d82560859dcfa2a036069c3e41d7e3c99a29947705d82e9b9cb00056f07907738d1ac259f1f9d2aac1f67729694247ffca6987644eb11ca583d8a1371a5038921056fdbf25b2c42071d2f0983d3c3d987e4e233144815076b8c6af0ded6fc2a513289a47cd1d554aa7758c7fff089fa6f56da14b7b2e358288eb6c28d98bc4ab27f6d634c8f271c8a098a34542b2e3e59bcd365851604b55805a5b09ed2d7636ba3507c039967aba755eb7fcc301d152313044fd1a891da04ffa0445219b8e1ae525c473aa86961c497aace11bfacdf8299df314e157443c0d1a8507cde63f381161ec7bfb0b002ffc241af156658ee264cd79fafbd5581996f70ffa47ddc1854dbfbfa050f78e8d78841a96270a32ff8365a6ab302291d7e869dd3b1d6c7601c56184a85976cdfc18a1fd702b1a38723521b4ff079c34a1823d2163084d5b41ce5e9ec17a92fe5a89d8476d605f48cc491d4da87009f1420c347ff414346cb5a190ec3c54d12781002506e07aeb90f9cf56f86d1fd4c4c5c9c911081e445f72f0fdfc04e0a9411e99f855491a1bed6b092be56953703ae45432918cb9515cdef481b24f31f2982a2bf7ca4702d51a1dca1cc5831dea9dab25bf797f7e0d80e413d228528b42432ab35d53c35cf97d4fa3462847e7463e5c3f22ecd7a5f0fcea9f3f22c62baa1099aa9e5ca13d180c6dc071770580492026d9e4911ca77fbdee16d2f27996cd4106b570818c27aaad8a8d307bac59b26f9b157ad7d38a4c0d28b0aa3a237904a149516fec5507f3d623dbd677df2da9d6e6fe1e71a46800084b18681db72ab1a352ad64d44ba4973dc0dfacb80d84c30acd43ba216c85c9834fdbe5a2d57207de396ee817e5d4b93a4dd281412059efea767109c4b16e674ad94ae16380ca6b1129d4e41f2b6415d3d11ffdd1845f6c50231e09ed3177aaa518ac4a4de7be3797dd68e7a496dbe62e535a9704b565629c21754cfb2f16f02f5a870e5d20768843027a40f00d6b008bc9f8c513f586d14c4541154309aa8f60c17535831c8a80a5acae4b5a4e22bdfe43ae4b51ee1341ad34aa51433fd2c95183449fa6b7e84ef2b81ce3d9e91b46e53441c76024f04818b52630c8976bd200109b11a559ead04e67a82810a7692d5a072cb0602fbb3266abdec54fcb392e295d4463d77a10a954ce81c7fb08481ee19f91432ad091215171cea58d45e49bbcbd786bdd23147338de47ae81de9c0400e14b00d99608235c2669de26ab6a31bc9c40688042b654b8590eb4e2844d2d464d83d8e2ea635b20e792b98270b6e1e43fd144e6674aa49405693d3a439165469c820e04449a08d7bb0076af2e2f6d4be797767040fa60b0bb071084176cbb5bb3b9bb58ed8251108c4c3d57d623a04901d66306d92fa12253f76197ef1aa4b147edb04d574f94189cd3be768e5e8098300d5ed05f59050fa6ef4def15504e82e03af3aa0c87ac7030bdf2b5a1f3c76c3c1b8294b7efb2f0427cd6f074bf780c0a720d6ca5363742697b134ab74888dc92751950570d5a439ddc2b4219a3b20dc08bca8040eae04660f591058c86ad201680df19b76216b9ea56ee87707924b8f027d0593c20e11acf49cb6d9ea0ac2cb7d9529e5997ee8ece122437a07e3fd2f07666460177e884ed49d311b8e2f359f92e57b78d4ef600ce946885a238410b2e5de019c0e870e5610e30d4728f8eb2c1138d599790afe8ebf0cc33d9633dd59a11762685a2880adb5e60f42b8ebb3cfae7fc21405a3b27e48b07608f86ae6f1bbdd876b7ff69e49bbae6d0ad7ead50fd7be0a36b41fadb59dc964c47ddba180d5219050bfbef0e012dac5c2eaccbaeaab15edaa67b1ca59b5a2ab52bda2cd6a8512ca4ca33e22fa95cf29fdca3674d7990dc3b2a4c79d9994fa502fb1301b9c724ddad067bd0fac003158020b86e0451318909d1ad8e2082a982801133f90d5fb007957aa6843aa351d3cdecee702b5a89da11629a8525b6441291d6fb26eed77e6c4843499a51a32647066b6f0983c1ca638de92165e64726ac43c27019e165ee705c6bc6358435a99342f657df66a5bfc0bbfc5c7b3b3c5165e023c2dbcdb169e0df7d4e89ab01556d42694f5d6da1c09f0b4f07a7424c0d3c2cbe1a301b15569506a858ded28a59f940f099f3703fc404a69682ba5d4e2ec927579c05a6bedf7c988aa98511c439b8d0925393201664596e3a76495561f13482b90ba3197982e9531945299948cb14e21ce8034236be6eb6a28edaced9db5d6da9ad1d6caaeebba8eb5844ac3a65b346e5af42d10a90ecc3bbd778bb4ab02f5594d189c5066fa05aeb7a15fd61442d2af3693bc9f9ef964459f3cbaee40427dd6ab50f2c0423c4e1e40fdf25e2fd47515775d9f3a61502007504988c04909a31b184a48d0e204961b00a1b32861c030a5123f92abd3e3e897501dfbf4b42faf0e3ded61ee494ffbd5bda1a73d7959f4b41fef0c3dedc55bd2d35e75477adaa76e483b7a7e3af8fb0d6ba61c3dafebcca4181e313d9a8006658417554c6082121c91a3312d71399a81ac9ed687655b18d4dbd51dd4b65faf3aef03c3d49822c20dd3abaec0f42a25da432f8ee44cb922893a22849e0dd353240f89e84a87bd5a1943129544902942993d253c43338324aaa1a9a9a999912153531353d6d4d4c0d4acc89a9a9a14c22088224c598580443e6c2db5d6ce307dd9819e6de6a954bac9d18bb64aa0b46d574270c2acb11c82ad9440993b1872c80d96212323a6942143860c1936a92f9eb0f7dd48664f3fb3c81112f5aa2351cd22b2b48cf4eb8bfa35bc91cc2c126fcd29a22783545259eae0bd15f955ea45f5a914684e016fbf5aad562569f6c72b5e15f68bbab72d1a2f9a53fae3ab7d51bd85fa4c27d6e5dd3e7c18bebe28fcc29a8f596bd6e5b539c5f35eab3965e8de55e5bd293e50040758a0369b3dc9c8a88cca64a428656454567f040d7401811510d0801740905199f7213b5e2a9385ef4fc95264c3db7baf66ca9c5271d6e5bdbefbd69df55e9baccbf33c1b1a75832a686a8feb6b12cab064d1b046c0fb6eb37755e8bcaeeb3aaf7a1dae754ba300a66bd8d1d7a7593c1fdabb9d0c14659fe2aeab8c924da46dd728cc4a4f4f01b88978b88974b327df9fe8f00e8585bb29b85f9b48cf707d359f7c8712e24a7bbc7b990aeed7182cfeec10dd5da5ddcab3b5eb6a79c3dab0bf8de7ee3a7d9eddf6c673d7d9ce763685c3e02e07772bdcd5c01d893b1cdc8db8bbc19d88bb16ee54b8a381bb14ee6c704703e2ae06771fee66e0cec31d0deeba19dc59dcc9c0dd9bc6c07731b893c13416be7b496336efcc9cc2cf8b29dcd3e6e0eeef5777de76295c8abb3bce6db26e5c1b77a6cf535c0964bdf2e8ab550dea971968c6de0a617b1b5257675a20201d14ccfb9c1a38372d1a36ac9a19345de386e005ae65f7191932dd63ba97dd61baafba936377b17baaebba7fd7ebec8d9a4a1abbc644a80c3020830c8c3210a98e780aa3b8872df4595fa4cf7a18b6e07ab15cd2a778975893873eeb99743548e5dddaa7becb64890fca8f87ebb02a25fdca43a8ee4775582121063168064125e5d11eebe1c6a1d9024f714b48c5a0d9d11e6b22e91357524887bd579248873d5389e5610913d16432626bb688b4357f18b1923e5fa833dce6185230703d9286c1d08285010175398404802083183c610a21e840567f5ad80c4fb0000a1ba4008ba324c8ea4b0b33c2e24b174948d8028c309a20ab4248620b0a3860042582a0029977a3de37627b6b662642df0bd6076bb688d4f0e0bb874c843cdcf5d95f39d3e71865bf91f86e1e2dc32ba9418d04ee7b483416c3fd01542a06a54cf83e646129b36479f62a13ec30ec937edf052f7eb64fc9c2ca97df656e24c3a17ccc8b602e03f3cf2c2a1ff31823d9eae4fbb3f5d9ffaef8befacace884ace5062959914f3ef7e56d82cecabc57cb6afd667ffabe1fe37eb7fb4be07d5f7a2fae53dcccb77640ce3551747d5079ec988c5212aee4473fc77b30ffeae5221c1df77fc7dfc6ec6814792e8eb5aaca990240a4d21a02394d50865f6a070df83ea57172fea2349a26f8c7ac10beedb7e852a337d0abf9bd8aa0ae04aa0a4015c96272272e8960d48004761d96e099bb5e050536adf9dde1f80b00fe17b5652dbb09409bebbdec39bcf8ca3bbbda079a919d2686c76e402118b0e4a51801b08365b45684f1f670aa328404116b87e2933ed02858829d6565bb70491ea3001f2b8a0f4523a6681339311e3e8b3be4d9d0694e5ed0d6ffad566c76ad347cb0cdbd0c276bc571ed7456fbc293b2750fa24a297386159d705eaed2bb8f3aab4055b1e0f1c47fa8177f5bdbb6eed422b73ed825de74f93962e33b78ac4e08a5b550bedb1f7b08df23c25896a54973eedcfe7eeeeddaf5e8e5d87c19d837b85bb066e12370eee1bdc22ee166e156e1ab86d703fa4b1f0ac67d6cc33a8f35c83fbc36de379066e0ff7334d2cf59ec16d713fcbc0fda631d55b0677bfd298f87e4963376f33bb5cb77ab929dcf5dae0910165a65a84ac709024b4607bb17649914494e0eeb54b54bf3c2f2049e4b92ec55f9b15eabc950b12396c9fc20d1b3a2898f73935706e5a346c58bd846bd9afe9cfe8d3f467fa32fa32fd9812a6bfea93fdb12ff655fd543f6c15f00057250b5b625df66158962cd67f9e3eb92ad15c55a2312a6343e202d81ed11e18b6ef7c40c081dcb85e80c6bcdb2fb1301cf5160485552ca8cb1e0868c70e97eb5e33dbd8c4e05c9532cd1969190eb32a59977ded7254562f35aacf3ea2ccd4a90bb6b7a17fd02ee3b93d921a344489fbf63af09ce799ea00db9dfbece0e401b697ed3cd31eb0c2288708719b493bcff407423b6608046ccf435e6a02a622609e6b23dbb954268ee3cea5329e9ba52f961ed0028bed010a60d18e5160191e58006ef575e017aeb7d566cdc2822c2080290bb2f8028b5f16a62d1d95c586db84d2b866a13df6325f603135f61d711a9879005691d51cc7beaada0ddb2c46714557ab7a05edb12771bd94051d38028babd1bbd7b927cfc5d12efb9d4bfba7be1750fe6c1895d9535389c63e6c5f9bfaf59de776cbce735dc2f6621923634719dd38e38d34df388384a1c6ab4a469414ef9cc72ea131fb03d019a54ba046c05689e7ee5c98b15ff1079a6755ead715371aa3d8be1fd6201a46adbd5b2f407bec6148211fee7e0c60eb050c04d8560e60fb2689aa12edb1af5fcabe12595950da53ec99f59f79de6ad7b314fac608224da1a44e5884f98e858adf15d2e125b4c706018f7dce601b688f3df568ad765504fdad2c28e92bb66f5cad1132fab006dda8617a4b9d70ed867c9852332300534c04a6098f7ddab6ef3739a40aa9d8a3dd2bcfa421eda1519f505292c86640a4de773310fef289471c78f4c1e3f3f1f8ccaa2f19f41c4f2d6cf55ce2f13126cb5ad6d932e88b879e3603e1f0e2591676daf37478bc1dbfbaa399bf57d2a4b4274c7da348c2308e63ea29d13cfb0caf0afbedfd7a2613f205192b52f004ee1053d2476caf0421eb372cd67fc362dddc36ebe5d88077f6e9d3f4ca238acb9267ba2645ad7d8103100849b4eb28384cbba8a47c48f8ac08c4ee5b8f6bd22dac33cb76d5ae3028fbd5309d3ce25c0ab1b8cd53a0cc3c703d8f7ee510a8eb49afce6d50d6ebf4cbbe56ca01280ca56f0053bbc2c4ee9ec0043a1b8cadd2aee255796955544b204a15652b9f74301e8c0dc66661de9576590f4b47a0ccaa285594ca4bc35456802a9b04ca279d0ddb08944fba24f1350b134d30c4d6ae3295f01d0e8fedc78689b72d5edaae0ec33749e441a0ac0f431ae699172ceecc24f1f58ea185a9a2b22aca68045930b1c45481a513f42088131e0001135734598089cf48acb6b26229c3571c9a7900ddcd0b8f1b02650dc6262a554c039c55362c707d77a3b1e36a05ce23ae3012c0f659c4f5291a040301aac3beaa5e53304a7d3616181b8c0d7bb47ed13213af86c37bb66ea45eed7a2117573385a33df62a241ace612d06da03dc79355cff3dbcb9e2f0956c2aca1cde63e2d5700763058c0dc686ed431c8efd4addbe5ba2b1637b189b85d5198c52bf1e5066af866d6e2cb000088cb8428b0a64f69e92850900a90a1cc840055834a1019951084cf0846d8c1a0f24d00399bd87646136d460065acc96d0850460c8ec45dba367c7af1bc16f4c913080a40b26a832536017e561c1f66247e9099a49e2bd2b350b0b4593b6ad614b8a3395973e6daba2b085b9852d3304314b9123fbfe38254b910803031ee62ab368f5d19c4257174fb6283e4bd1cced6b7dbfa8bebb88e6a339653c8df972c604df5d98977775f24ec95294a565a43bcc8b3ad5556651f99539458975812f5f5f4dd19c327498c39853b214c1dc9a538a58177818935a90a6c45bd3c7bac04f214fad2b8c29632ec347c6a94ce654067ec89297cac44b65dda5b212bc8c3b254b1119734a95c1bc7ccc2dc2c965a4284d18b3ceac0b042f8354c4942c00a032af338bf04e65b9ce30b8ba54461e1539fa8e449579b89ba5a8deac0b7c7dd79953b2147536eb02df99b829598aeaad39a5be9a536acdbac05b935a1b065f6b5b07d6531978f1529968d4f57784caaad64c3dbc79f50cf3ac7a1e9f4971d5adba2e87ff2ec294e5614a13af54abd555ab8e1cc999f156ac12c57ba24af54f65d23eed2b1eb296c989bb8be1cd1fb69de7f5d891449d996dd799ddd852802325adb5de459024f22caea719a47dd68e76a1b5244c8cccd85058178d39b5309919b68676971629ad8bbe59b735f60d4e8d32e7a26a6ca4b2a43d776c0798230a3ad67ab7b73f1b635fa9d67a3752a0d4e2c6e239d27ed5aecfce6c29c27ea2dbd9e1e1b13c2c4bc3da789eb5b136d6c65a1a3468dcd01c14c75a8a4371280ea55ecd31d27ed52e878e71c70e156650d6772ad441374912b4a75fe42661000e6839dc96853789114916407bfa3f6e9223684fdfc74d72447b72b85536c395e126d17c64471ec34da201c98e1c86178edc853b4488532772872871ea2ddc215866124a57c8140751e07e123e324a77648a0307e07e1227e84f0f790e45370928b3e10e3193cc684f43cdf88ce7108827209c80d80284121057007103220b209610b0829af711c054c37a9fb2cefa0d2924880944d396d206c9a559ea9f3e0d9706675d7dc33fcf1407b332761c92283c0e39a413fa8a1ce2c34482fb719fcc68cf11d671a3b9a24f25dad31fc0cdc7e743260f6e388721b762167cc4783cfb20ed40a23d56d0d868b0b85c392339430f5d24857458e8e18dfd3cf3d4f00c9f4297a6467bfab14b83850669e8d25c81aa4194eab0f1b6416760633c7271308f8efc082a33190e34d0709c10d05700aaa04bfd0a800d0767614772210e2ee338e1be906bc489f6f46fb8469aacab4f748d7cd1673fc835c2d4679f856bc4d6675f768d085d234a7df6f13512802a6c006c30ef1fc97559a6cc1cb6122e6fc395e14986a02b03973efb355c19a0acabff21d3c82d06e591fb644677b40a4597865b22ddfcb825adcfbe8f5b56e933bce511905b1a417b90acab6fc32d6dfdd3ff71938aeea3c81475c65a1e471205dd186b11cafcdbce18bba98b35dc23606ab8683d5b7956a91aee71d6d50fbae7c235b69863ec2187ece0d403708fb208fd082701b828741fbb35b098638c5d1ad892434e9cfa91fb5bffc4ee0daee136396a5707994d66302855ffba52db856f1c9a39003865261d390db7a45957bfe89635ebeac76e0bffb6e33b9e5976c7ed88b9e339a4c1a34ad9f0b06a7842906646c6e775258f6d5b6156941cc59b558fdfbc9cd1588f2e70ae18e8d9f302f75340af37c3fef53029ed0132f3ebdf2fc5af4bf1cfcd218f9bcb15ee930ee752859bbfe3661a25dcf7b999e602b8afe3669a2c703fc7cd63cfcd3448b88fe3e690e7e6efdcdc60b86ea69902f7cf9b696cb89fc2cdac1b373fd338917bcb939acfdc61e43206b97ce60ee349c5b99ce17eebe692c6cdb4085fe413678a8359a645e002f76b6ea645d801eecfb89916212af7964c5fb003dc975103a8dc5b626ea6602c21b3722f6175736fc199b6e029d3221801f7c79b2fdedc60e04c8730c37dd56fb8bff0d6cc4485c3d534bb1fbe1bee4c262a1cb62655026964a40b904427f64d8f86ebeb15a57dedceab4cbc5a17af5651008423af767bd1c64851d6dbd9aceb6e4450504dc600e227dcdd6d6bd812596b3b72c8d7b8c6755de791433cef238774d5863f901c02d384439228ac1f92732bc47688379bc56429bb3e2d3924a4b5edaabb6776b736419baa0977d7755d05ea13a85dfd168898cd660bc09d29d4afead1e82b7cd7bdbb99f50a016129eb330f1e7df6d71e5388a3f3e9deb652ec3cda2b3b000c9aa928dad3ef177458e0ec31611e4042b59a5e67b688d8d71fbc1af6e9b36918d89a1d0e1c0dbb695787edeaab9abc16d4a61ba30dd650a3701bcff5415a4001059dd7b7ce6f3cd7aa739dfb503fd442bf6af80c836b0eae2b5c6be04ae28a83eb0dae2a5c69e09ac2d50683b8d6e0fae13a03d74a836bb5b869cc7b8dc15506d7fabdd6bcb670de20f54341fa2d741d6aa1a260ea98337db60d9d0694d907b74fbf9a360109f52ba3f0aaf3219df75bd0316d7994a59a679f8d8289a39b4a9f16fa050443d6c17d1f6b7a3ae67d4e0d9c9b160d1b56cd0c9a19197526035ccb7a9998fa12a67e45d68fa2aa3e157eb7deaeb7f506031c1aa5505330f8da76f7bd3fcfebbcafab5fa5e207862955155df8aa522128bec3e276739dfe4019fb811f188617044122c07fdf876cd7755d7829df4b2cda91f6789ff53c0f14c391d6b69dd71f387637db775eae6377c5d4d8795df789a1edc2d07eefbe303563c390be53594b555445559476a22802219555c49d19365269474ae98a01624351d66f55a96be535218a63df5cadbdd6daeefe486db54874777fdfe779dff77ddff77ddfbbaed556c05a6b6bada9063db07d64157cbdd43b65f534960c5fda65d5abb57ea8adfd58ced4004496c23e74240e3d93517bd152db85ed23a3e1e9054f69a78cc6925dda65f4b4ebdaf443ed4521b603da0130dd1522d5a132b5b430f02d03fbe0819716ca520faf099e16ca5466f8d18064e155f7288950169e464478f07b102c6cc919402c040cf61e0488ade983f75d0af8128b6d5a994ee57d1dd881d6be9a35d82f5f443b7e20587e4f74fd2521ec6e4c569f7d5a49cddc2a42adabded09e3ad4f48ef68892fe0362aeddb46d63ebadaa9542a43a95c495c439c431880102da43711e5f3188a1053d29ee256c01010d68b14414616c9135494bb24a11bfafbec5dbd730da31f5ef956661e13ff249aa93a6ea539dcac2d4c9d7108627c31741d62af8bb18a6561fea549d599893767dff5629277d660a85bfeffb48f32bc2b2aeb2cfcf2c224bf722ecc5b759442ecd96f7b0a83f9a2dcf2ca27aaa6fcbfb28debe687c5f7ccb88bdf8a2fe784a7657bcd4d61294b4cefafc6e5ddf5397655ddf1265a5f5f91dfc5e8fca5a6b600f78c08b5e9de9aaedae1d4dd5b041fb759e57c31c83eb58d29efa9b7e75b857e0440e4880edbdafad949d0aa7c0990fa7f0978bc09f59d21e0f9b39c441d42f334d9479a6cfcaa23d75e6bf19290ce807610e0ec38359ed6a1ecc84426ee1faef5906ae17bfd71884808b41085b5017c5f51e0ff63c2898d29e6f48f5fecdccccb0700c608c4175501c031846a027c5df3dd06c3dc4e067a6684f3d0bc030428969ad35a40d2bbfe08f0ce8967216a999a270550a9f7065834fd87637db2d2dd5b0fd538ff5b3b5a71c88c3f6d66b9cad59248b0485ed1b673ba8c6355333d9b03d94ad36a8a828dcb75d6db62a55257b1a5fa54e37dc9920499b30a5f5e1d308824fa4fdf23a100471984aa5cc20400c662ab8fb70384369fcddeb2e0758300b724bb89528fb9624d220a83abc5b7b7ba9ea68d3bb0d82aaa36f1b4665f56dbb695fefb5ec7bb7defa6c22b0f7beb83e9b48886de3b211f6ec830031d10dfb24a20fab970331a4dd430aa31ea53456434cad8b7618b258dd1907d3eaddd01e6a5ffdaeb3b69b422c71b70cdc2d7b1df885fbb6ac59694f15c1154b960b30b07d8be008dbd0de11b7bd32b8ed9dc1ddf6aa707725711b75ed1c9020085e6e576c60890815caae0c7359b27a85eb3f0afb280deaaa8e7adb6789295972a0e959373406dac83d98fe607a1c0abb4c752b75794fa5b01723bb850d59977d183ee76003f7872c8ec8f19715d0d5da7dfae47567c4676e5833333737ac9919d6cdcc0d1277804875c453b36524f5f045296a52eb328b545352579946b26a66281da6afe00d4a96859dd6557f9f3087d8070765f8d473883b5b61aa2e2566ca347483f4d9f7c02841f39f892315d2a6afed0944ef54fcea9db1307b7a5aef258d89d80bc2de5916664befd6f7feb36479370b13cd9498baea35f445b308fbbeca2c224b11e153a7b2ef5da73e334925ebda5e56d94c94dd2bb65fcb887dbf28f5f0d6a7c7c525a2fa50b7175e2af36e16e6d9549ecd7be8d9faf4ee79ff59b252b7f5fdb63eb3f5bdc83ef5a27e78fb81adcf34629f3a0ef62913877e6896a0a4b87bf570b6c325885427c4f43d5e128bded8d7068b762cb5a0803746018697f6d89453181ca071e076c52fc6b1c9b1af8c0656e558ef19034361564994f5de3d71d7cafc5b984ae8b4df930865a9d7c8a06cc109b03dad3fb0f5818f6840b2d4930865e093f09185179f4b2dde092aac0619e1ecb5e0046a1775d19fb006fcc1150bdd155699af1ece63f2b4dcbbd19e450d6a338956ca52b7f7b4484aa59ebd0f845ba89e55387bb7ec85800b4f04d54c523d7b23503d7b5d5c5b6355f8aad4908abb7a9dd3f3bc9497c262555d1b596a4c65f12e7a75f4da8e5ecbf3ccf6a0bc7858d8b08705539419184ae985a3971a3dd55859d08107e0ee33f8bb288ede38d6dbddbb1f49576d7adfb51755a355616b0f647fe2fc13dba76e55b7e393c48fa668ad6dd58b3792a96eab9aa239657cea53c4ab6e242349a09fe33dadcb5e041fdeaa9e7a91ea29734a96fa29e055af6651ea53c2370d7bd94b5d5ac5cbeaf5b488d65a1fa09f26b6efc15261c7d29e83f7f5bcd7d4350d9448ed29ec00d9e6009e995338d39c35abac33b30d26bf8b5f509aa4caa918b32b55b314132729264e96e49c2cc9657102f4a438830f48dd24274cc49c0adb3b01534b98c0e05ab1f479185f2ce4a4a06713a338d2e0199998264c26edeafa45118d51d812ebeaaf60ca1819193358427389926e0553c6c8c89801471a1206520814277dd67be1b377247db60d4a6ce8b3bb0f43bf2e9429ca4c9db8e0be122f88855c959085dc1175516ccdec3dbce85d254828ced4e9aff07d252a1b9034ac5a4fa684c1f526d12e539d89937e555a82dc91ea3924edead76b43957249c354ef3b51dd254b649a5b9531323248186646901c32624a7bbc7795f6cabed589a1f79cf27ca8cb3bad6d3b2f87a118aa56990a062f8ee4908a433343c1a049698f67e6108b2269bf5a62bd25d633739d5933d3a8ce6c000c9080eb3b1ae28e1cbf4be411d9ee29af5cb2a45ff69dcbe57ea8302aebe7ae26f1042ccb3409b8cd1696c8d193e26ab6b00417aa83e2dce16ad2f2c3b4a7da58200c4cc95bc182022d4e4a68d182232038601283892c4f78e124a334f0c2011a0f38e0041e3071838b2b52f0012d54200610aa103e63ad65cdccdc84e293b84cf5d0de1b96cd125e75b37c17bfa7bea7be535a7e0743229fd9991d498150563cc3bae9c817a80c3020030c746177468853745d67bbeb68d8afca6285e19f533b4bab5dd77da813e069e1515514ddd9759dd7751d092c8df6d0d72eca7a6aad145dd7750ec09ed94d33f0baaeb36996ade96678e1fb34df0c28239449c5a84a11665c91e46a84114b5518ca84a18c309c09439a309c11863561c8b209431a614863654fc1af69745d67bbaeebaeebbaaeeb7a5079211443d35285d0964f2cfb9ce91768f669f80c84ed0e6c5de5f3c5f63614c6b2aebe1547f22b98dbb3682ce6e0676a5409f6d92c1b96660e532cd690edd453a9f7c07608fe7e5c5eec68ed558cc9a23dd6a634b30160ee0c3d594b56b79c6142deb1741259ea029a10aaa1ddd05a4caddbbdf7de7baf699a28bca6a6c60903915499b8b843e5265954ce663393044027f2a430d6286e2c675d312a3756afdcd817ed22b1143306460c876f6a1354fa35e5fda05a10521096205b90154204dd829682a8c0fda029625c1a769445caade4a45c1e32339f9a194c336e3838383838383838356ad4c8b9cc4c86265393416ac240bc66e5c640592962ba34ec284bf86ac58c81ea970a87a932cf7c62250bab4dd4082c595865220781261b1b1b1b1b1b1b1b1a3468b45ea758da19ebeca270176844a074e8c03366cc983163c68c9a9a1ad68970968889a889c8c9c2aa959e226e832412c2c8a8885392c8e8432da6d038629e8e6897d528c2c784d1b098a77e09f12d5508f3a7690265d0ac5f421cf5ab887e19bd5a318fb23c893db937acd18925c44daa4e8c5fc430c534c53859580cad84922143860c193264c8989999a1f9e8347e313635ac3e91e3064924850a157167ac57ee88b3a3ead58a39e2fa15ce804b36c02bc625314a7c2acbb22ccbb28c898991390882e015e04d4711cafc23ee18c34b63384c85fe34c5b434ec284b9d3d8923ea17590cd72f291f7dc66ae55628eec413558a1b34b3aea02a3748881bf4e40661e9b3bf430ad181ab116610ed06650669e0126003401a5805ac81574024100b6853ddacc20d516129efb0671fd41705cebe1b8d2180244992244972b55ac1fc27063db3b06a45015bfa358453bfa2fc3cfd84f193a308e049a14b97281ca3152be629c50c9ad9a0cc3587fb35d72f29ef572e2dde48c1559bac28d5a62b4a504041e1b464c992254b962c597280031c20bf5a51b272b3b2640597428e5a1b7069d5e1c3c58da58ac46ca92a310b79e0e14b0dbed8e0cb0dbe80f105075f72f045075f9e8c64603403231a7c71327a81110c8c6260b4c5c8c9c8e9cb93161b38023c575c3d8aa23d7d1d52480d5c8f941ac0d480db0e3becb0c30e3becb08312254a04f08ac40c091a123524900670a327a3262327a39c11172328a32e4651465efaf5645667b42f42c07da320ecb8424789094c4a464ca10e3a8c318131283046156358314605c6b0c01849632845814094084491c018b62853447940142aa26089628b621b43290b146e499eeb2d0138530a94518d4ad40d37dc70c30d37dc70030e38e090c313c09480a6043825e009c9a32845418a628b728b924594a5285c44c145d11285294ad3181dc0fd2858a880c54482aa2145a98536d8204610c410430c2188c1458c21884104318a200654f641fe41068218b9bc83cc83dc831c46cee59c1850565c5b009eabcd09ed402c417182823b72e4c89123478e1c292a2aa2e14e6a4e909cd89c28d5f00c959f722e47652f3ecc7c38f281e643151f6a3e2089d104dccf4b5861acb35bab48312b8dcacd4e4a415734ac4a6161358a3f8718862b87810a7af6b3c5a312487d2a41b3b07ac58ce5ac0ba95fb50aa656820afaaa55ac80e6a01aee29cc9c12342bf74707d4f543832a56405f3f31a0b50aa67efd34f5a9839f1cfce0e0078c9f1bfcd8e0a7063f4e3f3488e52c2c86fb99c18f0c7eb6fcc480c6ea02deff81018d5523deff79c14f138d3d792c96c36108e59f1fe0fecf53ff10a584311c000010c311f184c513079e3af094c593079eb478fa00ee1b2d592559946840c9069e6ef8c9557201daa30403465c2519a03dfdba80abe40ada6394041465083389082947c01da2c452ae803b040b4b31936a14a6929b757ddfcab4c2c4c53a525172b3f2b4d4c415219e536b462ec4eda189f6f46b90426ae01eb22dd3628821861862882186186490418619de83530f4f3de47a8082c240bcc8952c5998122525372538255a943029f9424953bf84785fc9967e3d795f899385d52aef2b79b2b0bac45309705f891722d018d6d97d48222954a8d4d9294944e5435d831c92c242987546bb4f5e197085784de2427945e246f9ad4cbc46e0d6da2b046e13af58ee103faa513c89507604f8e5cb1759961f5e91b84f82a032c315898bd45adb49b589ef90dba1890811224488102142c405175c78e13928e570cb61298b022ebec62ac50d3a6a5743f16a2589a099851d552b6696297df693b86c0729248543938821c7018703130e4d38cc6ec805091224489020418210111109390d4b34e06860a2a1e967ac579cb85843059c5e651b705275e2e20dce0abdca08c02a7349ae576e53dce88e7e458aaa484f3f7efcf8f1e3c78f1f408000197abd72b3b0fac49285552770f50a130e29a486949bc2a28f1f546e3d7ab5722bd2ab14b73ae0958a5b97787dc0ad555ea7b83f5bdaf5e488ff34595895e2ca0d62401437a81e1171282cacfa218867a1f27ac4adb35331a1b0b01329541e6485c68a783fa8091a337a3f88091a03e2fda02a3406e5fda025682ccafb414ad0d810ef0735208816c480a0248290a0b11fde0f3aa2b120de0f3a82c69cbc1fb4001aabb3f7838ca03129ef07cd688c4a8de239480adc0faaf58f1a05d2ad565e1d708f866ed501172b02a6c07d053c00f787a002f7a360c17d2810c07d202280fb4612c0fd226c1dc41d8ae2873b24051177c8012a1c12953be404ede94bb9434fccee10144eeed0952280a8566e7493684050a298568e00d515708728b1ea43dc2158586526552be610927525d5274e0b65158a2b7728bc1fae51509ced931d51bfc8ea1397e21e65a90b30910451bfe028af469836d4a0149fa9530d1c2447846461b589f7896c16566bef1329595865e27da21b8d29e07da22cfa35c4fb444bfd8af23e1117fd82f2fee1999aa003b84f84050dfae1fd204e348680f783e0688c88370ce6e3739ec7e1f331d0330b03019d52580e3110100c76f20c837b38790e710f27ef51c3413cf6219b855528de1faa59587de2fd21240bab4ebc3fa46461d5cafba2d068a55ab9f833aae043f4a1e3c3f3814267bb068e0f1f3e6e7cb468f8a83e7cf8f0417f7c564d1ec5ad50bc5eb9d589d7d94daa4fdce82655284c2a4f984538614ea9351309d72be052da93804b698f945bad5c222ea53d08b84dd498b0e1fa216e7d142859e07a212eedf309175596b8d4ba54415c2737775be4ee05b81e889bbda6cce4df7ff032a0237b17c0b589e9c3bf33b914fb7029ce37ff004bac28e1be016e7e017a50c286fb3cdccc22c0cde700be34e1be006ebe921d9098e1be0e37b3f289640ca47c1c6e6002536605e0e6b306319e709f869b5f7404aa86fb00b89965e4e6b3087d52ca9721061f38e13e0c41b3cc7281c80d946ce084fb436e66e5130591c112eeb370334b869b90707fe8e603b9b9c3e247bd72cbacdc5d2088aa2087fbb19bdfe3e60e0ba09bbb14c06e0e5f3f11c0c570413c4b2c1f1d3e4cb89fa3e7da583c3b376eb8ef62e17c53bba9ddd43a9c83747e9a32cbbc3534209c8366efb0c091c914877350d84d6153a7587a874597029ca90970e18c8cd189d55da0f302677029eca620c7205c0df7f71475aa55d030aa0de80a813070d55a227495f6f46ffa54e5fa6ca83ebbaeeb4c151753d5a54f1dcfe89e063737e2027077f742c80edc999f3995aaa83efb5995537981c1abcaa93a54e5745cb8ebbfcf0b570dbbb443e87ca8e6687d707d52713e8d173b7035ef5466307fd6652634d6e19044aa9377a054b88675a7f455e5a9ea2b11ca7af0962a958a62d567544e2a959999a82e8a4d83b2f3ae3233751ab203871f6ad00b77907690682c543d75d52d29c4078726a53daa5a31b883e4bd8addad587c25894493962a1ca699a2962a7c7fa04ed807a7213bb02a751fac32739c68cf0ccaacca71aaf566eae48355e6907d2a47e584bbd26e47553f22f4a04ae81a76940528755a4ba05b2af5dd860054451a3254943dfe1de84d120199b4ecd11de8861be86132f96ed87b77a0fb01ddefa91e406636c2404fa55e8950a684a40e1ee8fea0c2611f5207dfe3faf0bd9a30d85f2f265c73f6d639ab44c3b3c5c1903cdb25d891678bb4c3b3b535815ec1517da69ead135e70ed8267cd25c8b39dbd889eed11ec86674b8b0979b635169edb49f6dc60083df7137eee30869e3b876bead907f73890e78a6b97c37358f45cc26c7866c59e8f839e756a78eac78f949969e08eb2fcf0f1ce59188e92753581be704240cf8eea17ce52db9985e1e4b09de1d8198e13b6e106400cc03d3a42b1088e4428c52e9ebe602840af98922fb0309059ca4e4922164ec921176bf9e39424f2f14314c5cebb01ea479475751728a62fb6dc00258aff61e6521c3fa3d5ae20c510a154e1702e67a238440808514ca23a18e4801095cb819e939353f891422250660a04260330512030cdcc868e7284b4a101dc7cfca8012a080896b1500314911964e6308809c4cc2536334b66e6b360e69306361898b286a6c04906272d4c34054e544b59c6621769edb6ac275ca30952139b9126331c388e703992bb01ea862e3744f52be62368c88160190b411ec66048119a0ba13911cd8398f904820bba4170b4078899c3109bb92c656666b158304333761b0282c33dcfb15b4c8ccc584b19c268c1d3eb3324910c4f7d768f1e30180c067b9d92443d4c1972af2b43187d368d506924675d4f4e5b9a94ae30928591a599999d9d1d9e671c27199eac2b07d5e5862eed1212c220463372eb97501896258bf5e713af7cb0ccb30f967906c2ab1f5b181be2f1fc63c33f3ed440801c08e87508a58f53920848d1cd0224876f1c899a21814212852487e4090918489c90dcb258e242860c1a1a9a146e80ba21ea062fb321166440ee0d5137749119f2a3c9ac5f3ede3f62407011214441828a08210a1254023181f041c2f70789f6f487dc211d16b29539e4e084739e69e08a8a8edc23b923384a7f9f1e49141228245c90e4906841c284e40b244d7d764cccbdd7cce1de00655d51d685c30c071a0e5570a85924cc8289042783b2f52647fd1aa2f5eb47bf621885531a540354d5d147a186a51a7201b0e9e8e8e8e8e804c026232323232353fef80c4934f419724887854ec9213c359f9a8d97a82d4e603ce1e0e0d46842b3ae26b526484db0f469238500d1c403a6190f98686848a21f426636c24207224229f4a1166b6aaaedc74da221cac98c67665d4040019103220c209e96b8c06961eab36db8b4cb8a8e130292a4f19206ae55c36d42e3396a58d1fb3cb3a27b43172a64de0015815286a77efd785f8630fa35f4be0cb97e09bd0f4453bf5878dfc98cc67c04790e839e4b20cf2c2c839f8f6564cf27960962e670f52033972cbc3a1033b38e57c766feea32339f4d446f22164c23b78e41996d44f9b83b51443672b5f24a89d4a78d28dcb791b391c35d0e7d0649143e163b101010508f1e40403d7a00f5b8f9c4401fb24077e894248a7da857e410211388a63fd9786a72d484860b430a69fa5353934c69b3d9624821b55a4d063924cc9fe1fe6c85f757f8ec8eb10b80075d23afe126f98f9b7494c39308653a1c0992fbd0e1384a163680db004a30005c006c31251ca60670b3b01aa0acab04f4858015d001e0fa8500a63e3b66e630a434b0d5000a88dc0e1708a8a2db8401b48915eaeaeb50241504e4599e3c8152fe381416f67128218ebd89151a9be1fd264dd0580cef376182c66478bf49151a83e1fd264bd0d80bef375182c65a78bf4903688cc8fb4d6834e6c2fb4d18d024091a2bf27e1324682ce8fd26473406e4fd2647d0988ff79b2c80c67ebcdfc4081a8bbddf644663a10fdaafe02e7aa6439002f79bd4fa47d1fb22006e12a43efb466e921cae97e41e25b1d11d9d2487dbb9d000dc27a204eeb7b004eebf5005f7616002f7656802f763b082fb33d4700ed702b94990a03dfd483a725431e3b5e4794d2d799c92443b176b46ef123dbc4244f09ab8c6eda3d9acab346d85deb9d2b5aee13e0f0e1a2ea4466a2bc8b6b5add5a23182f7f2708f34ab59570e13ede9a26ce1542fb4cf1659a3a25a6f55b15994b4cf9b1cb385a54f1776992d5a074ada678d2bc373ee0c77dd18ce7361f8ce75e138ee0be771897c85db82cf1df25f23d7718b3cbc3486168cdc1ca62237674986db3a8ae1b660b8ad176ecb85db22725b482ddc16969c1cdf905b69cff71c92a875f15e8e5b0383b7066e99f61e491482172707bc37a78171dc241d6ed626ebba645265b22ea6fae4e454a16e2ec52d1b1cf6f01a15975497acab511851508d2cfc8128ccd0b919c14b836de0cca8cda8f52b3ff71c0a423b2ebe0a57f615aecd79dc9fe3b83fbe735fe7a1f19cdbe3352ed079b84b4e807b808357060d1bda1202b8f906b85556805b6d49f55614c8ea23c9ede1e11572ef12bd7583fce6b270035c2a0b7d8407b857bacdba647846f409f500a251bb8284a54fd08b92f699e3c6aee306fdd7dee70a7dc7f57115eed057b8df795c1ac7715fdfb93fe7b93feeba36cfb940af716107af0ceeb9493afcdea41d747868f3edd08335a3d5906c3fd2fd95d7faa409ddcf826ece52ece6dc6464c850f5306fcc96e999614e2637b3f951fbb9ad2bb785d427965b9d7b6fc404c56e5a1e88877c0805c56e5a1e78c1eb834d338511e7ca9ee3fa18bacfc54241b19b9627c3433e84ccace33c7f16ab2cb16792423c5ce2f80e49c4f31d7248875d17e9ce98e4f670f0c2ac405be226fc770aa405b63c5ae5b4df96a0852e1ad721896cae430ee95c23cec5cf7185aee3dafffab8cf1dfa00ae012e80bbc30b7075f8aec2b579cffd59e186e7717f1cc705face859de7f6b8ebbecec33dc00970977c855f61921b121541b19b96f79c534e0dace345c74b139a40633427ca87d06f41e66f31f3b71bf3b796e999bf81e66fcfe11eb7750454a555b3e14f789392d4b468d635b3b9ada3032c31c00e3a78b8963b8e83240a8f831cd2e11e12df9bd4430f4dfd27504adaf269cd789e516b7db715ded6d16dd15a3ddc160fb785d4b2b5945a05b82d1ca4100ff798ad19ae1c71cf4deae1f726e9f01df7b5fb2bf733dad3c3c31ecc27b9b450a6837924eba2a1790f531725edd3137f7398684f5fc7cd59929121432523232323233383c595735bb3a32ab52b4839728037856bdc7a349bd52ab44aab3ac85a036f8873a7f89044f08ce005718dcb83929ccb6409ce0ed209d48757b814eff0d041051c666b0604d442d29075d9605d7d15c8227daa40be603110202a883a749044f6e0f5c1e28e11e7dae7b83daee3c6fefb739f1bf47b697cc715ba0ad7c757b8aff3b8b0bbee8fe7dcef35aecd935c2a03af877b6e5292240f7fbc90a8f8077ea32d01f4ace3e6f96c0969cd6ccc703feb78f1426334c74b929b03450552acc73ff0db123420f3c66c999e199aa0d99abd6eebe807a4716d42f073591e632d73ce8324c2b9d81ac1db619f9b13457b7a85833158bd0f36addeffcdc1d19e7e8e9b033ac1a8aa284b18d009067482019d604027189898e750a975c5755bb3db3ac2715bb4dbaac2e3b66a544736ea016dce6d1df15861e47151f80ad7bc0a3785efb8e793dc1eee736f5cc7b5f11c57e7e0b5b8e7262979ce4d4a721c376987f3707f23c0fdd200ee7102b867b2aebe779368a14cc94325af91e43c3a7c6787dfdc2142ec32937478eb0e1162f36053bf6ccc3a06f465e3087a824e16f69c750085d9b0426b166683665db67ed940eab30afab241053d5bc779fe2c5659824e79464dc9cd81ba3929dc1c2f3939dad3b771446a967adfc6cdd1b93948391b34f17d1b54f46d208d3a749ce7b12aa77a4e957b0e37f93ca3d69af190426ae09b39b71194e0edbd95f680e72189bc8b3868ed55f85b03d3873bdc92e8db09bf43b26e16d60ac3b2fc2185e8c02d9a0892fc90e0ed17782b0e6139b755e3b68070ccd68c8bb23eb77af4e080f1c04c58cd0c1ad88c0c180c7c924b65e147c3cc46d8e6d6c6a4ac9b5b4aada5d68d68d972b6c849410b29e702395ee42ca145cb9922670239078b9c20b4d3b7a55fd6a95f2018a1d981adc2bd4077508224c01bde9a3fa87018e4c2e5fb3e10043bf0a02deaa24b1710b45ebe2eb80b089a455d74b19db52d1aeed6ac5d6d6d51175c72ad19ee2f6cdbb0618a021518a31cba22b5004af76c369b416956231db44c38c2224da9c63014492c3e9e3f25a1e76f02b8fb37058585b7fc5d017afeaaf478fe8e3c2f5e17dcdde3426121937521d1988e77f772f4e5f3aebb5da230fcee164761b277af677bfb79b617e0f16c27b0c2b39d0277b74e509888f4caf6c8f5dc5e76a8d0bdc3a03011675d340a1b7a775ba3311cefde4ff4d5f3cefe86c678602b25ebea8739debda3af9d1fef4cf08d04dec6b282127cee2bb8af34961ff7280b18de2cac6dd6c56461bd645dedc3cc3042b7b02c7b98998573c8046432c1cc7ce21c32e58ec27d11c9c23c9a75e12cccbb5957bfcc9cc3fa3133abe461e67205a450053387e50e33972e33b3441c6ecfc907b8bf52b2b05009d3a850d64526d59c75e57a46d6a83ea370dbd4279b549daccbc963e11e1cde9df1859706ec57a641a241a2315acb5518ae9648a2551844aba7fa843be686fb326a5839b8e639a7e69955f35cd6b0582c968d28bbb5a9ca2a599956297ca9ec16ce3ca9a0ec4e436bdb2b93939393a3d27159b41a271b376cd8387f162b97394c81e646a3645d3448406ebd5525255a9363215dbc78f1e2c5a73a5c2a45d6a525954a1cc91b83c350258a23b9026fd20f0ad2eea9936868686868509099c95836b0a4c112de128be348ae602eccd28d766f599665696aa13d5cda6547925cc1942b164c169ec465407e9e5218ebbc8e3be3d3b65e6a5bdbf289f3dcb99828eb62d9626e314a31b31a39357298939393a3fa6aa97fbabb3436eba241a241faff9f06a926478394b2c26623575cf4d95f9144aba53e574be5128e89a1f5d997c4716370314b31b525fa45e959c362b1582c9bd517ab25eba241a397c2b02c59ac8bbd64c3adcbae0cbe3234ebea30bc89c9dd9cd29b531f53c7cead58ea6a662905e393d3cd0a88673ef3a6c9cfac195c684f4cae81dc3a23222a8409533f6ead595768c2909526f6dc195c9f1df3989898189967cf2926675d5e64c828c39bc2a9cb839225392831a413a8f08678e722b1c129bc204edd2043e085a105a622b2c887792e0dad68580c9776751b2871e498dc8981b27b3e817c66c8172ccef1ce5472dc4a12b92ed28c5502658e09af9769909c669c684f9375f57bee8c128d7cdf6b9246d25699b56a98ffb82c2bdad5dfb92c5b9fbd5aea90ae96faaca2cc2c2eb8e85766850087606669264763a9f262299662298aa5d89931b4d5520a0c59acf0763b77b534f3645dbde2a25d7def168665c912bd5b09f0f8ec39c574b1c58436dca7e9316798caee200e9086240a6754099479a565c80d5ee1fab43c77c505ede987360b03f2bee7a47a3f54ead76aa96160544ea54d0aadeb8a92f659574b161693cb313952480e8e8142c12deb8ab2bbcd0a6fb549faf1d40f3386cb8e19933b41599f6372ac9a1934ac19192c56784cadcbcc46a13d6ff624f01b3668903c07c8d470df5c2dc56c4183b45a5a2d1de7989c67450609f769ac56aba5182f707f464cee0b9c6372bd041931391a9c637231b9150d15ad05e7985c1b01e7985c26718ec9f516d57de2e1bc5ac25dbf52af8dd46778a5911a4b5771caecc093301861d06412922f545b9312eebc77f7de993f7c373c03f5011e6ab3452488fa058737c2dd3f32ecb3bb91471285af537066c2152cf6181e5712a498863150b86372edea0f045366ee52ffc02f2687bb1b03a53569d370b370427d24091c0d47c32161c1d570b50be06ab81a161c1216dc15dc152c17c082abe168381a8e8643c2d570557057b0e090705570341c0d7784ab82a371e1d2a54b879b75e9a2d4a50b6ee605378b8ac2cdba28e1665db870e1e2a50b54146ee6058a0b971c1417123c3555c10526c1d353152c521e4a8a822660c122eda1a42868c215381b89455d38e50e3c35c266f7ee238bba70b24051174efd720ae3141775f104dea22ec2482ac1d32d5d80812939043469775d77afc420d959a0ac77ea93e2a22e9ab068c919c0a22e9a7051174f4eb8a88b2db8a80b30ca0e8337c26651176178b7a88ba73e8bba68ea9711a6b8c9a22e9a684c51175ff4498bbad8d227c5e0972f5f9e803bb3a88ba63e29b61e0944aa639b56a0562b1a169a63bbcc1eab286bab4563b43b23d0b4f8d601b556c144c5843a418049ea3b1326262d2b134a836a32a14e4ceef75dc544a552a90e9ec9f5e163727d60c284c97df0e194c69a3c754a63097818541bf0dc529ebb3a7b0ea93c8f579ecb289e59569e2fc5f369c4b30f4e2de099079010cf14f77af24cb53c204545ea629d5da3a01992ad4f2b94ac4bfc404b0e01a382519f3c266a4ddc0a2bacb84759ea16f00bcba4848dc98aad895c134d3df4d0430f3df4d0430f052840010cf0aa844d0925256e4a902b98324646c60ccd8c152ed6b06c5812880004cc2308607935e256245e17706b12ffd9d2aea32c9501b49fa609fc3409f1d713b352b941345bb32e24296e90cdba8294fabce2b6c4c5036e108e8a1bc464c31b4175f4a1b070900c2964060ecd201c9499dac009071c9e70c8e10015c4f3895bc02d04f1ccc22d04f15ce21682780e710ba741bc858b2d1935465c05bc4a71a13811e234d040030d371ab2e8d71032a0a1a808638c31c65826935d88cbc28b2015b115518aa28a5badbc5240895bc4eb046e7da2885ac52b14b752e045dcaac4eb13b74ee01398c0451b9beda294ab7ab5dda32c4c5a35558a542952bb6135cd8acc8a1c99346acc0035439719a07850d267dfc60660cc909b4169865a50505050505050909090908fbb907301ca852817c078aa41a3c9852617b6e4b4a80ab4b8a0c505261b518650c0c51b3c2e9a4f5c8a296eec890b54975cd02c965be1414704fd303ba255a95d41b259585de2fd18180b8831c59a624efdaa461415c95571ab62a90a1c05a028e0f4f3f3f3f3f3f3f3f37abd60af11c05958b572a3464e8e1386167c989872e4e059fa61faa1f180d2a143870e1d3a74e8f0f1f1d9711f269f261f2715ceb3c483e361e2f982a789670b8f130f183c4f3c399e15e03e4f0a68d00f29e4904452a85039392485c38b2e217c7865c2c407263e08f1a1ae52ae934f318519d3f2f34449214e9ac8051912c2e512e2da786e3b503b4e3b3b3b3b3b3b3b3b3c3c3c387ee37663e906aee7f72a59d8bddd2ceed2e5e2e2ae96cb749b6e0770ff6271dec5e42293ab73b1a24051b0f51657332300874c2e939b24d1cf963e3b0c99fc3431b93f4e52bc5eb93f4dd6e5647f9efaa7a530635ada25c406366c5c2734836637b55bbb486ffad28d1b376edcb871e3460a29a470fea676837463b3b06ac565c42f4934e5b7a6c21141ca879a0a15a6761dc59afaa7eb957b9445882771594c4bff0861c6704eaf5523926897d5ae2061b159a1748302f7839ca0413f3ca6a55d1d9b80943aab52984701a740a854aad68e9341ec989a111140000000b315003030140e87c4a2e170a4e9ca0714800f85ac5e6044980793248f619062c6184308000000801100c268da000213c7708f8a8415793ec03a432131e00d107f000ded1ada506550f6ce59756a7aa02a638511f40086048d988b6f924a3e00ac0553ca5fe0b845356fd845ce22592c3440b650b2cb2d0e8c790e326edfe3c2fc03737c5053855dfc521ef1e51fc2c6f8106d1cf2e9fdef4876d23f64de721dd3cb0a2573ac7d996fd408dbb20393d3d343f86ba4f6691a43383048f2c34cefbc066a47279f1cb2a96862ee2d88cb948065551d43b44f1150ba173e5364448052915502faa5fad7c93ccd215e52a9a2bd7cb844940f162b6f64502451a8e40c5ba71b5eb5b5d59a80074dd1d20f86c9a7931b8b835b0e6f5f14de9f91f3832d6fcddbb5ea607a8439213649f498057d74c396113b7dd9f68bcda020c5616fa5944b0a301d51120cf5faade7721bd97833a8b9b913df30ba7bc3f125b071700fd8c953da80641475e23156565a880d39335669da00c2e4f448904008b6ec6a207158f0ce8c0a21439052a4530c024d952f06c11f843b2ce6e14e12d5d7ca88f894e213595120d379576406a0f1ac56bb52480cfb23ee23235afb7c0c30b696ee5dc8e694079668308621f4243ea505267afcd591388820eacd5b02facbbb2e2d44e0052f35d3043292bcd03345098c99388a9bf9b93bc690a3920a36890117f0cc60f8e497efc6dea7e3fda8c9bf9b1e50590ea42614fc6977a6c18368bcdf45a301dba18d06f201740785570bd660b83a5b238bef353488b133ee03ca92a432ffa559ddd39cf176091b514218d99688d8d780abc57246b281439aaa127480f33da584c42c4e7583c1491c93ec4d0e496328b709e6c498873531038b199ca67fa71833b5f55927821809f75dbcbb92e1b515f03aaafd4d0d1c4e8f9b93383571fc017cfe656f4d5db4d2b11abab7e69407ae7cacdbe0a6da2ee66675d4591d2c375b4dcdacf47d0e02d7d3ad6aa9c65fded05537cdcd5cf9c1c30faeb1723e44f7dd635fcf73b466dd0276f353c447756326a6db082b0c3930ebca9d484f4c7cec6a0c8d05926ab8144aa77215b0d4c8c4b2805e83b6f2da361918133ad2159ad0453a8e48acda8291c1bdad4955e8d6f266d367d23943eb7756e707223370a013b1d3e8471c12547424dcad5cfbe7abad8b990f117ae444c8a18d154552d5755a43205402463ac7565b51275c34db3fe24d26a3e58ad584d038378182afe6d534138bfbc17a29308d21d592cdfda4fcca9eb083daa0ede6e0577064ba140c8f478c74871f25386c991d8c127c0797ce658613e8ed7cfa4f4350edae109d4373eb041900f3088a36aa0f04eef31a58d8f7c41b0b7cadc61298ea5672c0499c80943a894f484b8f5208ebc6786d4cbfd79fd05f258c500ff6e5b90a87d255c8908165510b6a941486a3e80eced037a33ac50fe2fa1435390782111aa20c34ca3baf1c7d71569b98426545257ef1beb1556012811e70e8ca01539e4d6b863e4230104259f697e868e861684cf15d5bb6507a5e838643df941f84d4acb39a70c694e8c77813dfe4ed7e12653b872eb27138b08e6cc64818d335554f35e9ad7a8de315d83c65bc567605054cbc93b695107b725478ea56108b375953408d542a472ff98a4c64535411a67ced6f10d7b87abd6b1478a90610519296760157179c548d10504bb644b635b1b20e4821d104540259700fc1f40586a6721e80b8627b74ab3e32f0da32a364eb80a33d051fda483c65977d96f1d6c696a324d89166999e84fa84153b18836261305c444ea4390a9002a4a0af3e4dfa9744a302493e697dba25e963152d8497d3c6cde7c2f22971b760704c4cef3a834d27301e2ee04c7ec16ce0090442d309520e304574eed3f066052afc0dffbab33a600c420234b92ed15f880dea565e1502fe2f6404d32b117b4d45f3cd90c1c32e2f1dba724d0c4c2db0e8e849aaeddb7498c573d29c727c5ae32c4be1607fc37b505f46ed4694040361b0814e23dd8706cf1b5b814929455cd2d75a395682117a0a964ea2c79ee0d5d4f8d3fdae9f18d6a50b0e7f46aa30b8cc65fcf31a0262f429b9783a4abcedf153770a8a2886b0eb16474a158b14e76746b39083f2104e10e0a3c1e093b6d96bc5803a3bf38525664acde0d800a84d0bf2574878dc3d875d2c7ed44877bffb49848991b817085fd6b7487129f23abc1a4b017c4bd902cd3cd23eb4a2a09e3b50eab2be291a30f8804511d55dbf03b9aad0b788e4688596c3c6812a3b9036a0d9cf8518819d63854e9430d9c9dc8390c58e949a18efbc448889599d635311f48178f9b29c12bc7ff7807bf116f39b912553307265f6d6b14539f91b667aa36b1734412048d9a52371ed3863d4261e09a804da9126024615e0e9b5e73ba1adbcb55d7df7d1d1beec751fe4ba9227545c3f4cf3fc64e26affb0ca7155c33ad27498716a80469b9db770dd81a7be3b9e8f6ed83e80d562c00e3eee80b31ca45b8639e826f304af2907751de5fa5835ee983fe9c25dea6331663acea19ca446bedcca137eed78d497b8a279410afb5cf887bab02e923a9449ddfd2e58235ed869fb51a1a5166e2f84eefd9a1653c1cad4a2d2d46859bd4132e5ebf4cd35b28917c0062ffdfdf12753c7da9298739b6e5bf178c835ea404de2ff9823a30e03e53f100e7fd25fa649b46a5b3e71ea8f16a00f917d6b0e50949dbef0eb8380ec2e85f5205518e187323bb64186d8c33a041ed95d4a08cfce655a99f39052ddca63973aef4ed35616901e0afd8f0a68e899ffb51f930ccd300af899436210fda5160b014bbcd036b1dc912040bdf8c9ba7d93227b46980aff91654a6eae613791f18b1bd4e9794d4e78501991d77f700003a9c81441dac04d20bacb09d7bbd06f7411a240b064fc5e321c0b3e4ed0703464cd01be0b51c1cb933941ea1c8d4d7aca7140cbbd7c8d9f1e501af69e7a33aa8e4c285b1cee99e842346c94451931401304fcd4dca0dd966a88e1891e4343845e1f095df7b0455024b31022affd3006c80d1a2862252353f40a450857ca14a8872cf1beec177ee99341c8e5a51fbabb1f0cae6abf126acee9651500d7b108321fe3cad141cf47895cfb86c71874631b4f0ec2db3f8969f0ffec6f1f40504aff5b56cad5fabbcd6678be6ac786f6f847de4ffe1b6cf60e16eea16b4be80475721fc8fffd542c033df0594f226e901f09ec97501df5b16fe3f9728e16ff0cd07e71fd2b767f643d74d5104f95a5fd194fde03282022dd5bcf1cfbe0902d0e6f1459d77cf15fe3647698e6b1c76dca6c902bf8ccbca14e39f593117c42c058d1b160b72517e3b551a0a8f71a7863d2ff5a8c61f88afa37272079ae742c51dffff40d0434ede3351bb98d677bf168a0acad58cdf88ed72b08071320f34217617a4807777dc557aa3c2d2030d8ac5205a2a4834daa65704207b2e826167268102eb29b591f22601ee3ea499b5903d0aebc7ec48d5625d2a311c64924d81701a3ff40717c482862630e825b17d719780a533bbe2b4106a43f209593d1bd639a4ee24ba96fe1a17df9389054d2a25b64e1378009c3cef12fec5697ae1e9db9989e658f6764a6dc7c22c43acb0af2141a69cd5085aa774dc32330bc3658788045d21ff20c74a833abaef162e091540cf0b20518837130c80b91e4ebf9e2c49957d83a1dd21b9c5c743b923ff7c15aa7501a12eef94c5ab3fed62d08a5dcf603f0cc660e5bec0c791e7f3d1cfd267d2f7d8dc0f400ed1e1e223d45558f7253ba19271b94b4a1cbe9bf1a85ddbb9093d3a878f47378b76843a229c8d75c316949dadb35e247975c081fc353c8b14d8adffa7436f91f131735189ed1bd08e8e18b2be53f446dcdc1f1b910c79e04130ff47b6888d4a28d83b1f3a66985988d2d38082f859a06718d893749d487b040eed9099ff07fb2b0639a4e679ffa18f49417c95e1644a0cb1f17b5f37d596925fa928370c4745462a954201b02d111b0f63b943da5ea3e51a7c24fa0a85a8748ec4cb8c33f3c211d08ff320a64cd37ba6dbd108e786a7d054332de00e446daf1f8553ab9785e4836761eadcbaed735caf75784edc343dc0f76a76016435133d5a8311931d4ab02f213d8fba78928876aff48717371d6d2789f89ed2082e9f919801c45ebe88fa399571ecb9e010f0d50fece70d4dabf43f75d75d8e7cb8d6a6086f2a11fb59380b87f68e6b283aa9b6a2db18059e2dda22c9674f4751eedc96eec38031239d2f34a639b30230692f01010cafdfa6c05c4f051253a0214042fc1bcb97509c6851975dbe45cfe7efaf2b9a2a243fd5f5dab7c38c52472d134e9ecf57d60c3e434b0d42dde0f2d090df6eb050ccc007b4c115e89e87b5bb2c0e40d7a087d443f01f806a2a3762dfb540fd2f2a05a31f42743f5bd511b1edf00c66daf672c1a03c4cb1f577dd609fe510d5bd3be201a2905df1435006df727bdd53cc76e65bb13d2f642c3131256a2542329324866069841ac0b058f9c728f93bd22d42eeced609be4ea7faf1f2c723ba9cfd37116998e257b3e960fd53a849543c488dac893c118644d8e0629459e2b518c24686ae8809eee99f10b3c9c4d44ed41513641048911cfec658e4de752a97beba20781c923e90172ac1d2169c26ca730b74de59e09d47c6936f485a30c2c1b225b2ee066cb703c5f762abc08311a4e441349bd5c6d09fbf6e4fd022819a353ae3e1c91c7d3c201994fd08ee03c9d70ea3e39ad8cb4c289e4e50fecf7965ceec05a4ce40313832706caf490e363d14c86abd120732675c2f7667c7ae07084ae1a988443a8f524161a1a43ea7f3de052d500993dee678ea469b12cb7773a8016b8f7a97cac81839d29fb227de10ebf930464c717505010bd08208965984d58c513fa34dc5a6c54b9c16365e565523602ba2e4b792b8ae36497e1ba8f2ead8f7da9c76d464b4148f4f6096d318e306cf05a367c1e83b3d9c9a329a99d32e30c37194a2534f14858610046a609ff200208349259bbd519c0e748786a36d3cd3f301fbfbf0a03070f56d91e0d0e27bd8e7ae31affc649b234c37533b79490849a5718ceaf8d422eb5afeffde0481b6767bc0a90e201a15fae479d3172fdbfcf73a172b3f7d75f4aff00d321540269de7619bcaf44050510b1707456f9328d95315f54e8026ec440b81903212a6e12905e04e4438cdb0d16adb051d1cb51219dfb693043eb2dcb8032979ca90075729b58c71a51969bf77d2fcda1919cee87c955ef20d6dddf2b8aee588e50550e40540c82fa018638d0ea484529cffb7d866f51e9c11dd2f5cc3a1439f17c9f89ce77d4b9313c01ca60fc0a22c09b10ea885d676af006c08e1ad641f8bb4e3a0f791e2a8133c2160b9afb3d4530b18bed49741fd84a2f8d8d41c018173da8f7924b9e4f7e14038caa9c86886d520451fce0b2fba06d5b279e0f2d9bbe1170f76da2e2f81c34e95f060dbc8b61813b76c9e34311bea0b0f0d02bcb1fa66d0e0e563dad8cf7c2466541efe74640e12d67f83c64093e6d888a48a0ae0e1fb0b7e64c4c03891389669d5053b14167a4e2c5fdb8ae61263ac5a64c5e71a3594051fc031925a40757d8be8f29ab3cab486874a1a59bb84c788b102818c64e0c5609a4b4f980557166f1b9890780567991e106fa315dd7921f12c3672228c6ac487587825067858951c7251c383ac67533e6ae8616e647384f8f98e0f62497c306c774f0162ff9e64d2571cd80aeb82ba90d3eca7348eebac4753fd78a69bfaa0a5c3cf20eae81539cd4567de18f08ddab183167dbb939da44e2039c1785c645efb702a35e43c5eeab0f69a2ff61b1b943213b19d091a818850404db0fdedde625c54330b28889b7aabe3699aab5d0d7d0561198883756e7437b6dad3110e6fc4d6099b88b0a8684569fb38ab308c708e0392c36abad2a26c982feb0386b48b55aa97e64e5242c39c15b276d5f1993158348a0efb1304d0b9489ae55e9727787662cfd942cce33ed8650ef04b000321be2f47641895cb083cc44a2fcdb3d713c8bc5c12898ca0ef6e5734e8978362d4fe0b11cfc6c8d51496ae92c37f5484c92a41c63cbfef92026f645ffa474553746b775c793245fc88f9fe0651aabc02d7499ae42401ea47b6efa018e926c8e1d17fe40e629257495d16653013d9578baead9a78f16a523d84f096156c9e57e7e06b08ee294dad929c3b72b17db081d3be2063b722bd817e2b83c4e55fb16fe10f1563d95815c8e88c2b0e76543241cd81b58467594364bcb1c04f82790ad3e56df26e245a7737d6150b16e84f5c673cc9ee4325e3ae3c48efd033b1acf94d7ac1d2a1c9616350a386cd8cedca007663cdd389a94e8759ebe991701e8ae110bde27a231ed56fa3483791aa04d244992ee99a782ddcb5073d711e90443d9e61d4816327eeb0e9bae672d0fce680baaf457e708d3eb26371ce91937d569b937c55a7f6c3d54a96f38ff731e875d3b9f261e676b4aae7d4d0477bd84169d8b3b052fb37e8d3c0a04c8c70f6a2341587573b75134ed5658e686e211d965766497cb65dd09ddee7bccb80379e2df88281af0f03f6cbd4a2ee85c1599e82ee95ff9cff85de929686edcda5ccede3d90ab1edbb6d9e430e2549ae9c33516ec5cddcac69ca0c8927f9cd127c76e434ceb8d3c22131190d52a9b2accb10088570aebfe73a6e6488d35452ca0c83c0751d08eb86a173e01c5d93cf50bd64d451ca3dc8851d53cd2d3bce63ccbe6771ccceb47efc379dd645e5f78336a0f79c35759dbd4fc9a5e722880099d8e9326f2641d52e05ddc691ab2c9aeb7cb05ae37978ad084f6cc89e3bb854003e81045f142eff8cee4395279d7b4889a13b2e462fd8a8e1d01711034adeb3e38000e4bbf3f3ed858a9b7a5df53798c33d23e5724498c2be08dd6ad469c9d33e64fb20cbd69ec5fc9a720ad8498ce3040321b53c333c6b67b938bd5274fd1ce16d6665c801ead46467daca640a99abc48970178a288adbc93862e684a3962cc867af0bdbd72aa3664ff83118d828c5e364c7b254281d8dbf77fcedfc640b5054aee2a55cd0f331d74bcc353eb7a8a9fd77ca115920f53b0d59866a57faa439213d5c1d9e89370f58d777630277363b35589561dfbc6bbab3543228847a88a625a1daa71f3969f1dc75adc3979d5479f728e0627bd87557d9a8cdc657fb21107a42d1ffa5ef8f157605d3ed1e9431467b2b86d8a02cf8deafb0c1da9b47e7c5c807079be84440dc28a689df4cd87d28eaed853cfd7c8c8626a78acd727def0ee23de021fa928a686918578472a3d18ef97dbde8e1310f514bb637b68b611b74f2a159075d565c57aaa1e35e1c8f31e56ba60d28d9604b949314126251e892d19d168609947fd00b5063461fd5779ddb4bb3ff84646d340eff874c4960b86e88af3b9a1e3c332a739c77c1c440ce01a60dd5241b19caa3048b6e755550309b5ecb27c62e64d0821e4ca3d27c6f9e4d8151653b82b0ddbd2b42d3ea205ce5d34092362cde6aecdfc64704014cd2413693110c8c78286b773de30a5159d0c3bf9f9189185b253210b45a9fac641ebd1ccfa68bfae63d84698f794704ce74ad13daa8dd15b19011aacc65e0fb098c297eec84d49a300734d8583a526136124f86643aad9b6f511ccc1d7f4aa64c1f749c73db26daa63da34d61308b6581601494aa85d7d2d751bda5e12cd210c58f20e654794ed735d82dcb80ede6efabcd2080c3ed0ad42a4aa5c01b41fc31616c0f8921ca19098800a7c1654ed38a913679c42526c26bfa9ec55de13f51f6f45af55d83fbf2057eeed76933ab06f0a4d6813aaa1ff7d3400f9b1330a2212533f1116fe3c717105a902a75200680d8d15608d626d3d64055ca3acd08853563ba04555db8d9b48407868fecdf61342a4b06ccc02189dfded841f43c2e9152e8ef5662eaa3055835aafeedda78c96c3777e803018f34e2c4040301855c231c4b27a918ab09229136360960c70b40c446f62e97589bb209ba5d35f069ea4643a2147d66545e21d3a8bae44c01806b93601a983e534e286292b58191727daf8231bccbfb1c2862f1a82b6af5a3f6910b9902e8986bd1b5799e0bc0cd331a436d50836aa45fc2cf01b3891d9f3ad472757a7508e17587bd5788d78e36374fabdd9222cf171522394809f3f4adaed9a981441c8a1f497a61382d8ec1682361cb83cfdf8c68d6db48baaa967aa2dccd91d7f178e9585b178be319c37b11440b05d81042f0c1b2a38160d3504889831f0014f35af84d204bebf09c515db097a20356ee42dce5b28a0c79f57d83580731f0c1d210eb6f6ad167ae3ba15f654c797a28794ddd0afb1ba2641bda43b6d7c00403657c4892807d521170bae5b742f065fb4331d29c718c258794809aa45b061a122cb919ad9c83344bf8deb6651dd3aa12cfccbb7d5a5cd1ab12c4331fdfc11e15c333c27c0dc9111e035d668a52dbc3731bf54fc25f046590df512ac4f12d44bbad3069f0eb2b1224e043950097209d450dc598219c031c2b2a180603a538be650cbc2132cac8c3c1002374bed032c33948e6ecc75b218eae9e10faa73aec21c0243478804acfda685b6113d0a7aabf5adec42ca6e48f770bdc9704fd29c1a2b6a908d953bb1e484229153a442e2c4106c0cc618d6060506eb9422029f322b8f656460f682695c380a3f60d3a774ee5a5cd584550d3a14268dda70d5cc454646cd0d3edb4cbde289149b5295a67c417589906d3210b801b4cfcada0cebbb1bf4117b2ec5c23cac33954d4925f688fdc657763d856b66909f65296e9068e9b2a542ecb2914ccc0057a4bb408072fd7bcc0772b9a7fa8eb34277ab9da2da8be032e138fca4adb90253412418debc97ad005649c67c771e9e016bf97c5b21bf372dfb9e7fea7a2eb851b62902a0980f35365b98c122814a66bc97eeb7a76120bbca976165bf80f8fd09f1ce060e7e0041e6f365e3f1708b1c0e36069e46a0061bc48f6ceb23242881bad8405c56d3fef16c30854d543b510db2f9a81aae7196fd9b5cac3eca65abec28b91282a7088289266cf1107a1b5bd81c23f17ab8db434808ccc5b67f823dd499795a5185c33ce9629300e87d9a622675d6416d22405ff8e422e170aa87bebc55284d8ae98863da071cc332c4b2a582623955e1902abfacf1b273a4e13c48a3ab51f1da05c1bec7d7e77f61a232494e6da4334229d49713cb2e0a49f5c2b4a38467d3504762e017b1c81ad8181b246319a9f2eabad6f46ec9db4491b6b18278fe4b8e621b60d852c1b19cab2c067d595ca55181961ce675a88c8473d1e0348312c778187f65e7ceece6945e528473ae985b06d1c4b160775389e7f715c41bb1c5269038204e406bd8eaacee2aec96b3783eb9afc24bd16986112bbf3ed8a939114fd59738cf6d5657551e689d6235aa1e4b5cbd04b2f53297f105fe39a96f8031a47f36a7b40d634eadacc5738ce396b481c8aded28b258b9cff168b54743ac6eb6661653801dc1397b23dca8eec331658c9ac13f0699a891308a5d3d13f5c419f7812822b3cd8e2c9a27d543c278322f9f3bbba5e793d0165aec518beee8ccbdfdb5e827f33d25d02759f710469a79f8b172e213e856dcea32c6b0e8d87d8c08889d2803a9892e75e9daa39ee88140d6f20086fac814ce821c541b44004d78ccf7c2c304cf776d36de93d3d583f1ee9b79137bda1e3d87bc8503101df0b705e68ecb46564f774e568c81450cbcd9153767643002af322ab433550b2f0e6b16533243fc0f702533808ea61481473f50bb7470e43b1300b49c6a61ba8670fbf8cc673d815289a1178e040313aed02f1828b283c1835160772a80683915437515e1fae154e01f57e8933a8348e5219048060047724d30f6866401216605b8e5591621cbf817dd4c639ad98c43b762b82daea0195a0b0bd41f8ffcfdc28d10b3822c4cbe61dd50b82d52fa821ba12f9c300c1c9202e07334ef6981b80eeb3aa83c2135e7f1e9df7a27819ba696254c8c339ffce3d5f3382a0e8a0fc866de87279fdb343706667672f4c76a2ef3ac5ff81d77ebd91533f342334738dccc62fab8b5afe6cc2e3633a463336f0f77d85459e6f8d7641605fdf631372f66ebbd5e8d1febc97b31239d68ccc00e9f1433e514c62c9647d60a6af18f3a9d4ab5986d673cbe37351262a2ddf4f8326b870e7a1fea384b114cd47e8fec54e0c9729eec384263e7ef9daf0a1a1da519f9e52084f37703bc45d5bbf252431a005615875380ec1ff87dd1e1ea159ec8621ae0baba41243e98c5dcf13d17bda765e3a19560efa3133e6f9f4c93b55688fd869fe8d1c080e0fc871d5fcf8022df02bbd21cf5e6a7b13875554babd0663a331ca188796e669a72b10fce92d6dc482205a24d67e8fb1c3bba4add41767862145928b23c9e8f4b5fe23725dd47891b4351e988a42b45f32bb5fd15fdeb1b7d6a63f8ec088a6cfc5cb1a08f37170d9ccd55e7d4e7c623040412516ca9904f032f4e6715582c3e707fc5223681f5ebe4fabb0472d1dae9cacea07622ae03d0f76405665d26cac5e95657cc4b5aa24fa868823b1cbd421a71513bf59b4b50a4f55004b841b947828372f9348e9021f33912b0bbb06643aa832b8491cf515c8c0a51929e5624b0620db5c62f010842c53448d7d4241b858344bb0aa0accfc0b1c23f65070ebdb6037802fdc2da45d995de0d46214a8e3274f489861a12528634970bc165dd1e2e18c83fa9d113992364c9c2986f63cea92c99b7110895181cbe076113552aea4951df126bd19d6b3f4eca1d9d1d81f2279f8ecc084f5a2cd788ba3e28cce1c935b2cb12610873f972b044803d3c657bdc0caa69734cc63283f10e46240f8899e1b47d979d2cf756e92a87aa0ecd96abb05cebc42460bf2f1d39c87a38ccd17b5ff395449e34b0663547c64d39adb14166948703a5e2dae25c6c6124b09e7943cb91cb50f5acb56e356d195a04170dce8eedb12a4b83a8d66ecee731f3a8e27dec77d1825760feeac75aaa535a7d79249332a3c04691e37fdd5b2b4846238340ae38a8fd5f10feedeb53e91266b97e3463735280711808341b0fca14b88a8dac03f86eb8e678e1daa7d0977e45ac8462e899690f18aec0d4318380a88c8d25b284eed4c44959f410f66ea2db3aefb178e79254447acf0556683a7c401bae564d4b158af36425a43d3c3a10f3e40b13e172bfeb35ee041fe73014e92d311f57e4673794f97ef79308a051040066cb74c2633bc2c057088cfe98b700b0576c6d4648e22ef33d7c201cf09028a269d4a547b0a7f636f4421b33f24abfcbbdf23417385ad1d893f2af6fc8f5e2de67f979f2c0560d78e8ad78644e0485febc25e9f80bf80ae82f7148dac5bfc4197b1e9a609fcddaeccb03004c460eb11cf851b7058d12911e96d21421a97c8ddcaeac84bdcfc638620da86e4f68d9f81e221270dca39522f46ab68dd5c3498a87941023f5c4225b125dac7a4212c4273e410402f0394520010876a50a04e1960d820915ca0082e5ee60006a869b40cdedada15ec3c6143331f828a5de9c988b831b21f22385328f925af94f0f56c723aeb99a5bc81bda215d070127e33a739c40a8f37d3960ab012f8306a55f930387a8a4306310d0771098cc3fcaa3040e880ee23b834c068e7b09101792b2701933ca71828f3d42aa1ead2235fa9121d4a26459344d0ee55e9745161f8350afc9919993cb08f7e5eb3bb8f7ef9e8fa844c0ebe70012c4ee28ece5c3fb143cbe2e870b9cc0d8d3d8935d962c3eca9cdb21e7b982ed5fb3fdcb0bbc219f0063d7e102cd17be1315036faf0dcdfce8e2e2efe2d9f3133bed9f4f0ffe0cd256f14c2560af3bb12d7010c6ccf37ba0b5a11289212e17b701b03b15d69f5cc23018d1bac3d0b6785ca17eecad39c9f5faa82da42ebc91e63c0d8995c1d092e499788edbe0d6738dd01979968ac89ccd1ae3a292c6a5a9d3db7da15aeec76834c545bfa83667b69e8d792f296d14c9b9814412614258837fce136f8ef50fee357e17dce30d15f94c7096d7aeba782ac50c3d6764b35d42d36926f29556eab6c81c539d559d00b1d8e831f638f1412b8cb55bbf369a0c0f113e522fdce00617c32ddcc5f86390e8ae9a6c77b6b5b724ffb31a210a71abf2c96ca67ce580fdc9e1064da5cab12799f37571bbe664ec4c1f85089d83abe9da53e163f4a2fa0fc1eef6500e5e2529ea19b9c693dcacbfe520cc556a692814c9aaa1b3aaeadedd391396deee2c7871d137e6f6f0ed4bc69784d77e9df8259584cd8035eb04189bda26eae7d104d1ac203af097e8f94f38b076d992e40bb5ee7982748d9a15c746290933ea84b3bbebe02b30f0d7b640cada4aeaf905c4addc44ecd2ec06f6fc4e2b69bfd47208c02ab2d5c809812bd136cfc2bab9a0a788e02d424160eb21c620d9bb145b12e5368f076033d9a7ff485b4303ab424c6f6f5379d32b74ea27229c96f1c6f3aeb5438e2da436cb4a27139c395460c36599874307b237c3bd79d282f92a764118fa2b4f6b1098ce409fc241f42a5087b0c44404938dff51268a289c3cd3c054afcd2adfe98eccf43c3ea091a417a0441b2cbc04e56620d32d08d097ce40238265c328c0b1b082457d5ee4a52bf3580191f898d6e1f79fdffcf3fc601133bb6b472b22ca25c3eddd0fba1dc7ea987611e474e3cba20bbc5c2f6a651181283ed275f169104ee38bb7be44f00c987c5a580f4fd53cacab616432975a03f2b7d3cc68431e38baa001617781b6694a96112a5942575aef3912a569c8395bcd78bd3a025b78b83e357f33d9bb33f469d48580a7dfbd1f25bc9fa7d1253e68dfc3024edf03fd47b026f5ebde43d7ff8679a4a3434eb2663c70390991ddaf451a34a972f448375b0260690ec190390a76aed0702c3a509bc2f253a189ead35027140ab0f62b064ddcb82cdc0848b0a4b74073a5da26519844399b149ff94a4b98272eae3fba0fafe94863b33c0e9def807d5bbfe420e5a1bd847753632690374237ee1f4feeae03a73de26382054142590cecb469fea3982a8fdd3c615d56a311973ca88d502dbc2ba3bf7a82146c7a0ef96612bbbc071721ac8aeb56356493e4d2404a95958055affa0eb94ca16e0cbade353dd27494042ec79d91fbe6e58104bab1dcd5e5bf579db8d95b510857b17f7646bc2aa08de50806e58f9829af5260a909e2ca0f5a572da2b6bbdb1017f21d1390bfd879dce0784264297e8528b14e780f827ba6ab16c7a38ba7cd76be22cf29831ffba55181a7cbf2b05e8e895ec074f9efb77ffcbbeda5b9c3a19d26417ea9af0caa65473753fac59146be4f31f1639a45d08e5672b2b825a0bb2693cf34a8d5d672c50197a6a8dd97bf2f7b07a7cac0b1b00a85ede3c4049a2c9a18a85b05aae8ab35bb3c7c955d382cb8965e9fe080180fd0d7de0e8fb73512129c1e27c0b2f1c6737cf2fc27ffa145e3f80deae6db378d6bd365197c9960a8d0438b34fcf788ba3ee8ec3d2ebcd827898bb15eac0d8f7a9f49b7ce616acbe8c53ae160a6c63189a81bb757dc1a8a0e38f1cb9b2ab526b374fad5cf5acf52f0ed557dec972c7d93adbf91950b92c3e4c4c9f2dd1c30f7ada10bb992fea3824adc12743a20c2b9f301b16f5f2f5760ef44daeb050180ee7a9d837673bd541148b65efe3ea4f5c61303a691f54e5a60bd20967264ab17f93ba2ac74243d201485f0582ff9ec30ae5e4a2703b37a91b00758f248a703d5db001c9a15512fe5d840a92b7b1c6013083c3ccba91b7d84f51bbe224e62d4e953f45e83ed7ba8c3f6847bfc84fbc87012b8df872bd7279a9751a5486b34cda37d79f8db3d19d7172031cadbfa232584bd31cfc4d7d15528e95710be96bff3d06594a8d47c531a216061d0e6eebf4bdb0bd32ff949fdb021ce6da35160cbab1c9f399b44481851ef1d3934ac3b45339e3dd1a6cae4d3f4b9c0eab8fef167d9ea7c5a24fd44ea00a44433eae1cdad78dd9baaa3b45b21fe33414bfa191af7b0795ea2a12013c2afac0b8345a5a1e0a56f85aae11221f20eb0a9d92415fe24d572a3da1e0ba2a573eadf9ae57af3e937cb3f7b2ec9fdc11edea67b26849b171c904c4f535bb1be78d0dee3109bceecaceb9ea26963d44fd57923c8a88cc949de7a0834f8f60cf60de618dd561cfa2b50944d3845f7a938ad8c94e64db0fc6d7e1611c3309b2f5caf76512f6fd1fa43dc895285da7a0cc297088715032d2525885c09943834d69966a2f01a032a58051e672591a28e5f41e1489bfee140eaec9e060ddd1720d291318cfe3e4c243c553ca8a944cf7b80ed482b779d09349d566b27fc59c64ab39e7c66f24abc52bbf2125e9ef68116ac2626a9333cdc5a6c8199c640023059274e615b3e84e0bffe263edb559f8e4a2181dc713f45c1172e54ef94d261bde092abf3c5c51c23d7a06ec741903bffc23357dac4c10c47764ca5a9e044d6439d34644439cb9fdfaf2a33dce1b5f82282cf371b33240cce7a9ac3ad192e6bca4e21f3916b8b2d58b9c735823fc20815c2b97087de95b86e80db38c3913b6207f7e737b43f4ee063f0e91812b974b11bd3ae969fb222f248c1c8be0fe1cf4d9525b4816aaae140f9236caac109d2913cbb19fe49dfe78677a627543e7e3fbacdab7a9cdb6cd3c240f0c8b4449085c437a57e3697810293e2ed0dec10edbb8495bda1779085e8b764cd53197d2f19250eec143df901602c5070b17b8d5e74878de4dfc55f7876ba56c19c771b58f4ce6e5d2a23aaca9c3b3b4840577257c3637f2fd4ce8ef68804a96e67d7d942fb7a355d8ef593dd991c6c9f9c5f7ab42fd3387dd4fd6f6ff10f31e1aea54f5bbc8f31f8fc08960b8bf9b7ef6b2b07f34e97a90305e4c18be670d4b10076073301988cf21abb88466192f780c86adb8f870f0681c9220262df509df7ca0ee8de72da41b1c3771b6aec620ec083014149759dd7e6055af9a126795854f70f124729dc3716413442887599d0e1160603132b2eaa2e83deef0fa55914412c06f4868d4f6a1e7df2468174367b1ea584dcad13d6856b610d4aaf638558faf09c5cb32377ac0db56405c769639adad8855301509d1648ade5473b00566d4e5e56d94d5aff86eae666422bf7ea31cefe69ee4665a09792929ff2640dd4d0bd4020b1ff2879cd1228774915bb483434646653f70b81994fd77b24d2b642eaa8912b1e5e2d0879ac46da8a8f9fbf49b1906811e90cc6bf931ab9df8b1043491c6c3809a6857a6bbc59f9ba51ae0076f9e226c578491320acaa5a68416056331572c8b2cf3fc26b66da5832674a6162956afd767234a3d26be513011276b907c80a007beb60b533a63e22a87dcdf037490752889bf61e72d67d3546d0781c64834610cb0e0f00c5329f37d7ec50f3da7571bfd01451509940498b1738870427ce05bed00d3d356ea324b6f086d8f0506237d5a3ea44018382007c809328568040c498f8b8f1e8f8e5faff1b4faa7a6707a224c331332df87e09d63927c3db798e9765e74d6835e00bbc1b94160822e84dfd9fe874d04fec48f6c8447288e41d18cf0681fbb768fdf42f77a591036fb97dc990fb48e95117d11f65abba31c2ed9479e17141b571b3090a5bacb63bbda038ab77ee40e8074d8fad6aff99c6df566114520fc853083e265bfa7cfd9d17f14cb147763e07a15f867696934a7512e7acd0d84b22b583afd4ec72b9556bd576c18ad713bbabd862a20022a11b7e4067cf42d544890b6546154b6d7f601920789a0c116e2f817565e6b7006a9553b14e10e63f7d8a28d33d85f9abeaa3bfcc1a53c27c377e6a9cd47985724bdf9860fa0c19191522343c54087076a6ee7edd05143f4efc19c6d941dec516d3bac64080e83edde49f49087c07d7a2a18b5304557f0679275b66df6194abaa6df1ee889864cd98453f5c632c22a81bdb87cd41c543bc3c8d2284e0c716e9b286b086840e04b60ed491fdce60694b2c1e7aaa56b3098d013bed5eb1ff5a2bb49518431a903bf6bab86f5b525595c50c7ec9cab5cfc25a763bd4b0a74c855aff846fa42ed2343879512cd2b956424bcc79a8d4b3491d2e177e45ad6f9af62c98aa3833e809fdc3e36e94ed33572720c2bf3c7375680dc28ba4b194f1065d016d99b3488929ea6d4848ba27fdb4fb2be9ae879075daa41b94d28d1494a2b229a786292d94f3d86c95a238c8dda88cc5619655811639f0f87c69619fbb548ee87bbe7355e6023c54dfb3efe74e2fafce52a05a9f43da6ea512b2d4313740163e0e50774d8c866432c40bd39c55ad907e3f95722ee4c818bcb009fd6fa9025cef5bdc3a0727812428710f5f68b3c0a94e26c23d52428c2b03c19cf2ed0542ba50b6dbf1dd7b06fdf9348cf7d891bcdbd10205d4ef1a6a8acb017ccbf6e49f38e8723a9582fe62702489160a54cd32fa36dccb0ef1af1fe9e1b56ce198bf7b1235e234bbe0875afe8932f3b9c4c25f4fa5e4ebfd0b2bda4fe441d8e3e85f4b0f873ce9203685a56830fde82d5c24a8e843d05ab8436b2c6b09757b00e0e8ea713b5c98467c6f62de396fb0af78c344f54a4569eeacae5fbc0cd230aa131dd5d89d399643c009a89e7b05b35bccf809a34e3503ce1efb87dbb554d7aed64658bd0111507ba1c2933f2a9526405bcb301b1033e22d5a3127cdc98d9db6196257c751c850b2482439b77749f34e6a76061fddf4683c0b6cf41146df00bb2eea4976d0d3ad9081d3354bfb01e8b7b256c90e636b3321dee024e95a0907ab8536565a500884a15e95b98adf0ea0e0f70f825e33adcd29b27deb62073539e0ba3759e16eedd1755cff0eb2b5ac6ce78090062b65ccc7c9f0521008de5e91e3e8a844969dab1cc0f3c62d39e0f666c10e1d23b4c27f1e34fe3a589f969c1c02e7db83d785a6eeb4817ff1efe7f75e445f22d6fb9220c3bbee804496f0f8cd80f4086bc5100c9249a6adc888d424828abdfc86687ada82fbce8596b7221160ee8e6e65b9326887667ed36691a343b6413e353f7abccdc876b469b5af3343e54531736f8ffb7fc41ec65bad5c727932681399ca53a71c164b241fb0989e4271bb385644491e262fb4c89a1a6704a3855c01d5fcabfb53c37f26491887754801f8c5c92db221e5bed89c82ac0a9559ec729359e9699c8d8159405643162eadd15e23baea6490a1af61ac5f3a2e9e879d214ae15e22090679a28bc696ad9cb2eeb17c57b6b6bb18e01e94a21813357918b10c75220f16271c00a280b9b8ae69d6ed63053abe38564489c8005e35ebe450ee1773028d63d70d6ceee2e114e0434b6a48513d0f6ab81fc7474c04c5d709ec75a8f825940206c09eb4efb0e92abcb7c551b4302db6eb8830b10088b298b9136c42ff7d348acdc887f7ffb97468354f28897e958d7e356e6e84f48790a8ab848ed4bf95ccb8b3290e7510e0a56e20485587f18548c5e1b52d8ecfeb5bc2d3789e09f5ff4a9cbd743eab07d7fce805258d028acd1c8952a270be0625ff1eed7f2ecc2cf1e70155479dbdf0c2622d973352be399f0f99e9f38b9f05470dba4c05f58f1e15c5b879b0c37140655543a408bf3afbcf1bd3659bfc0b2cdcdde4818102ce2128ab2df86804b032076d4230448d3940d7a387f86e4a76adc1dc100b2d8c4140f4e85fbffba80b553756914ee4d144fe0adb01d2d3016b84e73625274a874721c6d28c123586121720ba2c1f5d0da543276c21a9616822ae94c6ca8f615b07498fdf43a4f0cf1253a990e9f307df0f5c889a71987c2915abdac7e8a8200303ae49a4471b3c1cc4bcd5d6c67ddf2f616ea1b8aecf3bfd3b60d592becd4207646016474b82490967355e80acd29e516673815ac900eb81dd4f4957472e862824ec0aba03d38f8fdc140c53f7e0307328bc35460d2863b4182cec87b8e7b24d09be60e68b6dbd9d29921cc53a0213179717cb659e5bf7a2cf17a9d5209826623ab868dcf828425f1ba1113dfec87ad6936ef76de2ae33004367ca0e121b8d66209c4df06907ce9768da1e56aa5ce702d2672d04d098fe0f5503f150dfdd0c120f39f4f27b4bcbd877478653965079f4c26fb4d8e02f756ce4cff2d6fc120cf0386d85c51f5964ce3a5b316552afd2ff28f2a775d68dec2e7f8aa479bfa11ff19cde15a0ba48ce19237f9d77f5e4056030cb4d12942d24777a8ef2bb9233da60347743d9e8adc78b6ab5bf371e452bd106e0d6d0ae86805436381d40099a4188e562243d99d787214212df971fca14910a1c28a8f01ecdd248c488da94d338432223104a30fbd74be107d82c8640043395319404f4f01394f88aa02123afcb243073291a2cb7a054c299ae514083f7480d4a7bc7f7b3ef61e22f7a5002b50c4e6ed499112e9426a502a33fd41588526c8c909bbecf524c3866fb990bff22034b22ed262a660a836206ab6fe0f4f10538f1958527c73c4a19c97e03a514e889b20f03cd03ab41108fc56c55f715c68202097e763686d6f1cf6515d7aeeaca682dbc6616e8099620105943f00427c271cdc8d5638f9c691d8fbcc6f452dbf8b9c0bbc4579579039a05630b2f02ede646e92026f08054523063e9af8115f0121994f73aed290f19beffe2a5839bc0858a804fb2debcd3217fa1c1d3b08e163871bd18bc3a6f456e37a288fbec4a70a85d55d299d808d1f90050511f617e513f004a039e054e2bb526c524cb1569ea768f3a60bcf181b14320385060dba61b54af07ecc710ae9fd0430b09f4d0c36c793844891b10b23e2a5e97e0afb54dc08d54cf6138ba536730729fa2f75b118dbbc3a812d407b26206e7591414eb076a838f9a1ad398c066ad846edf04e3e28d990f3dfe08bc112ba4ff2e65e5433a1c103014d040e099a714294a42867c814242e329e0f8093c05efd82c976f0cc1a3b737314286e17a58eac1834232e8e01c27f3003b6c1a79a2e58e54ffde00c93c2be732bce0b2b35600e07a0511eda9074dda748adf99cec7ac39e6ff0e954f584643eb2df0212d9b27acce666ffc2756a7d8413d988bfe701c58b49ac1233d71514eed4532bc2b469000c6f0aa77285e46770358e9d16cf90510ddbf2e6b07ea76e56737c8535bcfa82166b810fbac58cc0a41dfd2031c3d1eb2eb6c89b91a22bd6577a8f305e57fb83ce146345c031722596b7fb26afef4175f282df57df0bace42e24157706679b397d69dda4d26084ebd425716aa5dbdff78784f4bece97d7a478047eaa1d4609693b97a3564f395fda7ca546bc71b3fb3c4f3edb314d579316fe7fb1aaec614bacbf8c7c1d5120c25e316bc7eb73cf71de0f1fb522ff98f45e225600652c2ad36b74069be59583cabe22d384c780a5569b50f568c9783a35cf77c290d580441d5b49730249376d0e3c32ccbf2660945876cb0efdf502c52b289eab1fd246a6cb270604b29191836b775b410917aba6b51d21a1d730ee8452903951daa7e4622f4376d6992400663375ada3dbdd06833f19a76518ec9ae93906054f6c3092e575d501c910a9a9b58dbae1418b90bb2ae78b1b6de75f7e8dd3fe05987d223e5891a8603034be0d0d4f09a9db9248899b519f62f3eb9595e8588a4d3f727220247d5fe302b8624223f7e6055880c82d6a94341fdd9d5085288a0136ce4428403f8d3edbc61c462849d0121126bcd10ba33c3ca48b0f97d7ffc839db69e279d8cf4a926a9e5501294d4d13998286b60f431a99c8b33e3b3ce5004a4fcea39895b99957b1a4a1e8c94900871a1c9864d41379a6e41c0f71101a8a935eab29085b996b254fba32cc74dde01be4f1081c07a03fa150259697fe4c5582bac5f3cff015ef4de3352c0abe2b36a07a41c41ff8cccd1301b2090251708b1f870d36d4e88717c2f540ee4bce9978659487a854c88d6e40110120168589dd6000e038c97999bfd051fef803ad4245aada59a0224ad5842d1fedcc24889d6badcefec6dbc165f5946f2e43d361a7854856f2c47cceb7c24daff51d197674388900f212e20e7d821c7ced98ecdea9bf1931d8b67eb26fbacdf7634c5cf7a10af1f66f973e3dd4cd85e69dd5c8a80056850b7d5b5ac562d308cc59bd493fdbc1f3e2806270bf99dac0ab201d4a6be1641eb1abf347650db9cf4ae3c7122b94372dc07edee329b60e00e7c9c8f1d886bc2965bc40a394cc657998e8ad8ebee8843eff0a1295ee79cc276348e6a45b5da7150804354b6e3deb3e42680201cdf2073b6e363a19aa22a9caa34fe1893e8adc9da7ecb0ff9f8a97cef4752612d842d8b7844a0cdbbd2c4acc8663bf6a84087407236a801b5c37e3c7ac59bbdac74edd4f015306b929a4ba298bd6ac450e62c14e71fa3770cbaa4ef290bf44743a472a8be6a492aa78188f16671c13743239df15575568128e327d24b553870eec4f3d17924fd25f4782bd16a737a7fc8fe9156bb83be73474545c5bab38fd6252f02e23f2e2e4dbac025ff9633e7fecb7a37ed29a5947d75bc7ef736c78ff288b170f2f375df49fe6edabb3d99d280e14b332275d5a90eb028073ed8c10f72f083ede02a96937a00aee361700a4bdc7ad6d38c8b2c902a0e4d147d6619235c7fd0811c7850071c94b64f6e90770ea4bd37f73ffd2806c9279d62123967f48f6c9c9848715535ef19cf1f605b88e0f726fef34ee7dda1c63dedb329424366605751d91d93bb1ee3a2124022bf080858ad2ca30c1ee66907b6b87aacb993e51a53e87c9ac076db60e7a921c72ee520496f10c26808b6c20411972aaccbdc2545a9313f4c1cb7b77676ef17d7d36a360130c45616264ebc873cd95740678e7d3c8d153b40409c79d5b566f67ff5720bda3968db4b1d43c945e79fb4ca01b2b55e8c16f214b15ac1ff46746fb39b6af82d7710610452558e0eff10fb7eea518bbae0d7bd00844f1ea0bb0213971eaffd745bef6b30e73c50a63c35877db4bb98e7cfc03b8ad3134a912975d5d7f3b59d88bd835f48b9ca484529023a6228d32b91ac60c5ff94f5f3523b2a204b1f9d0d173111e59ed6e319fc3068b8d9bb61446b48f43da280c40be3da1ffb3320ae24450401a23ff7c1d6ba9e5635cb8880183ce47530b49b97d1ae04f835c15b422e4f749e65613ef588ddc39471e6de57b4c1b4573f7b9afd6e355ee6d12f4eecb0857dd76e2a63c03a8c4bb217ccd3ac290d1572f3c44f59f7099b1bd0b07502cfed2f79228775e66c9eee114cb21ad9a09739aa44f6825e46782c773bbb477f35fbfcbc2220e2fc1f00bd66772df5a721402f799893d8bac1fb91e6cf34b46d1ac1a9d949e104c06f54b522f576a04cef8b461573543218c36ec6145b17f49a48cbf83ddebb0954182464b1e9c18049d35235d8c0a48cdb7097278a7b1192e2b9e1ac94cbea66d228d306a2fe2a93130ea5070c1ea26e01afd602c831e143e9c2a8929325c616f903ca948f4b5e6f8a4c1779a49097fd4726dc1ebf8eb98bc59bf6bfdac705cf1be1ec05060a9b090e923b423792feb7311383d2efa9cf7bbffcf51a0b7508030c03058587e3e76bf03d68ac8351e0af9bc595e9e0f6e66bb0aece507eb4d78a17ec0a059d0fc58cc731f4b950af0686900929682a866ae0badb35c9cc655facc4f03ea4c95355374c9b1d7c374d9738112e80126bf563bf469767ba2ce49cbd0bf258b860a715c3d4aafa0c6d04524752a318cf019c24650a4499c77df3aa2b9f9cc91a5e9b9146006c9ae218bf0047656365cab0311fd82f8a456491aecb890b5ca66adce2a91636b96f175f43c0015b23ecdbe41a3d3780859b672404ffc40325706e1b321efc0dd87b06d4073a86121fd50d4234251906a1a0d004e91aab6675edc183edd0ff65c36f0f4eb5b2bb5f49813850dfa0968e745a5600ee5793bf478167767d3c9c61d06838802bc1ee938dc1bebb8846fad81e6dbc3ec20fa8bac07c941e1edbc5de235934184238b4ea732d63559a90e054cc556b4300533555a50b014ed5d054e7537d07d75d1fc51d1fc1839fe80a9dc48d0ea8b9c23dbc60cec15a848613e760737f0aada74d11185f14b8f32ed5b50c0335f942bb25ffc57883ead49ebe58fa2e301d8d0337b82d0835d22ae3d8dcbb9c60ee01dbd2c9e596c04a0cdff4fd7f291601ba4fa0b2e73e0d8b2c247c20079d39e06ca36a80fec6432d46117968540878a706aa8880dc564e43748ff6aeb46232016b51b334aa1cded1ab4d0fe5f0563efe94a6020c4ccbb532b328cc345a4d58413f95821342b0d8f1fb0fdf7db365d526f9c1a443c3e2d482d3ad611e462896085541ac3cc570fa1ea654868044a9046d3ab3d97fd20422b487c2bc84fcfe9a9ea1aa69f2ad5ca4be79f4d4697a324cdf5d84a920d3bde41ea20e5ad514b98b026beaa24c2f776a04440f96fa247f1848e8b076d92f97198c1c1089aeca974d4cfdd101d5cecc85edb71931ac0fe3d5d70738a226949574e3229366c6c1a350bf6d1c570395f18130d038f249c6b8cb7f40973d7c98977499cfe76d1305b0895d604f622fa78256b2cf388dc49529cbab87fe80cc6a5251cb3057bc60e20ff2d4cb84dc8e0b11c284353eb898e590f61f0a94200e988707988e87a60a94410366ff295927495069598cfc366e2fcc89a6d8abde8f619c0b1abb447417b5c8e1f921d961a120e76ea011132c9ffbdff13329895ff644a897278ce893cc26a178f8696b6cbe398190e7510c4a07bb0c5e81663e39da4f380feab5d6905cb25e147b4ec375dddaf3db4df8a29c41a45c27ce229ca3b51c9d92a0994bb29ada66a6a58be82acf51f125bcd9ca3342adf8c051759a22edc9d03dfaaaa8edf91cc75f874820cbc670c729273f7da75c3096c41401551ada194b742d651db3cace0d42c4b385af23d41fc41d82762234a008635b448f3c8efcdba84ddf5c2254fdb8ac28b92d6474e04b3994207e878ba27efb102eb25aa27222a6a2cdde81d3e28da89a34682d6857ea2b009e898eaee1688493af5eaa7b9fe56ead952a2007a4ffc74747b303578cdb4b2cdc08fc04ebdd7ee15d762e3fb32d7c947a4eb265365d22152c323bd323b3033d3e33d6b4c5b1f101dc33c7754040049fa9993eb4431bbad26ad6c13f97a0c878490a6b87a735e2bf1d9882615e822947574ad70864849b09d716ba227b50d41065f4ade46927bea4935e025d39f4c3c787543aea879beab4e5fce126a498c2075d80f92d2a2f91be83740c22c12f6dac0ce55e2cc8478ade7ba0f2412a2f51fda30be38a59235324a18ea1415e827d4f3554444c1e74fbd12ae27e99d8cfd0dfa2dcfd13baaee8c3ea259d70c17a63df1e0307e090f63048389cfb27184c0b2c1bf40a1becd14c042eff317574f4e75637bbf0b5f5124321a7d089d1b466dd5914d0e82a5c97d3caf1bb7a2d27b47b094468f3bb87f625499fef8d5a9f6294f8e3dc81d7c4fe046df7bd9f2e063bd4ab830320881f07b89ef8fb2c723f0f1d3ebcb7cad8977003ba87a90c2866618c7d49de2a138edb155f22b279d93870b5a2fc28e4c4122ba93a84d80ae2ed92a33a446825b640a93692dfd3cb9df9659e3383d1d33c42f4472bbe0996bf11dc4b8e6624d01605650443514136fcc1a547e1b60093411a633604a39edda8f5700ce14a58ad9581b7c52912c6c9fa91891fe2da87c7b796ab9b07fe18c589b5c52e4ed1cd2e102864e587de12b7f50fed844ad08a79c65962901ec27b50e6ef44da75b68f5677965da71cfc1a2ff9b8da31cdb4f4214eb85523fe1eba7946ef0d9c39e80f5ad898e7fd3f4df1c9b82d8f4e5cfe20ce3a729a9072822c569fd080051793d8915b549fc41c32208396b01841f305ea4fe2612e48b8073c4a1b469066fca563c5a68e1df2d2c472d5704418f7da76f318dc6dd5e2a900ac1f2eb9af007a800a9cd47e6855e351277663d121930e23c21b3426c5698f406fa52840ef5abc156003a040baa1a1e2d11a29e1bc4cc53ad63a69054c836655f7600c9a47bc9853debe9517d166a99c5cf74ef7a089f20bdebe6198e911063e4c530ecaf5f8d67159641b80eb3f3d87a0cd76fb41062e6b9f278ac6bf08fa6154895f4285ad32b53fab6d2d3267423a89ca8fe7fb3ddd2c1fe4868d315c279c64f3adc5a5be85b4f5a1f4bb9eb50aff375114ab8fb3d3221ecf3a24e7b87a3069c56d3acc14c28f17e3141846c25870ea5e6d19702039ebf5dc109b8ebe82826757c4c555acc924b889c410c6416009d2ce8095258758b07d01b711d2d5aa483971f250a8bea41f66b91d4d0da933213b84841d1107a477b23f0d89e18f11b57c5c9af8548a6a00100fc91b3587856bc9575ab61556ce0bb3800645929e8a6ac3e42033aee1657d4c090085a2570b8677f083049dad8a70c1f56d6bce86c1093d1a56a259d2ba68ed5eac3367c51f82a5bcf6d59ac4060d21fd92d3f91d90e5ebe4515c6ae39499195fae132c304a1252d7a2b0dee641d6d19498d8cd1ea451fc3760b79579fbbd9e3db674ef55fceee754ec2ef4e95d0633b9fda697052eef838f3e3fac02924efc0a44cb7b0b5c86f5f41072025821b922c50f359d4584e0c09027d15a213736cdfc17226f41e978c5512aca98e413795031c061862e0f91e89cdaa14871ed814cc259cf4092993df78f9888f811dbe1e1e9451d92fc48ebc28a426d18bf2b0223f7fb684bf5ea1263fc8e62d96ddbed8aa2740a860e53fbf00ae2fc83114c76ee4f0a9fd4882c6f83330abce71a64b27426f03bc18444d4a12b293a7dc900c47e3b16e279681408a9dc521b6beb3e302c1d2515a2368d4e9392a3b69132ee32d978b7bed1130dfe56287c3e3d47000ecff48573a7116b8ae96b3f7c6f264d5c0f33678e7010603b8e16f45d1ea3fc3630515722a679e5ff4e22900b9d7856361c83063945381d89a84a9341400a0d7a251cd60ce52589d69623c81ac12cbc8c2ff668f9751f630fcdbcaeccb14f426f68c8df4927526f63fcbd3c134a512384c9a371339dc628a4fcc2260023e7af178c31389ab46b9ab2ed3586f6b5d80654a0c77624dc698317d1c74925c710d2f691a8f8d13fe8e1b1da4aa383dd8c12998e11c2cd42dd2d6400aae8a42f4b7040fea50bf263cfa373acf90ffe8e663ef9a033a7b7606012816ccd18199e2b138fac84f4034c0cab2f06fe0a239c50e4e855bd0c654c8819247fc8e786d0a5040e5b4c53e82db768dafcb69d67789343c19e28547df8fb4e4fe244ef242c8e69da471a02643b3ccbdd60596c8c87357fc80c08cb068a83d55f417a3f6077da12670551de7b03f69920d58c76586ac358cd38aa0388ae1915d993562cf535eb1ad19954d9a2d2a8c3c8b0a22656aba08d455b4e4582be908ecfd175f3cbdd17f5e71ac9dfd2c3af4b539f047ad6798e77afb17cb2941018e175f8cf834fc9be6a869afa180779f5c0c2e13bbb397c56fa7d959b8156767f146e065b27bcf37d91a1bb81639420b9c5b1a73e553e5c4ca9a91bf87b259bd905a54faf64e5e263caabd2bb8bbad6adaa366915f5b808f2df818dd15c730d2c40381a56f5d67e37089f9db5a8b8c4da6868555f3ce3f50f993a2e9f3092ed41ef2133ca597bc6910ca863d1289f2ba1bc468dcb26b3a976dc3e0027de8e6d7652145dcb2d3abacb5850b59708c47ae2650935f309813b32e4536ceb115927d73171950bf352bdc4ad30ab6dd49be686aebedcad6721cb2c3366e54f5e3d5ac47d9ca69db48489e3792f5b9929bc460037773c1c297b865ff21ada962468b81bb530faa6b70f55b351e13f4d8f1a0f9808b11d5d5acb65607dfbd8f13e055a1fc90549c81e859829ee59f25bd19f229b28d3565596e2ea000ac0e0ec37053a72a0eaba19c59d9c38675aac05d11bbe37b5321d3d7420c7a62a13317c6ff4d05a55b93d0d8d41ae496f0f65a930c83b719f8cd90384413ec451c6f24e34a6bcd242222a630e8a4a19beb556d4080dd5fd1129536e8b612c3d6b42f64ae5004aa5cc3fe88d1e8283ea698f469443b38d123584d9e82154cb66b41d0aaae37dbd43a01dcee4f8757d10a7469487dccecdd893e45e62ec26d58cb0e1e7a335b768b681da34135961e9a97388e35d6ac30d1cd89d36a03013992bb328e590056e65dc57660aa25fbb59f7db0f808324128164402108f1882a88fd670a3e40b0e692b6c57907cd5502a3e4fcadb630e1618748bc18993bf50315178ac25249c3b832509b4467322a51f2482b91252d3fa3a844e093b21285ff1de71697a9c359a621f1c408d852c34e476c74b3b5fa5404e3f1d380d08ead9d9fa4e38c44e108c51fb31f39bea1196806a7fc685191a8832cb25da77749d2842739674397ded5723ad4922374fc92e7d139ff377ef3a6996731e3513bfad7ad77a15ccf8228c8a1f3587d80013881ff94e4a1f2ea339f397ff709c6c807c2e6a5284c31b0123ad346449a999c7cbc6128d10c211548912b8c823841952be7d358a6502f6b4eb9176ba18e3305305a9d134dc85371860c227ce5e429df02de72d08dac3f412f7e4e17ba086e733b0299202fe80ff232e645c78774f10139d6ebcc7122560bc746f4e24d9145292fa2db3ee039f86a26e4101a4dbe2527f49a82be52d972cfaf16077f45ba4212d54d9ff9f15f3c187158683c43a66241eca3eca1eb7cef0d73774bd42ec058b2d4e08352fbb2620d3e7e97bc35201c70ee5b499c350a6da974e5fcf0663fb7d079817eb9a750b573bdc014a746c9c2416ebb4ff4e36d2fc532c0e8d6a42d4e51ca64b00eab98816d536ed5c7ccaa206c475544fbd146c76bd1186e0fc113b0caf2110eb15a2666c19a017f65a39c7c0464cf0da2957eeb5e721998a963f573046245e892c222a52279abd1258cd6fcc07717e3b5910282831f54b68bf3d4dac20f0b5b2482148a1d94bec2cb0409d53987698c29e34899425f2f1635ab74911dbebcc9c420c3937302b5bdd82d97a7e07835e7f24f30af1201cea95205c9d442b051e3314a982c29f7ffcfce1aeaac1942301a58e383cec37b33943ad990b6a2613146e483bb2a35d1a63447cde15f969843aa3bd9692b2101f5770cd28ca15d81072e434bf770013a804e6ff5990d4d426da8525fe384ba8204983fd2e6f1b902d5f317ce518fd3f459c3e139c5aeffc485e800797257839d1c80a24347de67d605155f26a1c6c5b7ba222a2e35571e2d6fe995594c02149093a77f0333f227d707784c38042c3bcec7d10624954b59f43a4075ac5bda7a170cc38bd80760078bcd8c54195be49dfb670fb7e6b0dec7fd11c47915adec57253c26180a0a67c046122bc2ff9bc903db460021833a9c548da4d3d2cf3d679b8c7fe75ac658469b1dada8776d389267b20c39c3d0d01194ab7982036b2b46949a4a0b7671b7912dc7e58ed218264714bb47fe296fb741b56620c017a902fbe8492736f171a88881d3ebe1c453578207a497de7d02d859b964ea33188e0323198861483a790c87e89a7b0ccdc9bda6177cddeb5dd509dd8a1c74d141d4036a86485b624774240b479834e46c84aab05c0446471762c00562c30940ef49a18812460864fad93342ce58682ddd1566a3a2730b989d291d484a6c4e55dcc45be0051ba9abb879de23674bdfafd14b63617704aad1fc31d96c3599a2e36804c05e59e094ad5b10dbfc78b6b50267c00a4cba65010869bd5613d9f222fc28abc34610c6e9f62c8235c0f0a2ce24050c32044ae8a4e13f6b6a1a600e4a4d6c4ab10c8d13903740569f91be2e22db7bd549baf4b8245fcb4b0b9f1dd8463d4080168b6b371603a52a4924661479f54232869be05d4846902a504d12b6015a78899452a16b460df13d9d05e2481daa96243200e60c928e64e314ad083830b2f9483f71a7bf8f998bdb7689f44d06ab3e9bed549c8c7f7089c7bebb0a7112b345f231a0a8be42812659b41507d1c48ebf8a136ec7f9da3270a31c8b16e776f31cb9931727ee86f6ae19dbc441e390a8b8af49dcf276d21d670c1c6b45e24e0eb33600b71c51c529a4059c9c0315b8dfec490bae07b92920a5085a8810533c41ada00d745e2f2c9d8d088bd092093490092cd872d2166279d241b7676d7bd3ba15e6d62a59d3a17eee0ead4701fe83679c100bb308c9276cc9d12c9652fb78fe6010982c2248dcb1848af5e5a2ec88f68b1102ae4dd4ad95e48f95f93010fb61462aa40590a6276e02b832429e0c839d4206596ebfc90dc48303462cea247c6ec00c1382082dc552fbe0e5e610491d4e5eb558ea2c08955de1aa1e2ee78ac332beb5305b2e037f31083adbbc0e54d0fbd623d093aec2b63021cac281552cc1300d0ec43af2dba72a62e3a4eca689ea03b232467d2dbf82f683bf621953393ef5a00853e98e77ad24eede8aa8879dc0851e8ee4634a5e5e1b14deed14c90d610a6b990c58805009808c2fce172e72df087ac604e16d3d695449cc8cf81be21b3a2cf8e1a695df8c09004dab10e33369852ff8ddd7ecaf3b749e23ce1564bffa74069c6fc20cf34c00de9f4ec28df4cee886d5380d432c4b039d04353e6355b6fa27b3fa69fdcf83f1dfe15a1fd2fc95021598e0109f802b1bd6909f0a17eec20d71e1c7c534c660987763e27126474600fa058b9e125a85f106c58cfc587127e21ec2386e475d5c27211a6b5cf38546b792ac7ee7f21ae7e33f2bdf44983d1bd12a4e9dfc550f96f15a6ba5ab50a53a5407cda39aa5cfe1c5b89e67bc876dbab0300ab34f68e10244b5d900225771266b0bfd2388edd50967abd044522b0399ab7a486018161dadc227b8ea37d8217a26332f2bef1c041ef233279e458f599d169be85d4e1ffc7d566cfe954154fce091e80a54c8b2f8ae37c9d8eeb79f8abbfd428bc4a0ebec9fe50a2c9108fa49fb01230ada07cefce9b4eddc31cab0a8b4d5da12131d6edcc37a1de372163e41696e5f2c8a23c4bbf1fc526b6583944bd3308c7f49c400a69c2c634cee0e1de85762969cb5fdaf2a2b456e927e93475ef2cade8db1bc113980be3517b04cae4788fc7384493ed6725d353c3080f251e9cd848b7478ed266187ad670b26b8684ea74e63ab4dd02711a01182d6b081f770941199c941700fd0819d2dd43b785fcf0f90c810b67e02bc9c071c352e48462064522bac4d0f6b0703b24a27acac1aaf54c08f8d9addedad6cd0e93b99cc3ddc5fd9e0012b007febefe64d96580a13c7362336fdf40007f7b3031addaf49dfcc7c8705ffe0f6f6294300cb21238e49e9c9296afe62ca909d690d710794b77c7076880c45d49ab8e6fb06b34291b1db601ac696d29a82085d51a4465e35f5df966610f54d819528e4b95821c31b8d82d9e6fd70ba8ae633962bce011376566f8ce4244e020ca35d95fe382591092b29e1e2e0f62cb9b515b51e79f63fb092918a45ffd7d07020a42d8f4137865949ddbd0dd1e85b30e2f813bab14899f066dc18aa6454f3c67f0f2b3a13b882a054bad416b3a2e4f30b292580fad01fae98fd7c8f15ba960e8f243f45a271efc9902c1431a36e7cba1b282587f969de83cd6fe18a82be7b540010f63e10d3cd0b17e765df6b880ee7a9acc4b081013a4ce228b4c41b31ccc71aefee580bd92d8dbc9c15ce69be616c1264a62da039c50444ba54546393e97bb39edeab42f2bd60151108841c335e41fbf7bde5c98d39152565cb0374b2442a442bd3527896cc3dc0cbee6761d988132830d95154c3605cd5a53a3505dea47719b1f3e383c29f45daa8789d289b6d0f690d4cf80c4752c7c847a75dc6fb29493ec5991b99f2f64dd340ccac84b5131b2284b94f4ee881baf9269ca14ed79f22a60220dd646d82e1647de9664431d41a4571f0ca0df4572b92de3e471bd901ea742f2b2c455f3ba64232b6ff6224163e12133b59326b09cd68536833ea2f8c5bce176272d24610e5673466e5fa41d2b55a528a6943100d2c4f33c962bd96cfac3a2b782fe689e6d40db6d982909af88de25dcf107290b3b8480d6daae31ec8805c3878dbb9d87a4268b2cf0df1abd33b100c758c476fca1136ec378f51a6aadf99f7139c55ba3677856b5148e1ab233e47b0ada2e1f2b434f23c88af2e0a29cedf9e8fdc1183f9210234f0965ef2d0801436f7d3be2d7ff4d4922842ea838ac8225e6d635bea50d0bc600eb4ea1e37be7fb8a9264c5813916b89f6d863ce70b27a0b03f7f663961655ac033fcaf76ef235b26d6e7394dd703d7f98aba354b37f3cb232473432b829aec3d18b02e8f8317bfcb4f310bea396dec5245ed5b3d5c9f9fc2ac18546421983635dd3d4032f8c63f5d51d02205e4909ed460da32477de22310df606aa9e73184194d41144dc2e4054036fbc086eb0ea109a84b1f5f33b49dc18e8d2384a5daeded52dff169668bd13ba687f8a8b83e1bb9df4ced0d3683c1fc39ae177573bbca6c8577c09eead6cc38209f969c76621b0009b476b49bc2ff60d215c99f9d728fbc8c732826a8b2b0c75fa297f9f29091f1b95aa3074dae4c05f06423c163212fba90aebf45fb16ebe4333a88191a7a778d2bb2de3d0a7d3867127d590ad6efbbf747b2a3493d606fd1c4daf62366120671ce8d8c894988bea3317473a9209aa11716eb4c7c88dd71464de8cbd2698d0e5c54f0949073d9b1376e5fd1f503a4343464aa898324122a08bcc90cb2d425f7dd890f95d293c94dcda68548358c9418655a6a9a0c7ff102e416c10e38dab7946c30628523de9cf950d1f408e196bdb4ee51a3ee1a7798354ce2ab5818a461a94531c83ade93de24efda7de46b56b0e0a1965a2a902d75d63c6251d8c38d663a43424d0ac02372ef7299739aac862bba8f8e785021c976459ba0753c58014a379623ca81239c27eb02da0c62663aaf0b67b74f4e2a201d2a03fd7a001d894464f925b45f41114336483008616cd83ec045e4e3118cdbd57a29b4aa81c1e7fcd1a388a6a574b47b671f1dfe8ad7f5c0113d71af65e098aecf79135847c967f4329705d8e4fc2313b74b757754153c8d2d7d8a6d3e6d130dbaa52e43ef97c67b74724d72809622708cc7edbf2613eacd68d750cfc19095aaea8dca1b2cfe9edb8c38464f42f44f289180157aaea9bb7ec104f9593600e2c77685ee4fe1ed499d275b73509372bac089b7d000284e0323caf0394a36e6910bc938c64ad627b8895c3897a84f24101ceb80485d3d23f4ead73485cb564544ab6d3f603bb51a28d45782175aea10f96658504f169bf647e2aa5b10671b6a1f3792f698aa891f9e6294beff19503585b494c90e465f616d718b2b5210823be48c2eda80dd5d429e3484fe588d3f7420812f20f5913b848ae92de1d347ccf17778c6e2e05d9e62d55467461ae5bcb20d0e6b6d920f40468b20058680f833c6dbb5576fab8929aa4009d482504702f7cfc9dd8277e19ee5c78abdb7e7cfe6c7160a97672b4aeaa66ab115a11a212854b090e9c07f6b4ac24685d80ca8fbc56e508dad760780b9a5c0d8fb4464068ea5d6083b6490b8aa4a06e0431a63ff424fcc96b16448223b5b34c157ea45fd4bcffa46b8738c016a168b64cf76769a80502441a04ead42bc65620da516d0ec99c4a72a94057bb98ed4504e6d6e4b29698774dcd5085f9e9ba868d9b8219274ec8d706402196923cc0db4a21e8ae4e593f15e96736adb51ec5fa1f53f8185b51ae7fafec7eb535db2bbbbbb794324919040cd20bca0c3b93d4cbcf43b2bbe766bb7bee3159b43b4bde01ec21729cd2afde2be498fcd062891c23f9f3ba4c9fd31ddd36bdfee55c7e8a019eb7edb66db46a30e72f8ff0d36b44757bbad45495a9d7234a2c99399e0edf57d2a45cc86f522c806fd2293c618fdea44b18755d288ae038961fa0364210f1c6816289aeb8d72187da401f85dab03d4dd9f4233ff026cd62c37611c40f8880ae2c69630b551c17e6b8bbfbdc7dde9138dcd968eb0455f2287d2d818df32cebcb4f37fdacdfa6c64a41907ed7e5a8521ae59986308129d5a69f43dffb8074dc86afcddbc31168a98ad2526f8adab4b46985bd19c4b60fb3ac0fd8947fd789e348691536b34d4a9bb901a029ff2b684d0b1717dba9b0fd674d6cff2f27276fea2beb8d482009a4b475cadf087f95756ef222bbc50e76ceeae9ec53db596badb59db56fc94bebfbb6a38de26bc53232285a564ff7ded30feb992db36360be0d88a7479106b9f5fe17979d3f10eb96cdb2330862bdb2730862ad82da9904623dee2c8258a7ec5c02b13e6d94dd79a310787a4331ed6c02b13ed91905c4da64e7138875698b3b8f20d6a49d5120d6a39d5540acc39d5740acc19d5940acf3ce2d20d6dfce2ee00bf820d6dcce3448106bbc338d0c03626d778e01b1f69d6540acebce3552e508ff0930b5c128d8f63e2ddd56eb3ec2153b5e82df145937f80daa21cb1a80ad7405849774f5005afa0fc0b282b029ffc003e84a0460ed21963ef47cdfbd873cdbc6ff7d1f846595c0a6dc4704745591a0a57f75209af2f7749d82e2f897c0b2eacba67c7c556148d055f551d33d554815c2f6af31bfe15f5f9655a740533e0525057158d8eea2484a60dbaf9c075dcb729aba261db0307bedd7d4fd1be6e8f676826dbf96f77198e37eba4e4159400e34e2f74a6dee6ffb233b4a04b156ea234bdec0830d559d7167f01e14692b3ffdfce91b34cb386ecee3348cf5c8ffbc396dbaa128621b9efe3698c9955a6bcdb171036d6d204776d65481531b8cef687b6ffb0a6cb8136e56f8f44d10dca56585a48d4a6d845e7db43d4766c753187de511f136e53aaf088b07f73a5adf1c55a597951c000f04a0adedfdaff527ae0d722f97c31f93e6bdd6d61554af40c81466d3dea6b29d6da532221a04dbf93a2cc8870aabb49db1dd34181495e6e7cedb1daa343f7f5e3b73f8f5e3e7cbce76eeb69712762674633b7b9c9ddd98cf76ed95fbdeeef6767d6e8cb62e1537e4997d615f56093ffd0a5a1d0356a1f0e14dcac5c69121a8270f8268dfb16be3cef346db63fb1bbe27a04c617cf0d26d36fe1f381a7c70bb7d10d261d20720eabebe0afffef6a0e8abd16fdb8fbe027f7b165f917efbf795f8dbc3f8aaf41b690e516eec568838e4f1c15a6badf75eb883dfd33f78198e3ee370a7eb4ca2c8328edc97b817b92f91b81749dcd33d7a30dc199154e4461cc79130ff2421ee8f3e3f89fb11c9699325c4fb7487ef853b2149c550d79d233f0d7b50ed4bd66c6f9636545efa6fa40a9b77a77b2eece17df801085f7de40d77bc21ca8d5772748fbf7b4c76a0bcbb0b7ba0e7eec81bec9b3919bfaeb4acd9345bd6b87db0294f6d20aa1043d034b14cbec6a6bf836599903e788aec7cf0556a535fdd6cfa79e3b61147374c9a97f6292153cb0ab23de7e1027c828020670b4184ddf8cb5d840ef79ba620a867f4706f441010e46c22ecb6dfbd6bd32c62e3984514b1b7e74c3757b01d5ffdc3c528b861bffe5075388f04db3f573cc2e4087c5f7b5dae6badbd3b87bbee13d1ed6d05180c3202dff7377ce975413679e9f85f4834bb108789257cd3c6b65fe32c20ac2d4b5ad4d4a6bc27e991a69eb3b7cd87206e4070efbf01cb0a49f069d1d3a27f9316a9dc0e1e2267db1cd3be0e3ad6c276ef86efbd9bbdd4e6e26dc3f88bc024f5920aecb2d608f5c0cf92fae461b4e9b5c6ffba7bfb4203e5a45d565e452cb1ca99b79f4359326f7f071ee88ae5ed0f80b26abc7d1f288bc6da1a056bed11bd9dfc8fa617539e886ee32fa19e886e779f4d1ae673054d1c17863b1c698f58a21c88377b05a8e6b79a98542a26f5b946f704b13f6ade2dccc26adc729b8bae3ba56d0cc8da186d3db4d6bab8d4eea46d0fb73ff6877ba1a19dde941e455f5a96fea43b5a96de458bb42cfd8a36d1b2f42a9a8596a54fd14f6d4a202d4b232d4b25f1f5163f77e089a6fbbe94c4d2df70c7e433fe6af078bdf0965349f8f86c1c58da1eebb22eae0bc1134d3ea76786644433244227112f97d46adbbfdbba4e0685e55bde9f45bb9216d269b4ed4f53e2f2f1ec8cd3312e9fd74f8f0bbb41366567241a595c7930dc71f95cc594a7e18e0ae9459e863ee446bc94c9b67f0a8aaa1c7122a2edbf32822e339f79122f8b8ac2d0c445db99456293d85a114d494c0c51dc4f3f3b2b84d32f35920b698b3a4d4f1ac8e6379bcf36ba27880f99f9920bcd944c3831dcd94e5a4a2cdb44a5a686f41790fbd894c380a44e420ff2723c69a099df4e487701d51a10141414c8e9b12aa934da88723a711a654b41e1813882b587e87d6e01397db2596e4c68889744bc7419d1cca6849c43455a74dd34ba120155994dc96a91adb33af396162b8e5fdad918e2f0f6f50142ed21923ebb809c36d932faca866ceab44292d0623865c8a3421cdeb6b598cae87365a12c2b2b9c2e15191aea5456c2cfb585b6b0b08844ce738505fc5c5da84b4b0b25c14ebf8bbe31215ebe600e772852b0b4e4cff585beb8b824e1f46897fad2fc0e8915dd1365c9125f99d78c6bc6a5f9ca3eeafdaf12daeaea1014598584ba961ee2f7b93efd1bdb42bc748e1be2a5d3d01f6e147d63fa0ab943eee9e515aa42e848d6586c4bd9dcc36d675460709fb49dd9a2156d935825d76553342fdd2ef1d253485bf39878d215480df274edf1f3c34b18acdb28ba26517d7c64689c68a0d0f8e7f4b6519aaa688be44403f994b6086624ed8c005efadb010c01e1834d6d00084cd883dd2fdbcb0613ee70313131bc57be43915788ba9076e6250f44f326d9fe3789afccbb64096d09f155cafbdf98af54def1fba7681e54c81d704c154f2f13eea0fcb69db693157d830b99d38d1d77d216898cdd270d947a170d14f3a5beb1197d8564f43df51d62d1b748ea57625e45e65133a5cecc9bf408ba3d9240328ff508ba9d4202cd7c4ee99e20b7fc991087b75148fa03b17ea92bc5e17ea644d1de8c8c8c8ccc8d5996edee8dd9d94c88e366dba220313335bca4a9a99a93b648485b44da991462aabcb979b94939a1dc984e6e6e4c279a964cc4b7cf9548dabc43db7f1482f98b5da2acadb59612652e88d0c4b64b10826be3b0e912847cb1e27ee7d1b594080c7ba048e8f9dace9ffae238cf5a969d79ca3fb7dfe9635d341c014f888159224f39108e33e2a5d70a926e44009ae579d02bbfa3f1e3d0dd634047e0fd88834252d0d2dfc4496cfbf808ba2a0579e9949581b78f4fcaa2c0dbc742945581b78f63946581675de0ed63186561e0ede31e740580b78f7fe80ae7ede3176549e0ed631fda52bdca9bd82580376f19770cf38661de2f506f5e2a704159a518ab04069af237efd0b67faf289941876cfbd70aba2ad564b4f56fff9eb47543c35b1317a7c3db776a0302562d6fbfa3ab97b70fd295cbdb17e96af5f64d7445f3f647baaa79fb2c7455e3ed3f5dd978fb307475e32db9bd136dda8f60216e6fba11237e440236df8331a0f51bd1e944e057a927a2db2c7fa38627a2db2b3e0bf215277329e88aeb829631cbe2882c1828ab6b82165956f7b2a99aaf3a58c90cbaea7cd0d25548f3a67c1e48f3c71dd21c7190a5188a2cc53a13578843b5b9cfd9410bb2680138ce18fc4017dd807fd10ef89406e2693403be8656c0d7e805bc0dfdc3d7a013f018027fa31f8003800722013e48dc6c083cc0010d0082010b50c00f7ae527017aa50748830a0c69e03609688f0179904d053918ba10109b1cee70346c5caed10101dae9cdf62dfad272fb17ddd1727b170dd2727b1a6da2e5f6357aa4e5f635340b2db7b7a19fda6c222db7d71308c31c7aa33e8320cd2691c21d95cf610d10dc46a91d861c6902b4728f461d0d37be029a034f01bd81cf80ca05b4106f016d3ea767684fb23748341594107ae547afc082624238b8bde2baa245e3f0a87f6d4305b406ecd7a055de865e7d8d667d0d9dc3d3e89c4fe91b9c96b9a112801797108797da6874b690f00940114bd14a112eea8a4a37725839ac545c368a7c891d61c56527f0030b431c5908628c4f1764327d90a90b327d90e94d719bbaa0531044eaa5a611afd72b8336a834da79dacfefd5b3ab673db94adb6cf6babc7d2791f33c4f87c160f5d268b44aabb4adc23ec76eda2676966d62f73e5fe14bbbb44bbbb44b73992ddd6555e630efdd619bc3be1fee9126089a4c1c69b2b06ca409035353e332cbc2463ce57f8408c96c498d2cd1a0fee66ac00ffeb5dd57a80f15dbf7b1aeb572351c576e14f7426c5ffffbed5128928a1fca7ee4103686d2dbf699abda44752a8fd2a68d6dd487dac4b11dd250596a4b75a92ff5eb4a7d15f2355a58ade1cd8e0500fd4f9add0b698a2fef429aa3cbb7902dcfc2f23478f66559a3229bb2305f8d6a5efaa0ab9119b4749234bb0d009d6bd0990b34e53f3283ae3218b4f49aaff2cc4b30d0568df7cf5fa0adf2fdb317682bf5fe39096dcdbc7f1683b664de3f7781b668bc7f0e83b660de3f17d156ccfb672ed016f9fea322cb22b9b6405b37ef9fb5405baaf7cf48680be7fd3318b445f3fe390bb455f3fef90bdab279ff3ca32d1a46007812007ef499e63900e0fd33c9e579ae4aa9cdf8309a529b94a7a129b539bd8ca6d406e56734a536a64f694a6d4ebed494da987c0dd171b40d627f60a7b04134681b05b5f1b7d1d607d4c6bf465b1a6d81509b1a9a529b540d5fd6f03332357c0cf930e4d32049a01a481b0380587a6b05e69edb35681bd3411cd1a73ba2ef885e3aa2ef5d3aa2968e48b5cd8e28cf56c8512cbf9967dbdf06d11657b70a0e4e8534c3dfbe6a13f5db0e8ed2748fdab4545820d82aac101b042b043b048b046b84ac248f816e1b64834642b6bfa78386d87e7b1bdb97f45239e98c97eee88395ecc0d069972952b192f7fcc2d517933ef302bedafaad97dae07ac9d71572716c3b7efc66774921f077a3d884ff5f4cf2d02d8a3f22c658638d6b9090fb10d5a0da8f688eded799e3c578db38aeeb3ceffb4e11932c60371abdc4d5f1506759f807fed184b515149960a288893b54dbfe9c65a288898d0967a28809cc0413454c30c14411134c1431c18467f1b66ddb7dd38bb8bb61bc5dce762eda4034f18f8a81f8ca1bddeff67520267e1c843506e2a5e31ff687da600c0407e11fdbada67b332bb0edefe038c857407c453ddcb9e3ae7b7cda1aa2addfb116306436e34e07b1f7da6bafddb1f6de4e665d7ee408122449b6295657e24bb629569f0d3111a4d26f57520435894dbbe7eeee74ed7125e1a58f4fa789f8002d0525f04e5f2088d9101b80001044dbefe05d57dd5e9d37d0eadc815667ef05398320882f18d6307395477d8d2a257da3edf17691f062b06b90d71f3ff880cae0884dffd33b7839393e39c360d3bf1d681ac26443a57210046560c4b677fbd15a0b96a5d7cf7177254510863bf647302cf6e22dbb60670b6efa7a640df214a534c423b3fe984234815ee9bd5bd723831ae35cbc225318d3be7420d6af16d3b0d48468769bc879ea16431d45ec213cdb06faf3600eb422cfedc4cf9ea562dbf7421edfe2e78fe77bf16998e37bf133066b48529bfa81da94698578b8fd7de9b3f891dfd710c7e74ff3d0fdfd47ae842f04f8df87e07f0f7ed5fb5c0df07d767777776badb5f6de7b2fc6186fdbc6711ff9e9120f91fb0e0443f0bb0d72218fc98726e10d458023fc5cc1a721cf0e9e718883dbdc36f991e6b68906413da344f2705b2429b5b14fa90d89346d6cdbd18b2be20b41fad18ba4094c5cc941fad193c899b547b6fdfa65ef736d88524a696ddbafb5d65addda7777778b7facb5f682235d24742fc67833afcfb6f6b7901c8d74c90991fb6e441249f1bb2d823fd2a4ef3689f4a3b7610e1249b4ed8fc8a06ddf7e4fc390c491498f34b7a06dbfd366695aa26dcd14c8b67d1d88f6b5290d141fd6a710b284dd60b22836ee861ccfb13e3526ec26dbbcd836a28d0cdb1914c77f1bb2ac6d665344beda685e7a41571b19b4f41fdac86097b0dd9ed46641591c8f87511e5b6c7fcbf1b0e125c763fb5b15d7c319b1fd39d736b32cbb9d61b799b5b7b6d1b00537da46e3b6ed8c8d0c9d87af1d61ee565bed49b7d7134e2e5694298ce9c1ac0f6d197196786444e249ac6ac9b64e9ebbe299978ec453fe1b8f9c24545b4fb9e495e0242e5a1e5efacbcb179e55ebb3cdeda72b7a8268dfdc7eae8faa7b133fdbcfcff6f3e3c1ae70d248a02b6667af130bd1bc7eb7ee5b0949a513143daab0b8c8681a3135ca1a36372a1cb0a506d0e5300ff2980bf9493bcff33ccf582c168bc55c36e572b95c2e279a15d16a2c2b2aa831168bc562292e97cbe582b1c183567397fbf8ebd2ea908c88363bcff33ccfd7ebf57abdec2c66531e8bc562310ff2980b9d3e74a2d168341aca799ee7697abd5eafd789492c168bc562dbbf54e3c612671daab24a34abaff33ccff3f57abd5e2f3ba3d5dce53efef29f92789ee779925eafd7eb35b2b3d92cbc59d5a0beba1f58508d05719ee7799ee7ebf57abdea90ac12cd8a421a099ee7799ef9f57abd5eafedffd5c05222085a446bd57569e7eb3ccfb3fe54580daab12af479e7799ee72ae7a5ea72c4731cfa4bb34194e5ff388a625724d2c67789e37797c65dda7669f8d2eea5d95bb1100340836df28488613665966e52190649b3eb3ed214c58e34c78d34ff2f96951c963901cb66402db5d44beaa5135a32d8f4b30da0cb7c5573d5d1e3a5dec72ef7c5ed86dae34eb9a78db24920d897a329fc5b98a3dbd5b4f3066efac464e70edc7469670fdcb448da1904bbd1ce20b8e910dc79046e3aef4c0237fded5c2a819beeb89d4fc04d6f3b9bc04de39d51c04ddf9d4fe0a6edce29e008aea88cb6e74cb0519bfa27f0b4d31b9853e1aa7db91dcb264da6954dff055baff73725380cb3d55a2b696e8f7509824d10cdd1963035beeabaef4d0bc4b43f4cdb237cd3f218bd6993d89664df9778cd57de5bf20e6163bbfb9ab288d8bd0903e3a5dbd9f6db8834c590dc1eb433df3edbd9f61f59dba6ea7aa40db24060ca9a8dc336d6834f21a406d8c6b8d7a6348a18d54d091e4751a44fbfbaeaa5623b69f284782fb62fb6fa08fb50cea48a637b7bfbc30f7b14e2d83608ea19fee2a81b7dee407cb789a9f0d1670f8fcc22f6e877701eef473ffabc81360c010ff85bb87200d39fbc034c24b5a91307086d7ff079b6ed3df879b43d52e87d0d717cf6340fddde7bfeee6badb5d6eaa1014e5bfceceeeeeed65a6beded7eeebd1863bc6d1bc779640907e1e70ef4740907e1875da94486df853c265f3229955e043870e45024439d2ff71b496f9b8620c9b3ed1149c1e70640b7f7dfd79087277f6729a534574fe8fa6cff5a6bad152cb242eeee60f75364adc5971bba17d730decc2d08041d0885e8fdf75ec85337d803b7c3cf9923b9ef36c7fd4a8e6e87dfedd0439ef0c1b7618e9024f2b798cba4f9f994983437fc97bc2790ed2170ace4261fc48e7005126252f84efa451630f8ce22f93ccb4ff60d7d449f17df90657d339b22f2d547f3d20bbafac818dace71dfccb2be9a4dd17c957d3e32e82a7f5fce5fcdb272f6f155ce48d055ce5fce39bf2ccb0039673a859c939373ce39035156a9faef9c0839f8bc49c8e71032853149315c5bf45eb7d87dabda37eebea04baedc7befce1b58434cdebbcd2fe8657b324f8268dfc430af74bb23b7c17b7f537c41540a8a6196d591e357851df110520c13890d0d0dd1968b1dc23f385a6cfa2aae8e8e62f9a6f7c5f6377162d492deac7acc85328531bfeaf6e28debbc2f83e18824964c4e4c28a79411a5b2c2d2e202ce60ae1b635951418d292e2f2d302c9b9d504c2726a5976781d95749248dc2d72bf5b5d14230fb7f9a54797127edf3fc3b9206ea556a255fdbdf2bac7bd18019f76c5399179bedd3f64c2f96b24d9e1029a536fef56f4c06bc31da72d9f4bdcf85e23324bb462f3d5b6d2262622e70ccb39862776f622e70707b23dfe2188e65970543f56b43a5ba2127c7ad85f9af294bebabead407e38831c6d7d6d306714fdfe3be1f2ef63ccfabdc47963c50da36dc593940feef1790c9ef17409fd3749bd8400401ad754f8b76113a458439f09366edf1fed33ddf9bf01041406b9b089b2dce0d716cda1cf7b6daf41598b7868d3fd36ac351478ec0397cefb5f6f30573b88b73cee220d639cef1901255640b42cadc753bd8aed68dbc9b0d794a40448e347376f7dde78ca39e91bb2e578ef39087f36ddb66c7b3598c6fc883efbd38e4b9d8da2de4b19bfb0e793890d3a52944efebee429ebab9cf1ec8e56f6416114b76f7260e916d1fc73ece199e4452507af4808d208cf6799d18724789a7ec9f76685810cdf0f50a79384bbffd8cca2b2c608cd60fa349edb24b7d5d2e9d19bd43b329fba0de91d1385c3665df45e3f0b129fb2b218e9b9dc99dda1544330c4a026ddb0f81d80f83866c1b0ad9f6738a8bf5930b8ac9e5c4a5bad4b1e3e886535846f56334d58101faa7a13d4df6d81d69b26cfd5ab4a97bff455b219bbaefa2ed6953b79294da5c903a012c62c53970a4eefa9576d185b1e97b45b475fac1b24dc7a78f7bf84ae5e96398af509e3efee12bd4d3c7419625f334e5e96321be0a9ffed397a72e4f1f3b416d1899f8894daf193c367dac045ddd9a97fe3e30cc829f32b06b0c9f1dc61794855df8455be0d3bf33dae2aaa0e5112ad0158e795904235cd01516c24ba0f8a4b80b8a655e127d4fbda7ddd3ceaebc39b329e9e97bb5417cf2f5d37f5fb53c7d18da628979fa31d48615bfc9f1cab144d7893f380cf1296568c8572befbfa388b368f871e2acddddb8cefb32d8435cf970045a2db3470de4f25803bdbccbabbca8707070707070707046154cc2092a12e345ab6424958c0a954c4505854ae6844a86e3021c170e12383e384de0bc707ce0c0704824d0ea98dd43fe0a4d12680d24497305bd41e353ab71d16a449b86b6fda4a3ad209a9d68358dbda59c7018e253ca36235a528692318abe3842057165542a853b9bd57ab768a07f96ff9567f9f0e78951caa9e55554c484080d39abd8fe608dda0089a20cd29c39a51c31ba2922922919234918332a18e1e29a34e7994b6df2b3c5508e2e72c872602107519118b4f4ef3a511c7f3ec7cf1674b5438a215fed08ea82ae7654414b99af76c4bcc40265ed40024d59d68e1c45beda31cb21065ded00431cdf1c5f8b9fea3e322bd069975d809a65a99c38ad184973149ba06f68d46687143b827654b123b603099bcc883ac1c87623be1abdff0e226759dd7202ad664919c77067fb28c67143e1848ccc68f5490728ab5174209a57a50b627dab4d64748f8c112a9d99a7214324e385cc6c668999d7cc13333f334033b099d84c15e29d1152b1e0544493045a83ff8ca65902c5f1a7a169aea037fc536636e54fa3a7e101b08e0cce2945c60b19229999cc4c664692992921339399cd2c31f39a7962e6670668063653c54c6c4668fbaf8c543064666969b1bab4354d93a16ba41464105905192391400a9143e4165624659a0c4dd3b5195db3a429ea1a290519445641c648249042e416e410296b7141197f0777015b7c5a906879b564d172b66cd132d432851659cbaca5eb4a2d5dd789e238bed9e98b9f6e2d48b4f8b4bc2c4ba58450cb8b6b79b5bc5e6c7989fbd59245cb162d432d536891b580d1326b29dafef71c921921aa53f068434057c0acf85151f12069aaa21837728534554e90f038f22462ad2290fe9770e7f42f21cfdd296f35893c7d4f90940f0111e2463d4a175ef14b2a2d868a885b9caf192a3e0aece3a2d5c64812c6a35056f449d310f1526684680a2924cde96188a6b85f40ef828b215a3d1a613efa1883462f46a2118c713682612c1a6b63d78928d74b091d541d4d2d8b2be80d91212f8bc86c8ac8cb235ece8a5c52c61d453b92380be65d361f236c0c1a9f30068de71834c6462f46a2118c71e62b150b4630d4c622cbba19633423c958f3d54d188f5a19c17817c23d3a394fc838a19d98715233c1c0e432bd4c4bd0d2df964c3faa1fd615dbbffb7fb3db352e3ad382ded468365564533448686634443446686434456886a8cd16db69aea0344468e9bf0292346718e2c969ce88a43d39555490487c729ec8ede43c21e3847662c649cd040393cbf4322d61fa197575d456832a2be8cde8ab102a581004f44a1cf12111cddf32246975fe71e2659bd8e51a0dc9888ecc5e3ad78278bda4e15b10473030e1cef6be0571f4b98a98d4275afbbc8e1e0d8db618c9482e20b94848907c484d905e2418c947ca29245d52d0891067e9f73f89f9aaf444dcbeb8f2f56b4907a21d6d311a1ac9b639929146b29034c76d8e64a791ec0819c90524170909920fa909d28be4830423056dff95fa91e4a8875863c29ded41ad3a5fd4c665467fcd20b2555750258480a280c2091f57511247f4b0e0a6b67f9b34e748d34481de9c4332234453d83bb82311633ccf2bfa9ef87e3e1f1fec9b82a6be20fb09f957045afa97deecec78face11e96ff878e681e1157d4f7c3f9f8f0ff64df1055956016a42becaf15304bacaf1448934bb8d34459134c71369fee92de9157da7ca0a7a23446d869c5e12d145d8316ae3a23642a850b12068fb9f4873268534df8915b236235a528692318abe3842055a8a4bd03449a03727914fbb6cabedc61ac8e56534cda9698a782923b22977791ae2b8d92924cd5091387a93e6cc8138facf458ba34dbd8c0c68b5cfcc8056574a694b95593692ae7ab4a96a35a5214273724134ef4cc8e3ed15d265a3348c97f551f47b595f45b3e811456fea8f722c91e3e5ab122deb5b6db3b30d68758d8c8cd842b349ba521bab6b6c224e10a1e54022874fe9046dfbe7f0c98144caead4d6010a1d6a3c2cc1c36b46072874a86d7f1e5e3c2c51e2b4a2b0c1a5c3133afc9c40dbbfdc26cd6975b9655a51e8f0a3c313335803c1fc85f995fa2b37a850fd509b960fda82b9ca099af23f67b615302a7ea2d8ee82c247f552f9d4aae0b1554ad024b6ea08eada3765d09b31a8925d4463057ae30f436a9a212ba321a239a269663458d8fe31ddca0d2a7e563edbbf05f355016adef24157053083a26820f2470d4492053083ae68b2a0a5af68ce4b0a314b4be6502a28dfe875a0c6fbeece1b3f1415d488cb2abb7ecb0a0e5daab6bbf3c60f4505953282da77e78d9f09d47587a48b59b8dc32c6cce85db71fe95741e9710422c9df4871db3763d07eb639d2bc689a21174d230b71b8ecd34ace1644d5a61f13f2b86c4f0be2e84f6fd29c289a7a393eea6574a85f579da2fd7788418ad466072feb7f30bb9a36cc1c6a03b41a66e71ba0d5e40ab4fa1f6b204dfa0d1668f50ba963e84da75d364cc8e3ed91cbce0100ad5ed9b9055aadb2b1069279528b365565c8cea6b20da0d5a88d3590feabc9cea6eae71b40ab5376c601b4dab4730e5803f5d078233a1d98bf1ac867d481f95bf3d58e9997ab1d60b82c2b07cda6aaa0ab1c5ed0d27d9583081781ae7614010bf96a8790977809945580330a50c3325fe5c05dd0558eae13c571c42e73863aa148156a1511d5a94269951014ad8aa980a8463a04db839f494db3049af2d79ae60934e50fa35d40cf38e38c33f008a5724249799a27d096c9fbd33881b64ede9fa609b4657a7f9a22b4557a7f9a2d684b7c7f1a26d0967d7f1a2d68eb92de9f6609b4357a7f1a25d0167e7f9a24d016eafd6988d096cafbd364415b28ef4f83056d9dde9fe60ada4a797f9a93b6c637a2dfa491c2ce41bf7fa6a1b14e8ba65eb2c0ec8ab3327363c6d0d09774ad68daf54ba6d3ae7f92b22b0d6d75c694138a0a0a8f6868d719534e283474d531917f42be89a6d4a624924f1afdd52310f7e8ad1ec1b8472410499a2dbbbedc3051bb86346034d5a1a1371d52aba2a037fe9b26425449b1fd7780a1fab1fd5550d0d22deb660c9aba29438b1a0bc423b4a059255edb7f98d0c44dd1cdeca6d33739bc489283c8573761d072471190ec10f2d5cd17b43c83a80035cbbae182a69eb82992e3c757375ad0d27db649733aa168fbc370e40d0d894873ee6053f55f5cb20ea0d5de0ea0d55ddea0d5dc06534e38d4100200c4c99f6753d3711b0dae91ba56c72b1d5f5c5a58565254a36fd32e5c2c5e87a090298c39726dff0caa7c5c6683b32cafd9947fd789e238fe97a6d74c1b35cb0a8b78cadf88eb880f925792eda192ede192dc0364fb5ffdd5be259ec2643864625aadb3d4de11a7b1124f61daa56d27472e1c455e3226a94f6bb6edebe3cf58dbdf34c61b75bb69682fde34c41bb769c875dea6a1f76d1a7e79d330839b8660b869188e360d47a44d4312b5a96269d3b0446daac9c9a6e18969d3d084b2698872da343ca56c1aa68c9b86236ad310a5b269a8b2b269b8c2b269c8d2b269d8e2b269e8a25f360d5fa8cd6f4ca48891b124b24d5fe23457e2b54ae2b30bdaf6633b69d5ed8d61eea475a7f7cd326883f0a48d4e92089a9cb493132d055459a1b4a3221d95d07799ff70960ac816a21aa222a22ab2e93bac2a7156104b66dee349cc9c87f7a0f9ca23e2acfa7a157919791d79217925d9f4eb597938ab9e67ed71d61f6705720aa17f0167d52e89a8645cd2b3e9d797afb8a71f84afb63ff246c622222562deb069cdc38c2c2e33c96c38eb93a9542a95ca457a53dce3db1bcba45108b6648fe524729ee7b9f2e2f17abd5e2a1b2a2624168bc546574f4a1031954aa552a9b29d792168738ad79d4e22e7799e282f1eafd7eb65fa4e624262b158cca4be5eafd7eb7cd95910e1837466e92cd3199d79f6d585a0cd261d573a899ce7798a2f1eafd7eb45b248ecccceecccce6cc542ace7799ee7ebf57abd5eafce4fce66af1b715b781239cff3045f3c5e01f9892ac5f7d5700a9e17fa6cffb0abaf0b5ab1fd412e7761fbd78dcb4f6caf67fd1f13acc24cb01a6482d5980956854cb0ed7f42ab5fd4519a486b23cd87b6733a3b129d9ce318f29c36fe8a85c87dc542ec567691ed9e19bc1f7458a865f0be229d6786ed9f39ef4def07db9f7a1d16eaab9e2895fde2aed8c2a0455749ee4d948bbb62fb7f1bb98561fb7b75565f2ad7c77623dcdd7c7046e86cd7c742a4e37e81e5c2865f505950ae2b044ca25cd4e606623d6d94acc6cb6bfbe7122c75a7734b3f28e7ce162c754f908aab58ff597e4413131df115f7be4d78b8f7fe6aefbbee617c8549f33bb26f1528153ea27dbf38e2f3d99708cb506636e5f7e945a13915fbbe8985ca4b9add8f3e8f4a3a10312a8a2f506696e512e422e452045a3acaacd4798a94ce402e45a0abecc345c8571936056dc9fc80b66682d0560c10da829182b6481f44415b9ab6de25c8b25c4e9bea41e641fe917de41dd056f8fe190ada5aa12d1697a032c3ca374fdbccb01c4a32f536341f53f333350f43d6fcdfd7f792403540357f431f6a2e974ba7a7c607090111e356799677395d4e6e646df4e6f21b8e83fa52d35ac3f8353d34a411af3343e233c890f88c18129b0186c44ba88d3f0d12978124b1199ac46590d82747a70669c4ebbc90780cd4e692980cd4c65d482c06122ba136fe2c1ad3f018980c1c869a2a516a675097330a54ad8a36bb6c4cb3ac0ca329ffaecb305a860971ffb79961a71ffa04f3d24d1b3bb9b6ffbd28356a83b2e4f4e304bbe429487cd3e5dc9e61204992cdc4d29b1986828d345fd98a623ad9503c6a1da3b479da26a699b68969a2154c124acd1437d21c95e022941a890b2731127cc44b222bde516a8f464f51cebd83a7b63fe8abad92660a64765fee4d4c03b94b6618b50143345135da124fddef7af6fd5112fb7ec863df077becfbd9fed8f73f0b64dff7ee9b28af99ddee3ed4a0cedafcdd992a25db9fcbb0edf8e525e32744b7c705a40e7fe6f0670cae6a6c5ab36ddbe6f7dabb32b2badc7e43ecac750f42a6f62beb5453fe66b97700c9ca12356d795c97f3c699ddf63a8cefc5f8de0deb917d233a9dfc9feef1c9d1c96f44a7f37d0ee8790e7ed63d46e4e880a268dfff5459ab2a7abda960a8f34a25da94eb6fe70a7ab6f9e29db7cce5ce5ad22445ba45fa559a47f77dd35bd63d41b6cf7faaacada65c6553ee3da8e9a7c1cf59539dbc5516ad2973db5d4d7d6f844a277ff8b6aa426f7fe1bdd7691e6da0deaa3d92bb7eee367df3e7694d6dbeb4b3ee6c4adc5ec893f74676d446abe849dad903bb90479baa5d79b2999337ae20edb8d2f67d4408416f119d8b47c0acedb9bf1ac4376a88c365f76c4f75f2a6a98ecdfb6e8ff5b7ef2529f6c141dd03f7a64793c99d0b8fb3da667fba2788c7fda63b505eb1391a768e92e3bcdf7ee3381ae2a81d28b710dee3e7bedb6a980367d7b0fb0dd4a297389bdbdeb71d2cd7914394bbbee791d8d3f4be078a23cbf6e9ee9a17932603f625535bdcb680a5bc11af130485faec293928540af728dd811cd46f9f9242521195d2030b450e91939292327e8a367376ca73dc8ab85279b386c6dbbc88896cca7191d3a02b2c86c667504c0631e80a93414b2ff215a67979063e036de9f7c766c04b68ebdf1f9b415b2fef8fcb405b2c65d0560b6db9bc3f26036dadfcf6b2a9316031d0560a9381c380c7c034da9a798d683efccde5396cafede5d2c2f2f2a4feec35b40db5f14f691af4cd4a39a353ba460db5019fb4796df3ff42baac7c8b66d12318f70a09644392c40f00ca46aba84d81a81add1304a451f9deecb08ce2b86c5cd2166afc8c1a49934bf9cd04400d27dabcb162e980c3d6b319216eb32ccdb2ccd96c1129cf5548896dff6bafb601749a29d18958782432483fb8511446467ca72fb63fbdb18f2cd18e348989586c7f4a2263fb53d28fed4f4751d8fe7464c4f6a7e110b63ff5ab6b7a50d2562df24027b6cc84cf8c0fc84989ed5f676f821e6912039dd8fe353361fbd7cf8ced5f3f20dbbf7a56d8fed55e0dc303d13489a19cb4557f065b87048ea37150746e5f9d3783edef1b6912eb90b0fd7de33828b6bf6f50d8fecef263a46d2ee0b090ab8531b6bff5f3cdcd05dbdf72a4490c0bd9fef66a61fbdbcbc4f6b7f86a930f13cc2ec17a6d33415169a5d976dda3d8fef7da7d35b803b193d156ad020bb6ff898bb6b0498cb6eefb9f70b1fd71695685ed8fb1252b12db1f5f6edf07e206fbdb25375b9ad156fdfb9e69529ad5cd55d22456df348979234ba6c4bc7a02c3099cfeb224a284a3a78f79f8caf4be4a31b92733199471493dc5b225367d4a71824e4fb9805d65108d210b63e80b9c73db372f155838423665532a9a97b68ca15ac7d8a08e47bde97ebcec9ea06507b31d1668aa2bf2b2bb29cbff710c5df526ab36ddf287717e1aee8827f5b5892c3c526fc299972118b4b4a8b0680434828d62a32a46afe340969190891167fd9b14a926325fe5951a1e99ac797df948b521240a67be52158560d0954a0c14191ed9f6c322cb52d14640948533c50866593841a398af760c8daaa0ab1d5b78615158c485456058c442864523a190e8081546bf429a145109897ca0f8512289236437e573e0e84403c5fcc5d9f433fe2f69fc22887bc29ed0076f9b479d3064c790b3db623b623b84789c137eecf8b123e80353a0eea1470f3f3dbc7a48a2071f2f7be8e9c1b5a426a3832080eabc7c4cea61b4cfa89322754a4719f446898e31ccd033a903703a32608826c5e2ec8185916d5fe7d419d291893843bcb4f9d30168c3c74bfba5d61fa3ff61348dcf33ba6706e68d50e9c47cf93814c1418643173810e5d0440eaf1c6039f8283b580e413a429e59c2e81d31d01ac84071c6a037ec67bd53f3f2151e00ebcc903b348a637184702882830c872ee86ac7161f0e45b67d1c726882b25464c8e165592a5a0e305fe104e5e083ae70a4f8381207220e07a2131c8836d21471207ae510b47584aa0daa229d219e50f7697185155444b1d5d689c3496d70847c85bf256b11b54efb1904c1302cdd68c20dd98d2e6e34e106d1aa89d56b055bf95805b586dc90dde8e206d1aa89d56b055bf9b8417483e806d10da21b4437885641ad21ad213135999e6d3fa626e30ac1301c8d52b8cd64e24cd944bf40fb6824f40bf44b3f410f6922edc54bcf6294a47c78e34c6f5ffb6824f44b3f410f6922ed857ee9977ee9977ee9979ec5288951f2e114ce9405d16c99c56041af1f58cb926dbf85d652d30150f9a47027dfcf269fbf27c8e51e54418d5ef7e113156d49cd4b25346aa3630cca0432b67d15186af4ba0f8f63d09b7c72b3209a2a3325ced26faa24d9f655662a4524904412431ebc4d9fab98f27d26a6cf26142f50141a0a19285ed816a5664f43a0a953cc9e4e3f65817af3669b1d0cd6b59c86548eb010e10cf194fd141d0213f78e23b443f355cadbdf51b25343a1f94a558442065da9c440a99d86708a9da72ce86ac71628d2bc1949b3f448f3516a2d244aed34a472a4daa033442586af7484d16221558eac90e68d0a699628d27cafd31972228da8128276c6d82183da28d9a99de89d25aeac71f878998497af9f9489dca1511b9ba281f46f1a887cff42346b5e35af1b5851d00f2c099898f2a570c7f4251328b33caa0d3a43eacda6a2e14c8113b46368c716a3d77d98c311c2996254d13c92d6d13e1aa671341c214749f9d4f8501b4b95104b691c41309bfac1c1c371bc4e348e9eab71b87696ec98f48ece906ac3ce18646c580cd97644eed0be104d1c213d6463c857db5befcd94d0b69f0ab2ac97b79f8a5996cb5b1f4fd97fbd7e7eb6b53058916d79e004e148f1753c368e508a06291f154d4506e6c093bc99b40e245eda4fd13a8abcb46fa27524711d336a43b463a8ded8d7f105bd394205670218dbfef7f2a136f671845234f8d9f695a029151949f84ac711b4946dfb98346fb689c3e108813842273842546cfb1947288a6d7fc311ea76683b4aa88dfd1d43d5861d31d01bbb4306d2f46dbf149acc5066d4a667771f82bb4d93d9b66f323381a1d641d23a745841635b4714540714f4c72e7ac217431d8c0b212d62411f53c3e1f24902870c68b0ed9b610bb67d13149f8926dd029664776fd22d846cfb3dc06c0f413dc47c650296429e229734533e45641a88064c048a79947067fbeec7573b86bc7c82ae766c414bfb36e563b140592a325022cb52d16caac85738415e7662d0158e14e64d69fe68f2a66876b46dffa47514511bfba1abdaa02aaa373bb648b2ed5b1d61d0f2865a078ad6f1436deca77c2c4b07153445061aa4a259960e28684a0a26f4d8f6ede304f94a8713b4b4b36ddf449a3754d8f641d22cb9305f8b6d3f93e678c5b66f429a22658215dbbea77c76126a5edbfe977aa7a671b81c470fb5b1af2aaa36e07801bdb16f9500836ddf54156d4b2275c0a8cd4c34718446da8610b5b11fa3e919a594a8148b16f0aa9e656a4600800000009316002028140c87c4a2c13850e3d03e14800d729856564e2608e45992a5200c330c21650c00800100191818481c00b0700f9057d44896031ce9978412c352a1522c12efc191843f78751a7c290e5e74d8f9d8f1522462c98e12598657979cf36e2faf349fbf6338cfd1d16325ac3500e8a62316b1d14f0353c94400592dc6da33978eed09b8f3a176265145749e9de3d981e8723b7d2ae5944e2173336ec3d090aba66b10ff59304166c553a3469c9437798edc8794150c3ca75e20e05f39b078e179d5366b395d0658ac743adaee4b417691f553118584ddd98b6a2f887d2981677a8a49ddc60fdfcc2b2ffddc2a95caf5c0c833ad595c1f2134499a2f7e2f405a20bdf9a4c4e8843f0aa6c7094f6c63981437171a99f1c882f07b1c07c8154653a5a099730fb55907fd5e3fe17ab9d417fa99f0b31c6b6cf9939ef13d28a2a0130348532773f1c236c2286102709c200568f3c35214586c7d97d02649e7b53500d82e0a5c9ba5788d4de9248da4ee27c993c4485ccc0be2289ec2a2f9b6bdebdd1f8f1fea8b23dc0e77abf579038d64af88b3d94b01d60d43ea42d8bed594500c40dc1a717534e2e143e4f4b14613c0f6a9d1ee31acccb60ca107ea536a4930a85658ddb194b0a83b4558a8826c6c43c81f2d38b5914251323469dee9967b06ffab12b4f4aa11947cd12f16b12e1864ebc336f5911e803cc3d6e5ee53d0c7f533061adaa91c82dd0e1dbcc2d294acf04303e3fc78f2504baf152ebc4d677ed63e74317d95c59116b084d0d2afa5be6eb0ae1da2625de0061a6e65d86a1602e4386dcde25d4bd7aad4c3ca01bf6dcf56d886d5b17b8370c96a37321a99b45c19057626976bb257548743b2bb92eababcb375a6572173014b13078e7873014a2777c6bab476c7781a28ed75c26689884c503366a1fbc3f512be41e219a8cf5a2e7d1d2f7f9dafeb31c1b9c4f8fd59a45e7a6c0b622e3c84f1cfc15ea139c149fcc65bc9b75ffa29426c4e710257b1b9d5595dbeb7f2942018de3c48447a6c72e3618cd0ffc48d58177cbba42ec0582a6eeddbafd961d7198f759abf4d8f5e94184df24a3e742bf82fea424687565dda8d0b15a866726323f9a4d71d838f3ca8f9234eed3a1e411bca1a617416a6a6408457b7fde17476623d9c8da4d3110eed5d7aca4f5f6630e0fb2f8070b877e4e72f9edef8be28942b79914d8fe4a763e1a411fd71d0b5324f32554ed4b504e5f8f8269be8fc7bd9b207396a23cb9020a13c69a6848883ea9c13fe095082e0db10ac39621bedac746a23504902f48e3caee619f5968a4a896c162223e05065571591c3b92e4def6b630ed558fd5bdbfe9baaf0f42e3c19f95b0745ac9a0706ab71d501f503380ce40a757f6ae1e0a6e69152e6f772245565ed67cacc830ea06941d915f49eaa85330f7562b6dc37ed31c05f1d9a26af009aa674c6e2141b151b7dfb5c3b1bdfb566d1b9c72cd51a0805b239e80e8cae6ba8fbff4e035dc81595e8450fd17e63402a6e3455029aba93eba70a76bdd64b3ac188751594a7623fe99a83d3bf792a0f2ea95a40f02ab5ac1ce34a2c02ae9d126dfaa9219ce55417dff5c250b90ea09ebe51ac0d0c2c73aa22f526bf28edff42c8a9c64f262544117813e866519f27cac39603c71f46f9abab9a305bac047c71a52dcaaa2b16021b6a5aba4d7851864594b8358a35d9409f718f9e7f3a215bd7a459a11ceda9db20a567f923fc1b3c11f6abf3f78073c7a34611a20f813768d67a8d934b1366b01a27d3f880ea727b6455986fb6500c3f725e87f8ce3fa8ef5de85713a6dd0a36d830d0f24462004c20966b14ea41d0249b4329c7f6c605fc385441ab7a3bb7a7dc24e4db8d4d756a6a343829d526e8f16b0e585578b1654bff0f94b282fb33ca5c9d66ad1d35b5377162c06b5e5ff55256a0f51d579aa5d6a1756cebf7b213f16d6cd033fd6d9026ab25454b17e0b43363efc55c8b5a31d795e1eb04c6535623d9e0b193522dc8e3bd22af8042374721e0d825decaabb98dc131f98aa393805a1a56bd17216e9418967b0c2d92dc485ac8f0c86f6c19422d28657490f261632693b1d643c08c52cedeb65ce60ae7decec40eefdfc25fa1215fda01e820c52671a26e069b9291536542780acd18e361bec2fb59d6933b3c8f42b3067e9750342a9477ef2b75a430e5585e828acb4c25bdab430f8543d2a831709e7ad783d6f4e2916f7549c47a2d551d7e1cfd5dc1639a79120f1eb88012d704e7c9637520bcf09fafebf1dff5fb3862ff55756eba19eb8d0c18258db0b014ae520a06136ceca5f8952419bb988b6e62547c63fa9fc02bd84a61a7efb278d235def88375ed02b8e79822da0a1117a7b8859aaab431b346a33127fb947361050fb98e01e4f445b5b7651f1c724f5b0ccd6af25fb0645a8eb62410d72b356c05670c5bb8e983cb5ca4f71f0912be2622b3b0826ad046e397440f038ae08c60401a702399a861ae9cefdbd9e908794defe3f92f26743a1f36ed784b4eb57f19ac711f6815265435fed2246de3e2a151c5104b0db1e44716e749360507b809da40c1729f1c93c6d5440ef3316fa1e17e62ff4e14e370897507509246b141b965d093f7c9c57dc64c1418cd6a4cc38c9dfd0fe3e39c3909e26ea16ba158a4143aa6eafa15b74b60d9d79189992478eef29283ecc5630056bff4b50a99956eb215bcb64c8a01e3116743bbc6f0950a5117157ad5275d80354bfc45125eb85c38b0e912fc26f30553e441e12a1d999c09426e8796e4361914070fb7c57b7e972a1e2cb916396597bafdb2b5a38f170b58d7d808c7dfcf7896cf9d6cc2dff1298f17c6e8426abe4ae557f94861a923aa48cc975d52340f196410d21dda014240c85ad5b6700364c5c14bc42aa829dcf9acce46a22f1a8dab96008accaa270a2318bda6d0d4d787bc539abec7bc246842c86e5685e1d1cd4969521b53f5b233ad7815c5e1ee6b76416240ccc92702b4b8762fee2531988cdbe099739c5c12db2eb6413e9dd9e9be7c7f51308b970351d77e68f1e26fb878539fe3bf52f1f2af78cfeaaa5e3b57bd13a22843ef41054c2b0ca487e9d5cbf44297d046d7166fbc148a4e9d354cc8795ae83c9d0467e0926802114ba981aafe5ed982855d2e98400f0582c52d195e7cfd4f5fed8d575fad9f9072a1f37f0b9425a001e76d6fcb2734c487bdde42d7bdd67ad93b5216c35356a028d1085907b61eaaeca37adb5b72c13a41074d63ab8bf9b64b0c38130551e706b9e16c6b062458cfcfcef372b9836f5ffb07b221b289a05913f46d8143393becaa2562068144c1ed057a32f582ce4bf7e8508a44b56a329c8f4a709304763cfcf8e6e1986f800e5f4fdfc38837552f20fd52931043539d95f6108dd9c9ddcf621468a1132d929a8cdd8f7f9b09782f2dc09ce2f4df96f55fb5dc4e40a0195045332027cd807e9a01118df5d40cec898f9ec19574deb4e8e5f9af7232fa2f9cf5dfa6d03858bcc607d35472fd6816c5743c883a6aca8814143fe55ebfe7e81a37a30ece8f6411f9343d99570fa0e01832d028745d38cf1d368d74747dfecba36c61e4859cab72c43f78ac82ff4060e5379f2eb08a1f029b3e5af4dbb8d3f9b6484935230ff0c77d5539c08c839a78d39c237a83a866137b1badd7dde590d796c5bf05da25d08af8072fab60f4a13c521d43d0887ba0a2dcc8c4c19374e7e19987fd3a79d5c08184aee8b2e1fda3430ea8fc28d42f61b6a92db8b5b189c2a45107145a05c307b42a35aeebb6607746ac574100d2b7d29fad7b674098be4397672d6556ee7554b08ba6cca7758cd71b599dc5c79af17e645805bfa8e81cadb9286e39fac934f4f9236b2b6d09e9f617f232d2128dc8470a0004bf87441ed76c4bbb363da97adb0f863b61a523599a5d6be934fd7794fb297de80408122e12fa8ff3005a94e0205fe74a53f486c94c5dcbedc639da62296e52234e07cc0251edb17fc90045ad9672082ba5386c7dd1abdb121d07bd97cb43c8cf9e8df2364c1089a9fd2bb8c9f4745f8b651ba00105f1ecf5eb67d014d16bb0dc8dc70b040b8b753caae84755af65f022ffa73ea27eff3aba930ca75d8c12a298cdd93edb2d67d2626ead45714f3bf847b779cc2c6c978bfe50e7b5f172dd3e6e6decb660e6aea30c5a7132b553e71db357b1863552a8118e2174595b2e1e1af56f5e011a12ab013f58d0bf856b02260a21dc98879f6183c7b16b3ee1dcac90f8b1a23a5dd4bcf2576227562c2b912012f1a575afbd3a9d2956372bbbc3c18fa0cfbcec7b6b1593ea53fab3cfe539696ab7676ea3efa48e89ca95e567af7b37be17bb9ba0f7d76b4bead4c0250a3167c8977a1a52f6c345382eae8a19646f29c44078bd5f6ccc0a8e5162d8ba5e9c29459b833ffc4ca1668af02da031d5f716b8dbe4e9372aade826f4a30eb58a52f0c90012d04f977ca29cd3cde7151ec77e56c52ed6062e98f9d59fdd6785534162b8cacbe31c29beecfc3c881ab3ae2aa069d9ebe6a97103608371b03a4daa172b9fa3206cd1592dc07a6264362c787b8017ddc77135000111c260e908893e1b3dafcced95582d46444981d4f0c8cde99fe4393a0ae9c7ff15d39db4c66302ca96a19f02b2fd1ad6e7d28100b4c393623c170c51faf841139d920a4e06cdc5e66aa94e88ee23c28625a565e5630635974d3effd5cbc4f57a2e2c3d36a020a4b9089e661f18ce9064de015fdcd55414b98d5849494a3b4e5d05b79c79a15c9c846486bc21f9f96fdd813947df0a49f6c7cb11f3cbd0afe2943f7ee7724eba17cd12870fa2506cd97bed10ec227ef14900ef7227dda4360b3ad8c5aa1f2e59d3aadb7c709ca907167c48a8dd95bb35078d159f37809fab386357b48408f212c1089df076a3608f4a70debe4eb8bbb2aa7344f5d8495609964fbb52613671acb1aa8df0b6b9a0ab9f84205b1c1d87a9a60355a4fe65f7932ce4e69a6c61053746e9e61e9c4b772511bdb6bfbb6b4434eadbf9cf4b8bc6ca781a1d8bbb5aca091ac6ad17e5d294c3b745208fac95a78e253a4721d7cf59800f9f64ced5690272ed1f2e4cb5e1cdce39e49af844dad146b675e8942ff70e1b1141a8928df6928067e3ab2ab43b8032a48f86e7359fe3f1a645a02b84fead0fc3ea0f6fb2be2af9043bf46888471258e55a0d2ed6f3bb165b05dceaccabd63ffe97a364248db40600688469a04bcf00ece18783d1da9cecc7e0ad00fa56827b498ef95162ae6e3438ae1e6fb6191fad9f9b26ff5d555a950a1402e8dbd7e63c7288b6a8432d8fd3cfca14d05f48a45fd1ac5de8c9e68991095f108196a2f060a7b868da01a3f7cbd7a3b37f5fd9852e7ca80b7de8c2af7aa0bfbcd57f77e18a423308d21b740e96ab4d7c7d05c3874870c885cbccb95d7b9cd87b4c3d85459436b6c0291e5f499d64f7ca48040d5a32f6946119857848280a718806aa62a067afea5dd1850d0e41e89dbf6fe85231000945e6964326008e8041fe3f1e15f966624538f41b585584c1f5524787dc2bee879163c5ca9ac01fc9e52289efaf00e9d406a06e400e073350870e27bc7095b6890323d150e2300a852d1c87194e24b8dae0857d4dab3dc5b43093892d1ed5cb445f485de206b1c2fc574a276e4830b28f04045f900083815296fa29e5603c9808e8964640110992c2f71d71c24a4ea0d50a37005bd3acc9ee09d379e9f5830d1423851a641980c3b8aaae8a6fb95a1279c85de65aeb82b3d93227298695dbcf4dcf3ff65f512b7286679b86b998438a1ec363c4301cae8430a738b3b458ee1aebca11f04a9733188e2e30de253243bd7956864e740a4048d4d6c3ffd5afffafed587678bdc7997082215f5dcc36ae4c03adfc8cf9c77defe808b4d7633cda82535a5d4e90f54a9dec90aac4736e7dd0d1cf43a4c2a827449b9a63b4e499d2487bf0f5985f63a05de02d7c6cf64759d63e3f816687a2e36542f372aadf5758054e3f851c517b88fc87e9b6d0f50eae7dccb1cfaef329509b240604e0545eb4d0c6648ad35325b34e4bb21c8af35ffb89c92b0fe77c755f2b2ba32a17655326c8f6d30a2527a7f66c3a06132890ee4ab27334e61d2750d9e356bf915b17d9d36a8a4c42f49778a3224d25b4b764bc99a5654d6720a8f78b177b90ccfa446a706f133e8678f50145690ce9237eba09d05905165207306fca7efe80bf3e96a97597eb19b49a0c0fcec08ac9667b5dcc92c9d675194a26ca5186267c5eb286d01d123487e0638bf717143012e630441873bac40d595ac31cf5c31ffab364a1d50d7403d845ba5b26ee3a967750b73aa7b67878143bbdf50a001a28fa2f4c84d9a791dbc8563f8f545e6883f77b3237d48dcc01944066118eb890424b673ec9cbc6cf33541875560041c729df86760bb54351e50ee9e66e2fe1b98642e0ac6db64785f989564e9ea619237ffe68ac6bad3c02906b4233c311fc4614c541c0e6ebd9b2799eaa528e91a370921d94b7f2bb5e223006301fd139ca7cf9b04d8c4a06701ecbcd8d0958714fe671efb857bcdead4dd1c376d79ace35aee8e6e8ee7a267b239fa08f07e0ad6a46e91f0438e2c0d911dddcb288b3f3212fb37e6c4e4ba8bd1209011fbdd5085a020eabd806439c59e7da51eb2926a6cce997c6f258b25d7c463585ca22d405d85ce7318f39cc3f1c2048efa0946380db3c2f3d7b806da4f35d4dba4640522c278c36c667d65cf29d3d5d37d8b2d2fa7dd1a2f9b4c1bb630a4651959d09cae977d178271998686c51af23adef2d5467a3aa95f90998a562b8bf07e31039b781a60fcbb445c02ef2b05a73c9d08aa5f3c18b784194e50bd7c5eb6dedfec6ec94b6e713e467bb6b2e787a8d1472aa16a39b55336e8914d4b4667d8a4d90365a95d1b7ce4c300df4d2678af487952f8459cd3a87f22c4487761672bffc63a0c2c6786c65d274fd35a6bdf837c3dbb42eea4e2f01e05421c9593ce24f9e8df7e6250a4cd0d80b1f05dd0eec5bf1b06191bd1bff259092242df3b8acf325920196396ed12bca1c55c16a76187b2e4fad53c98aeadb7956f6dbd07eee15d1c57f50a4b8f75036d30fc02fed6ab811336560ca5898e4c50357a4acb6c020b1ec94f7492392bbc8ac8160e5ec1f86aceba6951a5d9bbbd9d64f9e2b15dcc57bb414542813ff63be71dc152ba5aa762309404f3a86adb9176658c295d6773e139515f9db535950d387a24e94cf20c9c4e08828a5a06f763190121e4a8818051180872739693bacc4baba1c124c4929a454c4354dfd94138434315819f60a61e1619af8f87ab47428247f10190ef4f8f5f81b9b77f6e6af73de55e11e1024afbaf5dbfd1501408696aeeb70565745582f1918b4a679f6375d934f0644374b29eedcd43282ff7c02cc088477458e59837bb9764f2f2a5d6d32741efb75399053fe1fd98f570027c6b75c6f7fd28e6bfddff9415e38af975aa70c800e3ba25a390700c34440082b3fae3587f48b1931062ad685cdf0a2a1e2aababf222c1ba658099c96a400bf983a6b998d60afa40cc586ce2b448a1bafc8d424210b7453dc06d05bfa0a77e360641da34be00cfa7ce29f5a3c6f447693213f0abc31790669d4dc5653cbb6075ddfbacc1b96e0edc0e8a8a484b8d7a60b75476017b5f959371f117a3adcf91a3538f5c4e214a54164a36f7632865d371a9df3a1c14edb09cb955bd8d9b8c130d29e7e1fbe72ce8669a795d3fcc757a072ec836424aae1d7c0374e4eefb8c154d7872b032b1ee5b8c5f92c3494f26dfaa36e67394d26c0ea03cbf9a3bf46dc510e6297bd638765898740e168dfe63fad34f8a62f0ede1d8975da2c8206dc26888dbb84a4c10b407abc4eed3469e6fad67760e13d25cec9e3b353890fe3c3640c51bd94f99dc65561230acffeee4ccb49abc18ee64e1d6411cf288327c72edb32452358ea14345df34daaa868e544b4d199f659563de511779367da8563d5fe4aaa7c0edbd48117c257153f6134742b1dc08a9d4a5fd8ac20dbc3906949d8169fa1b1852b9073157f1608543190376b3e01b9402a7023eab8e8e7c825be230b789b9bddcf4f56869352e77ad74c2e77fdbf5c3ee4ea3cc1013d26a376491c6d136f4782108b8815a9bb0432f3a380f853a98d3136934f08a0f66d225807c681e24a7f744036e57337db5c028e67cf42c95178eb19a016cfa40580fed0d5da4985209a038320f288038215ecb8034e439418207600fdbf1bf457c0dee4f6ca48a64dcb6f414d31f0a2c0ed68ebcbf812f2fccf608fddbd3d825969aa2c64fde0757d61f20addd79e3c972a7f513c3f18e03bf08f6231dfd2de9de7b7b4b56458bb8d6ccc4523ea1a81c62a7e045ff354d69e63bce9035301c1111a31df36abbfcab9ab2cab6a91e711b3927746a8d70187c851e5d916d7dc9e6ad344163729c12a2868cc3f38ac20b87e5c078f232b738f5503215d5597a7b77b106d7e3f2c02b7ee1a1c97f4002fa343f72e1d2b2a88dcf4cd167c04cce54344d1aac9cb6e48a0bd7ff8ca04771fa2fc002ce5f6c35b45cf304f19b9ccb4192340f64aa92d4343413dfb7df4fceb35bb69d073eb4d8eb71fcea627410336dec6d68011f18b8dd4ae1ba1fde0234761a2e24e2165a21678022278f6ea76db968efaabaed3ab58cb05a22f92e85ace73a886142d775189748c85d506f60cec333067c1a71a1db9d152b20ec279d61a8846784ca757208a6fae2132dab1803f28490efadfcfe9d1dbfd4326220aebbb920a91e3bc44bc78db2d485050c9cfd1f4326d90d23ee2ebcf5c441ea07c6436b2de33843779c2cb130213afe69d7439648487b2d1639ae03b87e1054e71fea3bc57858ce47797e07b43515d0fb2e1a29cd8bb8f54387d92e747701a81bc0cd6c670a97d96c9eb60f69894f08960560dea6b4a2ce0e4c0cb0527f7402aeb81a5f4c0e483427fc954b3668fb0bcd6e1284309adb2afb909ce732b2a497dfd28674720373e7bbe0268e8e441e5ec6bfaf1142e886b35bfeb9378b1412955e34af698376cb5ad8a74c6dce5aa05950d50e13883b686a99aca8b4c03f4d6eb1c47feb106eaa68bd881a4ae1f82f76015e443d2ecc31b79437240418817d705b3a93f936aba31d9cf6ad6e7ac0ae139e2686928bdc70a3edca2a7bbf1a14934fb430ce6328ab72aaa9c06611e3a402ad50a9bfca1095e39ec3d9253e5ee7a87f876887f5fd79cbb809fad051cbb0055f74e1a8464a4e4c9865cffc605cf995067c9a3e7f786256ea0990c8030787b20b3aa146c4193403ee0c878a36be433bc22e0efd44a0d3eaed767a70bc7644cead2de26e0d76480c4251f4e96492123b0fe2db0457b0fb305edf44c4fafe939515ea23a79a983896e00a97f98dc7ed1eed5d21c5fef55adbb624ad71155acdf1028d532010133491383d208f1d444efd38749aa22d9831e22bd67a9025e5dd902004e951cb6f685401661e97abe4a93c3dc16769f2a2dae668c321f6b2ad7c2041adf286317fadc16ca7890a7abef979b1ecd04b4c54c5df62737a4934f3990d7704b3177d29849e9bb21500168a7043d52df4ef18d83547e83fa0c10200b9920458dde5613c648b80dfa73d2d13e06d0ee54835eaa171fdd343e8ac410eff713176ebbe362939cffc4333488b1f21876de887fac64f7672b0bfe88a7d85dd8eaf929cc3b085242bebd121b76a84eb526ebc4980c01c9f5de311cf4723075f1a8cbd61aea14f45350d78f89b7bbfb036fe20fca4b704363286f35663de3d399443f16172db211974c4544cb9959de83c16efb9b8fdd4c351670e2512f95cfb098805e342dcd5632e4a02175810d4c9758921d8e3bfae16dc1d2defe32183970751ce99219c24e2c64b6e142e0b3ad00708290cda652b8fd819241d5dcfc95b64b373ed0b13eacf4275f545d59c8e8d42e5b7707df115ac31dd302999884280fc707805aa6ecb99296ec0e6a82fb6101d7f60617b66ef63ea6466d73500d3029a14af1abdad675a59bfd9c941295c2541b84c61729802dcd6c82ff71fb47660c6574532045830e05ca04b977217fc57f4102de4f9b592434dbc339077f0efe398213a92443d5fb425324973f01c2a39001406eef65cb37674e369b6de23a0bd2770c6d842b1a5d7ac652f4514813c0cbbb2f67c8d809400ca43a32fb867b683b7f23a754c792c345d34528b6c8aa63e17cc53bf1638faad8f02a843b43b1467334d2d88e0ed277288b2cd0256951e00fbf6293e7a1bb83d2fb9d7a5236bf820ddea769452dc5da7ef936c07c3cb7f03c8c82322f545ef321c74e47995e0a1121ae9cd6836c46815a21c1cc50ccda94d6d0d01fc1b68a06dbc4830c9362810112abfb5654a9f4138af4ede0f2a7ef07acd33a74ac4459231b0c703408a5348836375866858c58b51dba327481132a39a4adab40067d20f46de0a70b58395578f023901303e77c9b52c74e6ec41229889736632d8eb89fa79fb9495e46c4b81b12acca4f1a12668bc48923ae0825b7573dc48f91e2c29241c1088026b93d0697fbd4564db47233b56d74ae2c31b714b68898a0e4d387278be776bc46d825082e9da9efe9391e00a6ce64093311ffd111084f6c92b5e170380d5589e0ea0244383e35f2964146d107845bdca4f4165062672fceb6358e3b28d23f68d56a00db5b0d9571c0af9e194aa39db054b3bfd1ee87837a746ffc30b81f0001dc48c36fa39da242d509dc5c791ec478d6ffdfb41f863ee99cfb6cbe928b037fe6903697851b8817fc6bfe184da9b469298c5402fe29ff4d351cd5d1fc44a2feaa4216f061e146c4916005383d71ccc1cd0fc7a95411a495f2982011ec268d361feb1c592fdfb85d09ca0a43ef500ece9f3e87242162afb363e715163c99ca8ad9542185dee11fdb48489635ece3c6e00807349f1d938549f8ee3ab01402e676aa5ddd20cc3784d956a591099635287058077c660ac639afad78c40f58c364e54e49b560212d56cdd4b0d59486f9326dd981319574f51b4054ffd9350d8cfcf42a0b0d525a33d27325a886f56c441c6dd7ee423ce293f05ea10c4c85890d8471e192b2f138884700573d8ea21b3cc000329707c9713eff37177ec03465e6e195895d5ab3744171a4914cdbfd960ce8f88bb6e8d1edcba96eaa5f19b177fb10581629e435ccd5cbaaa328a645aa8b0e20e6bda55a60f99afc98cf114a95f703405a1e57e433a87e905cdd4aa5aa5c6c2aa33a2b30489bdda0f7a98082642fd783c52a908f838ee2efd1445ff4272a20be990dc93852c1e9795ed93d6dfd3b0fe3ffaf7bf1ba0aebd9a29f3979fc342cd03b07a8451ee4b248f89afa32a8fd1514b44321595f7be8f0f2fed38e29f56f37be48fc6966805c431989a858640d4ff0c6ad8f92627d642f3a4fff3fb41d3623b6a932ee1b37d36c4ec85809410435ef12adebe92f6db9009c1a22ad03998935f93215338b1c08d3770538bafaca928eec13b8af124260f623b89502f274cca8e82775d6599d9f57461b64fbe77137597dad48d971f3c66ca889b8516be230cd7f798e947fb13517fc8391c7813e1b4b9764681d68a88e672f10c64cc5cbd2625f3585fd81d8080420e7f73c9aae26471b743a990a1b4cac2a5269f392e43e337fdd0c0ef1d2643b7823d9fb17022d1fe08710f2d236abb1c3253e70041b010a44dd5dc5f0d1f4855d5af5d7bb598908423eef949ed1dee1ddd7349a34733828f58ad0885daf019b1bb3a71bd56bef6877ea5de59e0c52836c05a911b8dca7c12acd16a6f1da742c962a949b3b72b1a8fee40695e74603ca0631468991e55d2bb7c18695fdabf7baf789af9b975baed6e380481335ac5b9eadec7fb6cd5db30fe5ba506e6c511c101e32bb9f6a44343a1254a2faa10b7516fe14fd2e1ea63e01ce49cbc2fab2f6aa0fcd76cc53cd7552e63202a746aa0f360f3c3bf319f09ca091031cb341d4e5cf891e605f0ea549a3f1a453a8c22032a4d1fd17f7e022c42df751fc5d9b3668d3b3811bc7f52c43a6de01934ba982ea629e603a706af35feb7a633a9212ce479bfd92f86d192eff285700d9ed07d2d096260385fb3a3cdb84d26e60b1a74895348271d3c6256b86a918db89a35e4e46289addd92b469f5ca6a4524bc8c22bc4973fd257217cec9112db1ce4f937969296fd32480e77a46cc151966147aa6f0cc96a67955df7a6f5d6a92b300c51ad005251a7d8cd7fabd472c1fb08cedd8598926a5abfe130111b6ecc535ddc3fcb7d26a0c9ba5e3af35c7f297191241d5272c8096e19549ae353fc93e8d04977fa327f2346665c299041c3e76ea72b518f8820d7eea0e9881bae5c53f5e2dc3a2956e4a9b67f8671b057702816d2c19d016c5fd0ce53a88c404fc6a826a185bd029fbde67a03c0d7a1a3aa8d116aa69c9a20a93ca50050fd2f0c2e71e3bb417ad3be6094a67a6cfc9a815cfc33f6ca3ed6cfc58e0c74937cd538ed61b1a69711d3b609dffa79edc225a675bd087275545e91a7a8ae98c73e51e1ed8e8ee70e91dce8c34d471fc036f8e5552069924117d956cd781e55a6ed5e9858afacad678cc64f26bbfd5c80700f1eaa2c047b1d7dbf5fc3f79e647fe43da44dfd6dbc77137b0533aa68091430867e74a922cdff80b55b0359f1dd53db3965bbea82782aff92a888f1e8a0114ea16cc8776b38855ed2643e416da48d9f305db061b2a88589185dc80d8f5e5526ed177e2ca706870e7eb865e9f0ca8f24b172967494421c6a682e2b8d2786e0ca7a5f3ab70686cc2503a3a210708873079d9db8bd4f4581f8066fb9a0fe4332e51e828b88eb3ba12994b9861c7f8a7e5da71fcd44f14d65bdf143cffaaeaffdbc068c080d332044506c5fd957262e9da29c1a57aa19797854c5dc27bef8564d24e49d24396627da80a892418944611edbe40c54b3184d44ba393804520602bd32e5525e82c142057fa2bc85617fb46afd0c700d119fb54b99ca0dfdc531e434a63784dd8153bef6a124b8db24ce6fe5897cad7bcfd7e8fc0bc3f8fef99970685d8310557b7c58146afc8696090ea9350ea44e0ef7640e6cbd330653bad501a0fde77d55771d0c69ee782f2e10857c5b07176c890da09f3a3842cf4e5d0b7b722c1c42e1830e5c0ce14f9465cb8107f3c1069203969ed996423b263e861ba9ef562934a951b867efd8f19b32087df328bd18aadf1a76fe7bc61f201a290484303300ea4f0471308fd2c48071c1f9b97ebe3a46cd5e0350ab57040283dd6310cc08f4b77734f86dc060f507d1343ad62ac02a5e110cd8398abd675c1fde7aba7bbeabbb936bb6f782632c03a292f68e2d5da742316c676993dc9078681b1a14b361d007367d93ec3c48e310d52da2c1bd40c16d81193ed950a3fc66b784ec653cf8b2e1fe32cd02937883f99fb29d7251aa03aa5b557b9a21e31093386e170369614a9cb35bae3445cdba06407f47e1011ba2785200b8e190d42d1ff417ddf2e3c3dc83b0770f3bf0fd701bf02952e1590c5dc7a40071e11e8ab98ae8544239970c1bce981f59b28fcf08db5d3a54ecc5f713e52e59251494a722d79210a7dcd0f4cc8b4f97a908caa2d2c6f854d9392c0daa8049f8506cdfdedafd016110fa6553d682b29b35ee10f7906b6eb28975039edf05e5a3378ea7a89814202edc23edc677f3a858ab81687fd113d032472ba27ef14f7d6496eacb547618d0980e25b6941b730aed94d25f3726d49d7f70c5e446826b0eb38497929e7abe74b55b4a8bc432efa03fa5a851ee726ee8b8a04268de8ec845590db06ef5ea698619424cc5f05d6ca48320f1989dbcd2342ad61a0228508fa3cc801fa3385900b8703cc56d4914022f038fc4a80b502d801af03abed5863b439c913bfa614ae20f63beff3c650ab5361816b00e3e55824a166887226a329b00aa73f2cce9a3ad71c6e36f621026548a74e227f047719ffbb00f004a0e479c33aad7eca383f5d730262fad0cbde31937f136e5308d0a3e08ba7ec28170189c788c52e7d86630ab7d0cc661baf8111d125b50137ccb87ce173803e975af20565e74f8233485aa7a24a6e042b76fd2d03a41e0543bf431fcfb90228d1e787b8c93d24e6e4f2d8920bcca375cd0979fddbfbce179c405cf5041e98292560dc27500d53f9a5e89dcebe7049cd6ed4aa40a673885e5e73a97a13a818c69309a1fc4f5f06f5456f83fc1f8ddc49b22f9807d88e6f94203f7b3953e11f37916228b9227426f70f82f5cf87e027b0ea484044f9b4f52d7d99e1cee09950ab327c1d78c696dcc242f55ffcbe4ec26ccb2b9226a8a7f9b2fcbc59eb6786cf82c1e3247635c1006ccdf2d22ca76dd0b9be864ac3d2f358160d051865f3fae08adbc0804d5c587422ed186b8f570d58bd8dcc7715dfe028d26693d046e7e7c3bb71813c1c7d1b062a30da5adc16aa80a0ba24640d1c5e6c21afd55b108bff57d5c5762f4bed7f3e9e5f0c682e368c34c0084977a5810e982b2e7d1cc628ae3bb8ed01df4ed058a4a871c702977b39fe7a8fda4dd3829c48b1f1a7e60b23bfb4f8b333eaf104641f1c029e5e90ae4d26b7650a056ed119900b25da30e8e5ad35fe9c81ee3d2878aa13dbcb6b89b8e37a4afc5825d93a66cda4b1205e2a41b84454a349b88cf73b181cc56afbacd228caee73bfedb6c91a13ff463c36991e0dce2a5701b9e2635dbfb9d468a5a1307d4c3b355a181afa0e2c2bf3e17987cab465ce43856ba986ec38bc7641cf6a8b3def287e6cacfa79b36a6ec734e5b08a23b34d257594c4a44d12040fd86dfbb1fa2e133742ec0935e8449840b601181a38299c57ae854ea0eb1c44bc28c3dd6c20470df1344a74d214c5dbaf9a73620aee559b432697292a85c2f40e3b410f8aaeec95174b9308d48facf99c3c9f7764f990e9a0837ce0d5c22ca5f294b2c7f218e029e1280ad8d1a179b94e41f8deb0f6a7001e47ad63041288fe57301cda224850e5fb293dcbf380b084d26afd56075b66e6e16accfc0f3aea5654b7e37704a4c79862d6908b6c6e9b90ac8c6a505945566c2e2712cf6bca1803cff60c835645969e983f621bedd6dd3b5eafa0622a788d75fe618ac8727b6823389f528b234aae47b8092c878656986b6d27449e45fef94168a7f5004685adc501ab93fa2087923b3a40c6088d6c473a5db5000aa9f95216fb20cfb0a68d02ec8c6b0c15cf6a272d8fadc9a9b0f8ca05016a1960de49a3474b2715b3fb9cc3fe49549648a55b76242e0e490e255a2ab08a33d5e8f033d2b6880c4c87495a76098671b8c2a5f49583f56b8e1f026110aa8d1c8c5304593e1529f9ed07dddbc9111da564f95122591a1b67518943d65e93e8a60347cd4b8a76a1402033d61b8f896f19092f0418114385e79dfa2d928b090c8e2005f0e8432f9884d9167c9bddd2a73ef8eba970d6c06cbd2a62c581f76e176b45bd0b250cbc296855b16b48f251d04f066d62a837a83446b72abc476ca8e238989e503f65c588b1102e05f9f09176d69fe99c8d536daf37c1f7f0a3781341d434f021b0f15b044fea16d78fba390b1428391aa790df1dcf953fbacd8f1d4e26ba2bb48d7ed9535627358821ad4e022cc0bfb471da833de4aab9aa71667030c07cc0ebe6baa772da1d37426345a212e7c9b0225afdf5fd068947c9501801a6994f72959e9fdf92b76156c1208b642fdae85ec805e18cba60c0c04ca955fbe1b9e1b8dad52a45186d69224b53c0b973adc52ad754e946a1e7b497f2eb03fe4835302a75e07916f788e26cc22c3e9d28de72ec2a4319c32577a649e25fff5e42be5fdda340cac38f163ed7bd3d2623955fee749f4d3bf6a52dc25496041e2a223a6651e2fd070b0d38b8c76c06481a1141f711e8c83cbfd364b2c1ec54660dc01bafe53ec152c34c626e6b77c1c87bcf1e60006894f4674301a24032144150b7c44461f432388f4ce04d68c26e6378109ad42287a19494ecbb5da961dc0b5bf81629938d5532510dc21038d1286d34b470c8d9187f306ad2a847d5da46c236441b64eb46f657ba06b221b2e23f228a5bb92fabe92decec70f74a85b1e42f0e623c96b171928f94953a743ea4478ee44b76fa50a0954a3ac3715e0e14d4511a159051bce217d0d38efa9c0678c8c10e7f19856f9d53a4f865edca6e6f63e89a7934dd4594f9c2b5f132be03ca3cca32ef43572c0ebf34dbfa18896be4356f75306a1b4b03dfb3a82f4e79a230c9c1c4cbe2ee1f4565e3d36e969e0eb5898b4fce7edc2fa1a59f8999b408b20781d41aacb8f293ddc105ce90f613e422eafdc6e31cbec7802527815675cf3907eccf59f183c88b68fd7ce1c70ba3c022d7827a17038bacb9ea51a275bb4e2bd9a55435c7953fecf73b5fb05fd2726014abdb229edc1318ef959f23dfc971490982b6f6c8722784e50884e7282b978e78c668682176313d8c869a3479328742436440ae28652ba218a15964595b6ba8c98f7504159ab0f4202cb244f210958a59610fdcc55a89bb750053e8a66264199bfd62add334ec037c7b5217011b4160908288a4de07c3937925bd9df4d9664b2c0d28e05ae70fa0f44ca7870bb579bf29f5682c5643c9759aebabbec5d5db569f0cc9287a2347085b977f12cf861e5b1e084dd5c419e597478e16a20d114b9ee6a93bd875d236155a93aa809aae1f4ff78d0a50a3a4012ef6a7d9c79fd80c21334debcf41dcc7459466646a5c6068f2a636cca75974c3d7da2361c8f41b2b7bca2e99b0e8261597915c1bea422b949a3d1ab4f28b57bc9e6721f69e3cb5c396a0443e280adb0757131c904ccc302f02ed03f4a37cf85b371691b8dd8c6faf89cb574fa88e0d1590a9e9ef0d7e0dc096a0049af2bf01f227d653509c2f3e344edd1af6e96fd3ea5052858d5afb8ffba3dc24455e2e84784d3e6d62f00ac39a689a6a1aee4ff05986a8ba37ea0a2cec2f59bde44f62533609da840628b5107a8e496c67a7d6a7e5d37a7b2c15ff2a96821df4f8a0d3316bb7f99562c463a05a0af0acb3138c7b2905bd9d37b54d552082f86255a9a19200322b2cf816d2b1f1e82ea20ed6ef9c3c4fafd41c4f147d28d07acf5f72d07ab1d42ccea59c9207519b545db425463a78cf091c154c8113ea217c78bd9dd4fc1f6195945ea7655b3d18d10c5b7164c8ebfeb155a989c3794b97e87dab89d66645b18a218bc72410bab0855bb72eed69a1661a2d8451d1e9df77cd3d5c1ffa77bd4213a946e4b22f971e0b0bc33183776b8b095a044b27e94cc21f58c6fc221fbf03af3eb6ffb5fdd8effc0954a697cfe453432afa36cb757f202acea396044380df2f32f6de006fd850a984fa5a4eaa33b029945ea705df58e5a09b29e8376e2df4303a5745dc3f1ba2ef36a7b11331110f2cc6a8e6f39912f0ae33bbcc2aa6d82e1ca2f144bd1487e2dde4e7ddd780d031bfde62b0025111852de6a1bccccfd4adc2081923dba1329dc656adb59ae6cead04d2865d0d021635cb66d07f1f3034ce1d0992d422bb7d876d5de490eca70150fae41132366648f18cc0c3f52585db42398383722fb9f7268d5bb6a73cf0fe9815ede89231713b1d820c1af64e2fc47898306c952f187d89adf24c68da8d0f86cdb9e79e32f15df09fb96fe83c13c3d1ec021c2d4a41d6dc4789d04c75e322ede5ea6953209e47fb42295f4a214766df210ccc4c4fa6207d52fd42a9b1dc3fc6b2336a3d396673654fff8dc58a31b03b82feb8e0ec08e4fcf182d7b313fd83ddd6f8ca44e1146c6d4f623c214f78c9ebe47dd91f89243c13b92635fbf6adabc9581812cf8aefa973b846a4a5bca311fc2ba55ce7b07be6b59d1454c94ed0b8424a5bb72f6a40a6ef0f82fd5abc3154bb595b1cb7bf760bf668e6a3eff47c57bc2811f55540bfc720715e506a8a30307b310a241d8d7b7fc9b7d49112ca0f4129a6d3de215230f393bd37058645ac4347d9c0cd6ee7b304ba387458e1e7d30693c2e438f6d4c062ad49ac6e0a2db67c6b3328f5f24ed7d883160e43552bafb5f194d6a0f3dc70f9f18602785a96577add00dab1726ac5ecbe0d95eb10d2df09bf20287598a5636201dc10396f86772fcbf6e6450f1021963e2c8af1e7fd30ecb45d713dd8609a7087a1826383aee873589a578b805362a8c54baff678a93aab8ab2fe8a6b934d20c53646e5d46125283bc3421a5787346697e424ea63b906f6f47f3d252dab229ad87087ffdd1f79a5d01ac13e4e0a3a6aa495304c79dab45bc02e0b90e15911f51203da88c749c02f181c28403ca648f9411983f683d27a1e5c60c7eaf9e0835341aa62be51181a140ed780aa3e8f840491a772ce844bf476ef9d0984e1ee1d21d047a6eef366844dfe38ce04df215c251b7cf0dad767c44786de61bc132730337c8cb6102a180efc71c67b20366c2ba134def3d3612bdd76e3ad8965ee7b80e92c3d9eb090c573dcec407fce09303850b972158be1187590e216f0c1fbfbddc240da52100b916cd237c2bc00635f43aa5e20a22b34adcda2077837341c5238d468ed865cec4b33326c6e01544fdd8f2013dbd2aa5cece3349bd6ec14a46371b1f7e2779ced7f10379a1e022817faa9929729e43a5073e29509c89cbe46618fbe200cb35603a9d5d0ee80027058e03e7197d875103bf5cd5b5dcfcef2bd155403b4942972f95b80904c01d77e09a986cacdaf81787f80c17caf14cdfc4baddebf60982d2f1c5b92eeacede2fe78c722c2545e551aceafcc6bc0859ee52e4c74d76907ed592e044708472946fd8e6e46e9a52e10d769cb6a5613d07677caa7c41147126061e177150a3e9a97d7f88855a00f250db9b2f81f01d99f38e3910cd3b3c6d5d0f53c3e99110162722f011cf89b967c94e83f00f4da98217398bfb83df3bd8a112b9c1b058f70fa89cfaafb49963307ab616ca5402b23f2c737e020fed5d53554b31efcfb9cfe2dd501427b33b0ad0e74dc0e32bc20717daac3802d33dccb20e88fa0b0a013de0e40071c1f1b8272ac3180b35d3aeaa20a43f9830f63f19ffc9c227e55d27e25eb155421efa028c51045084b43ff09eb20a72d5ab27d470d1cdc607df2aa9883cf5f677847140fd75ea2ff46a301afd20f4c6f920ae63529d67eb7a1a6c766b708cd73fb84981e3c23d289adad8414e17e3b19d81df26a7711f9ee8f71ef99da2925102bce6d8522ca8bf0e7fb29de45514984f1d092edd72a3f96738e37fbe54a347482082d012652d45f5a381dcbf5289f3699530ee3d7c4ae24a927642fb6c749e48c059259f7c4f724bfd4d4abf84642dc3b5fe608ef80dae39dfd0038a0b76152d6e35522051e3dbb537d449d30b76b86e669544471cf321fa1b01ef79138c5d8192358e1bd6f8ea54bcc3d372813ffcaa50599e56a27c67de57e3cd278368badcc953f2e7587db98985a47b0dbd72a72e9eb70d469e9748cec43321485b8f7545b05031f69020ddc7dfae8e07ff3d30d698d376a8b4c1f65f2c52145aeb20d24306f630cf4e355a70e81a4d198b81e8e0ff66a13df252188058d2117d87d12bb76c751c6b7685c1ec37c00a6484587fa57f73891e078cb936e30f163f18a975d079a1acdd9d57efae1d537132d233e9d4ccc003554ee191997e28ebc759608e3dde844a008e384b8ed7f7e5cf768192f476a58158c05f827ce295ee6f1e70a5d6fc6214f1d7ff730c2133422de09743d673edcebf6091f83e7f8717cb8ace7b07cf71768f6411bb37ec73f5d9e9c10a1aa82cbcf8c69fea267c9b7387155399c06827713a8c73d5fc24f21fe71c4fedafa3017f30e3f019774f4984bd6ed08a7f057b23da5822e7e1acc411436c68f6966b8ebe8020fc7b3d77b61dc6cc2ffdd01217e8085a23a294f79b5d80e055da57fb1c37f13dbdd7d272612e0bb872056e3fc5f7f29c873752907ad55be0bd41a7668fd79f3511e02fd77ff4d96daa02fc88848c72b3dc9f8b40eccd2ebef600cb3ddeb87eadac9bf100d962f147670058b3807244034f0569c4212800f5cf6aa454f64b9a384d1f29707a768d346d80a2d8eef3423b48086c271078760bc6d8b18e061a07d04b71b5f9c21a25fe244ca2c0b055a41b983858c6fffb07d5cb6cc2f41a85af6222b46142078234de699c66b369ee2bb1099a1d780d0a87b918e4cc722722e4dd392f7b59a0bf7ffc3f4ee793ce83f01d2a6a0eb90b51831a0d40f1a83dc2a3afe190c3b1c57a3360e65c036930a840254f02f6cb1e23879c6fad50eb673d34eff652c06a657546f5d65d5556deee7f74667e6bc887da8fd4a83cba2444a8d2723c006e1f12e7ab124310fb599c72cc8f15cdcc11a1b0613f1c76ca430678decd6cc2034f7ec3ef37020bd2f3054455b5ed0e949ef849aef678f3be95c4d739c25c8c03c67b5fc58b055b0faa0ec990cc6f9e201e396de115dd4da6f420683c5ed7359ed48bc36440a2c7d975650db39538808c291ceb37417cbd6b1a0fac70103d51117ab1eaf235f78d5901b254211ca6bc6544173277cb8a384c1c1125d4789e370a6385f8141cc4cc0d52bbe36e513ca2ddf82d3e6116b44ff5d2616d7815e2eb09402943773d2600be9c256631a713bc9eba122154085f28e8a6fc9e3bb2cdfe5eaaf5fa25344cc5fccaa7e1e633f17eaf63989287502200658a457794b6561e6dfe04a6afbda6618c738c69c36720a1e3efbaba061a26c2fd185995a7fce1bd570cca93e115b553af9475668c9931559ac640d061e06e29b8a8550d8edc0e71ca5c6e7a2a8a35dafe8262202a623ccf8eb0d75260234419b4180f2300d86fea0e20b235e1896499ed880b84bef8ff97813c25c1308071d8595b0f6aeeb1b7962100fe600ccf9e8cd793d63b03027a66a4c6115a9bdbad39ae483996a672dc3ba7fc6eae8e6bc8ff58b0a0c2e7bf8444e35d947d05932d1654c20b68e7e386b4a9363cc4eb39baf734dc4ddcb03b540fa7d5bdee0d9d216ae2b788d442044b507ccda37b4d9f77cdea0fd7ec6475bcfde6ca6a46720aebe616be58d86b5a691d03eb0a249c9b465aad29bd67a262bd49cc17cf9a130fdbae126cd49a98b35b932d15feb8cb475425a6c853f2e69a7905b8a05dd780f024664d31b81c90f80f2f990b1f67e99cda405f855bf78d08cc63f6b802e68c34ce713ded97a353dc3dca00d6dd4b207de4633712f721e6276e68e68fa9e4f0916a0ea507af1625ad94d2b491e4a42da2190d6b1c1a5ac0c2a13ee665d931d51f74babd0b3cac65c0b1ed5a758ae45548d90c0b85dbf4461723b020238e172ac7102fbfb7275aea4a2e439b2a3caf9fdb18bd621ffd06e8dfe9ef6d93192d6d692f8023b000e55f929eeba05976ab206c00780170f247d29e8a6c3a08e16005a428e26fb5752231dc1048a7bfd04303eaf4edbdb47a2005c0e9b41acb041d240e6f16cfc7fb79cd0807097b6b237c599cd307f12e751e690770851643093dacb4c9db4c2053d5d400c308bf9ecd5c92f79342b437edef5124f67b94f5fbc974a427a0ef2d6042ae79b992643c0525650f6574ad97e968f62f8775230d0de97a6481427a5b4e6cd68faed2a1ae65727ebd98665ea6329f32c536f333ca277a83ca417021432f67d362f8baf3e6b029fa8fe27e6e22ee39733b842179ed4c0beb9513058923321772570ecb49f9b56294b9c924d8ccad3c1449abf932d60de8b5d6e5191b7e9fc40e55e16862d3cc93c86cd9ffa57ad71910648f72cd8ba7dc2007061fa2e346382f4f7db7a9a97ecb3c6cdbaf8b49765d1e545d124daba2fb6b829857f340d09a1eeb0fa79e74e7c4e8c4433858f118d664c2334a5da03cf0e3e17e180364300c8ea07cfe3fa712947f4bdc88a72eddd3ca7c97221b4ab9784e914dd19ac441acb6fd273547d4b205e72dc2e96b73cb1295b7f2682175f0675a19ef985252b8f62d100c71b3a92d0b9556ec57081da5f2a81be58897d3413a2cbe318e1b66fc7a134b785f60b1c24352936c1fdcb7dd60fbd23eed2ba015b24bbe9b7b83940945759fb566ee0346fde466691f2c5996ad77ef32e4953ccfcd9132a83b441e856c8056668be110cc34a52d03a977ec24738871d4e3536a1e18af1495cc88c2b6a7ecd5a73f1f20f366f5335b0a76c7648e25a2c253e199d942bfe7d35ab66d5289ecf054a36f5f1d6a808ccbb1f1994220949a4301a3294115459974204317030b485d7523366b3908f46af016ab8fecf0a7c0b2088318ef4bcdfc478b549e33b7044a9232f424f56001e7f54bd1ead6bf28fa7501da8e86c21069f81fa7b9bda01fdc20c6b63cdd7d636af406cf4314a2a9880b6ed8cef48419de52fc1ecb9be9e8086d94066185c592ea1d5b9063184bbd9749abd2fc7db8523e4d803deaa318dcb6e11b278ff7645a594e76a3c1a62b305c9a7d91edf4a38e62d0749c1f6f8e912d0666315c95c4007871148a9b8e8bfd635198441148fee499d4bea664b409df834579a95b0896b7fd38980479d9e1270e76e1515379aa2681feae1ce1460c45e485638de41ca4ec42df4f80a89ecf8856b24fbd62a074b258cca83d845278837973e304452820f047fa25814744ad86907e7b90b19bc6819a358956a2085c8bff5bc6b3f66d9e8aaf1e41f197c8d6f6b75ad6a0549af42b0890f14f44f52b88e04126238b7b5731d61c9884813825e5a32a0545708b80399054f6a1d71c0d85e19573af4cb8c04bb7d0482854a84051f13e634d24c3ea660121c8e74145df474f0b5bea9fd2ab74b8d97dc13e64c8de2fccd538dd3cf05d58c9cc8f828949591c40b34732f2cb530e5b6b9658244671136e81594af65e8dea2da6f2ae317cd60922f59515c67716a40b4bda2f9317b3284fb55dbb97c8552ead21992343ed2d608702e4d0edb2370f9b13cc6464bc085aa36a5d968853c61dc45b3ad66af7d29459d004565fde92e34385a92083566f9ca3ba159fc61c5c1bf160b6ccf002c9225bc987017c49672d15e89631ffa3641d8de1213e73df49dbc18ff95e792638e7654a937ad0d7a1c2787a2e34c9a4c55749255003271989689f5fefe2d471b2a96765d4ceaecfd81dd9da185df8bf11cb04442109b823e354f01c711e49f4a813fb99789eb2c64f47168909bed3be24ddb8fb3ad3239fc192be26954432a7d3ac8cc506611aa8a0f48a1f3a5c452cda9de64c5f3e1f3eadcffa5fafb873f1b341e531e0254e2fe0e19c0aa7480ac0e0881392e4270cfbfb362f558e4af7d9e3d42cce0d0c6c0b0e7d2a64898653d577bb0c3ab8c0640abba6af034c5e8aef22fbe2955cb142deef21f39687429302fe2e2ffe40f45efbf7ce3dbf47c6dd004049c22b6f861b04ab2160956529b3fb0bfff00662fb0b9e980b8dd4a5cbbdab97b5b4a473ae3a1c18afbaeb60ed5a7af73d0551154d917754d55c4636fad7b4151bec486f35431827d23eb8a1ec570a2bf0247feb68935a6442de00839ac2b76187dd454123b713a88e8efa28db0133f51c15378e034bf78209a3b795c3bdc820ef2a67a38cc4aa1a6e6ecee2635c2b7c95bc2782036320a827e78c3389605563dfb33f0be2869bf27b89bd72a9b4af2b52f3d727e407f3cad926f993e8589068fedaaad6eca771c39aaf04039e712ca2fc7684250aefd611afa67b7ce27db1d47504f7f8f141382a2cf5a0ee27ec865b41040410a347ebe61684164b49802aef890c5037da3c17354dc26ddd538e17330b07689af88b873067d9f1fc49a59033878be7f276ac20f579066fd262a44cdff66481c35c0d94d179c5fa9562f37cc2b5135d4f18c79932155ce9d409a2a5aca54fcda5e9d4a5cac1ed52167bbed4cfff980dd47c3b623c244404c4a8115c9e33c15afe5b8cb19c3fbd04778f5b432ae657f486be8ab3df5b59bb3e34324a452359ee66ffb612cdeef5054c3bae08d62f42256aa01eee941474f2d2ba6a65669e4c293f8022baefe8375e114671dea68b2c6c5339a2e0d1213538577e3f7abeb31ce7b13f5ae7336da46bcf89d3b733ba41955ad55ac68fcda227efb90f360dfe5c0e7e8d92e1b0839a10492db9848e06925953ce88092a9af8d8e7c7f56b7a9cb1224f79abaae58028c9b76216e98f7e4c830bc0d005f87fb001cc0844f189188076ec9eca351abd349c15ee5ae54eb7f5e2449984332a88089e467899a30ebd864acf914b9922fdb5191bf993999430460f4fc254a6930fca1263579727725745ca9028aab5c8c33692e7eb42f4404ea6ab83661d8204cf1d0ad68ae1de70e4a2d9cdd50fea953ce2babc0f61350fa3563170debad7e7607d6b72a1ef485a60408fbb50a9733f2f21e9c7d941bbe9eecb5c12a611e395698d5e05abfcf0ba8f63051f9ad21d9325dab33f91033cc21938031dadd49c5eec55a57149194ee3a04a226db9669087ce09cc0550d82369495b68c5970524e3c859894186f6db7157be2a3f8b3fa17aa861f64314afc92b64374ca4fd7e84dfa22b7c5378041f43a0dc0b9a7c3d939bb9405f10986334d33315f2de6f825e8e06256d9f5c48250386b2649b0d12ab8069d22fa8ed0dbc1d645a53173aa88fedfc3330b87634d7ba2d460960130ad411817254006d635d628dfb6bec88f49e384cbb6551b4437a693554cb71a1d6a308eb9c53fce7d316525e459a3745d3b49ed0000a8d11445ab9a7613c446f0a969b98d97576f8af1f13df910e06627d5b1725b37d3352124f052672a6fb03cfed3eb3fec529d81f64cec2d8c5c70800c6c97e2da8cb5e8bab2e047cc14544cabd0d971dadb52096a6a95e3a9d3d408327b152c81b102d5ae06f68414766078dbf6d2b903980f4c442d05950d251296d3dc2333cf600db218c2de9d8cae9519f748a414f77c7c02d19d15ee692154b20d885aa068f4ae4428657fc14dc957e55c29337768d8bb0f0f3f44dc2c76209aa6a6c0e7cf31c96bb99dbe8faa3470d3bf903f7e80a39eb7c119daae65ea116c42f8c0af59048b597db298a7b9f274737683b5e1ad751c7ae5c57cb2ce598a7ecb617feb640232abced902ace9699ab82960afd950b6f7e0c576f767a2d3c4900b8f4d2343df437c89028ae6b6476daf20138879bcfa4b5a872772da802359c71bf77d3ae10423d2273d787845b2603637147829a95149ceccc4028b4307c454c743f816be9f6aba3a8fe2a6fa5bfc02cd488b9c3ff481822036e3878acab5f92b5789fb9b6cf37d5b6c57a00ae40db11a59591b259c142b76f1ea60d0304e5a5925296a9903355b9d74e5168f7df38b0fda4dc4203ca97b315bd9e1dbf7285093537d8f31c60991efd775f128aef6acb18560f484106e8af0c733bd180416a40a99f942a35cc27163b3730680f22eeadb3e53a9950dfd4d42eea1c4e7fec0dec1d0d61c6bbb25606e1446f81bae583788b7bc8a4177133a808d5334ac4a4bc3c2a962d33f9f1eca61f1329e1025269472ba649d6eec5b539b9956128a04504622061fab93f99016f419df8917a919f617974de9d16986d4e42bb43f55dc0b639b544eddda318f13b1f37473b58c78bc3fb17de996d65edfc163d3bd7ba054070f0b15646960784df1233ae53fc16caf216215f429b4acd91939dbbc916f73546387b98148157105a918922f35199c02c24351c34b101d2590ddfcd317e9f77a415bab88491068cddba2634b0fc3e2bcc2ee9915ed8897db895ed2a570056e22cc857f477bd77e1cc03d89c720b1abc8d27ab8b33947f8c9bff00c3428e0765b1b23eac44d22490f38d48eed1ac69021b500cec21ad825c408175d2afb0e80e7e527d32b190e8653c8037910875419933489e3804f28d1f0a19717231ed4595084a87952df1e10e19fffc291abe91fd063d287531cbdcc6ce23182d37ad90a2a4ee5360d02b8b91e07f8c17008e3e0b732d1d2839d496732a76f2d1b6fcd2e03c38e38b287135748b867c3b261365320e6ccfdf171e96f581d63163c9b1670c341169340d1b5e9069a4ce3f9ba1410b6a43c943444e1d769b663e1b402ffecc9dbc908a3492d6bde70420fea1cd888a528b7c905c58e53478a9f98bab4bb5eca60293c097735fb26b92d0e33130f7424860dd0ce3f36a9d9f4658f244433aec9a63e33c4be8bf2fe130593607b8bb43417287923fcbbc4b6407f555294de2dff0b721807837b86190b7e1433981b50758b9e5efab661577f49efc83fb794e193810460b8c132ded83a7076b42e18fb3e533be30b8719b836e59d87a08d029fdae3064b9f529ec5d3de43823b0e3608cfc4f7786260045615b816c8ccac2c6819cc897e6781969d6053c50e8a5afcb21e81e384c68c31daac593660df0114ac21d2715ee5cb099d09c4312f5b3f2fa670653baae30cbb9d4ebe9f5edaebc52c0fe04833f5e9f0774d9a86b5e0f7a991e9fd6c3fd5dbf4ee4e26da06830e4a69ec16c9671549d383fc276d727be798a2aa16564dbc5a0947d042735e3dfe1ef8d525c71cc3999ee5f2652297b6fb0d85360552bd820826564085cafff05aadd4e485bacd8d5174855b7cd43bd5afed79c46b894a9027f5003e88165aa52cfe76f383e89347b53b3cb1ed76cd488a87cd6846d70f50ce74619e8089d5137e0680a81322ec087015126847d8833ec32fef086b5c33f21a8bb2df23833bd8285c8a8be5444e689d06d627ee4ef90b0c00b4d25fd10823298eb776ce1e8f23583b92c01c38fb9e8ef0c6a2824964a82e63d0a1b0432464162b31eba01f4738c85a5cda707f0ce226c4cc63524b1e7c9ae2089a8825a9bc6bf95a731255f0379f1bf166c052b3de3c90c1c19e2ec0c7d04693fe701e3a5e80faef8cec8f39ef72d92d8ac915003023089e4c11fd735f0f54454bec6233f301d6742912eebf59032dbaa48f3a9c6339115ee71ea4f68411d43207bbc717f9f69cfdea29ee7b52d56ec0714d557db34c9f9310477ee47514b2448c259f49d037285affa9ce0c600ca1bed642b6f091d5685a3b3f6f23216b4298273ea4e0bac3e232ecf766c08433722568533417666e08964050679540e5cd17d96581601aacdc13a7530b957c68d221b88c0599e0570b48df17cd655976b3b217b6d6d1a65ac404756c2125b9e33a49a7a3bb6f5d462ecd4d083b4ad5e537ea6aed5393cfcab47e81e3739da5832e8cad3f7c4e87d81410b7fe95b3c344a1c140540711ab44bfa973188f7f47301fe71596da35d188b2e104d7d3acd640a659398dd24f6495b29a00d80478fef31dea480ce6e258f58fbcb8bfbff99791610858b8bb2c9c772f708fc07a156a0d8ab6c28c5b9a09c7001fc01ea1193c187fda790b7cdc8eb926c1a907288b4c28a769cd2cbcbf770dbe0ea8870e94f2a2b9bec22b238a3d545cee4d789fed3d11fb1a09e401321da47cc1be425144fd0e52c201aa886593be3c3d99a5a06fd1f095a445d3957fd746cc9a2a064200aa55f9704f63bfabaf123f331283c4a08b3e63260b3c329f0cf121bf9a6c63d0f7301ee09acc28c9e38fbd1e8a9384c9b223d5a34d09b1ce633b10b4d9b8717f41e81f2d5ce1090b4b30b140366626eff9fedd461a52a3d04734b87574935c5c31b77c174e01af6a89c5f27d945b14af900f2351883c7db467b2561d70d000b8ef916e2c8491041820238745d77756061afb25462cf1e45f7bdfef50ceb9dd87a7d0a1686afdf98f06e1aa1eb60af2797c025cd6639cbe3fd0090c7e7a8e24d204f6ef568e262ee347ae9bfac045be0762c0dafae56157e32301bbc29ef83bc55471c1141ab36b2ad377ff4d4f0965ee550cdf80bcd67c5b284443a58576a8e4a5276a10aaf8823aa5a65cb005b3ec3c155d108eb3a88d4dfd45fd08b73e9ffa57c2da2bb0ede74f32080d3f5a3ff7baac013d6a6e78f01e107edceb6f370411f319c5a8a9a875439cd67f574bfa111686afab577488b89d24028cb29cb85259250c9fb486fd9f917a4c66ccc742c97211535473863081da01d8737c51b110285330885583ccba23abf29856109dcadb1661d35febc7b9e42301a3d800aae0287021ecd61d3abcd81c66b7da835f28ac18d313debb3b3836512d020e384688ef02f1cd878c30fbb6087111911c0685490ab4bbed9e1bea4aabd365402626124f42fe4396c3f11f3823c4d0c7efad01aca8a59662a35c96d3e2e142a6d448c2cee133560411efd328722f0d8afa7f86cc16c8060445f5e04b1ce0d307d7508aadc09359e7f203401fc43371bbbb146c0d8fdca6391424b99d16a7b196a50dc951f5998bdee0b52d652d1e33cfc84233a5ff8b00d0d71c2f2505f74ad712d6261c84c1974c9e3adeefe890b2cc2302dc071c9b64e42d8970e52027df6abfea1077c0142669f61e333da1dcc1b01b37f3bf6eac064d0751121070f2cf8ebc0e3a23a53e0dcf41cbb6454d4fd886c23f2a4231a0836c6304087fd111c333b209471726b113c0af61722f5aba3ccaae8f6de304627c8db6f507e3e140ac4d4e4e411f00b4bf761de1eeae49d733c4efd0d32a7571586cd2b84312978afe7bac17d5e137518c34ae43dd7dc8c5e4743353f9b0eb46018f316ef4f9d62d0cece2487aed7cb0bf37afcb8579ae10aebe26b1a2979191e27107ef8180d5a01cd18766d7dc2c54a3315af54e2453a760b4c884fd30ff429225faf0cee60b416d73463f382902eaa41344490f2aa8709411aa59f80adf9fe7aa954ea5ea16840ecb774de62ff25cdea1608a067b6f3fa3fa00367e5fb492f43d1e9a4a4d19e517ea31c6be5affed70efacc88a0c58ee9a998d8e6360a2892c1c2e44fa5347f1ba298f77ddc033867c5b34ec6c7deb2513d44c7362a05d4daf9c2ad222486bb8c8170e81f60073f224308d4680a3b0b34a0d4e8363fadbf0908773c83016c49c6c460fc026ed8a305e27154d1b63e490eb609fee5d7f83c0029206d4e4969c262589f0d1daa3f8b118f443bd67e3120c57ddab151c9720bf229ab68084a8f87f44915bee3e888cbc4c644e48df516cacabf9ff13ff78b17b0b58878f8480b2cc2cf47448b8403aca165de31a4dd8b2d8e2b7511ee966d91128db07a473fad904d04cd195808f5923be5814aea3d28f297eaabc4ec7ff90752085f16f8de2a9acee6ad6b8bc22434f5462c8587a00d0aa32229dc913ee8cc53be46663521f470eaf1d099b7cba702b1dc693ef4c2b9d797346303ee080956465a6459a63923bd1e42a7bc7b9f99bfe4c37acebd7d8979b108e170a99ef8c635d3421e7680062a285dedd9503c796ed60ba2c541371c2933a433aaeb99094530346c6f8a75a6894a0a15ae3169b630a1989c3fb36250b4f4a6ac3956832e834d7085b1c97cde9bd2e6f5031f76d28103b66dbf3491db875eb45f54a805d7510fec39edbe3583578fd8bccc2a5aaf0aee025b170875f44f500fa6813cc00c0bbb94b2b144d64741e052b68d8286fe046a0d775792917849d18901d9f6cd56b160f1625a607aefbc48a72ec02811da19cf9d033611a36540f2da2cf8364090368b76ff2d30ddbd440cde292038522775374529716501cc5e03aad0ba8f5a30181aa5033e72e52aa9613b6b14906a3e1430d0d7ea3653763155a1913c320899390e1b9de0f87683bf6d49a9eb69bc7b03ffdcd7d943cd89526121fbfd6c000493f451de8fb782cdac95d78d104236215bcabdf79652ca24532209780806090418805754ea45068c2a4626861d020cc07b54ea45068c2a4666e6fe70152e10203ee3bee03137000e736170d215808d0e0c0178c16b4a003004e0851f3aa9171930aa18999999cbc355b82eb8e0336e0f8fb9380e73739c747dac5ac8711a35ea8b0c18558ccc0c8dbdb602f8c8c1e9c1a3851718558ccc0ccd8c997be32a5c16ba2297efe0d37a167cc6cde1315787c35c9693ee0ebf61e34faa1a1cb5a406e770e0e0322c1d39bcc2dcb8b1498bc3ada1eeb8cc2c1d39ee0d0e948983691b23edccb434d4cea031e70abe028d0f156edcf0aa823626da196969a69d412d8d1ad6da701b2a74dce57a55ba289a19ac1456b8d455b8d46d7435ae11ec6ffc70e1a957522d01608666068d1a7e3bae74399fb7fb17ce53272338944f37a1bcca6c3ba68e3914ca79db41d538e7d3495e555ccc46c3cf73a40bdf244d76bb9acb5c569dba1aadce8096468d685dda3b6d472d006a623c06a663be89996ebdaab61ddb31a7c96c34fcbca675f564b9bb727b57ae5d1baf155a68e331db8e4dc7dc8a66a3e1e7575e7be5d3e1dbace6dd3ad9ab193433326f2fc6c1a86ccd75a5705d2a5c978deb5a5d97cd75ddb8aecbae70915230325e66581bd7616ee7a70b0037dd1ad76e0a5e03f52a5c203838aa06e7ee121c3a8e363750b7812ea9a1e36863af7b4dc15335ae0280cb74d76b0a97b90600ddbddc2baf4fb3a73f3bacdadc5801c74d0e1d2c1ab6c6a96a34622e8e5e513fdd9ad255991cda15dae288f646da1cd3eaa096b563c6676674d467783cf55ac3b4028e9b1c3a583b58505dc661ed8db539acd5612dcbda1dd6b2606d0bd65e3b6f896f288eceaa488fba96c435ab94a9e36460e4d31cd0ea68cb8a7687b42c4cdb02b53c7ac038cca92606c7538759bd6ccf5dcb551dd0b2daee889605695b989607b53d70508ee21b543781b36f2f86ba34aa6b04fb935716b43bdab2106d0bd2f2e8412d4ecec94f265a53b03b586881478f6971727c58b7990d3b2f5657db95bdb2bd32dd92a7ee4b6704fb929fe8977c3aca6b8e6d07d531577216361a7ebe84ba50c68558afa86b977453bda2cedd7a83bb7ca511ec4b1d66f26602f77138d0e6b4f511ad0bd2ea4cfb83da172e1b808d06939b501d75207da26ef279e57bcd81d6870bd1eafc7821001b0d30cc497212e96ab76fecbc30c776591ac15eeb302e38ee15cd718343c70adac32d06ec72482765d1d00c40a20448e99c7352d971624c25b3650f4a6707d98652ea93d279512ae7a4ce1e9c92ceebbae6452915d2ad8b5239af8b5217484c5e98cb38bb0f1ec1a633efa0524a29e34587c81e330312eb20dbcc79514cd239a713232da45bd349ec08c645a5e442e5f47975906f3019639a72ca2540e92345244b493995206794ab9b493020df480d8a6bca8b4e39e5947276cc37d3a5a4d74525dcb20497578c314a87984df160b1e4a5846b0b8763b592578c3146e9f09297a4704b4bc2217460898c12ca083be806d68141310809092750120844e316a00fc4a20bca3b132127658c502ef132c286c2081e0541246ef08fad90b32b02bf9d79b61e484b225496a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a39723464646464646464646464646464646464646464646464646464646464646464646464646cc373126e98e0109151e21a19720d70e63ec08a1066a76d189699115c918993483a901cd220d09090a27616429b1ab09a1216997412cd01319e3d32e4e7a5d43c8322ee60b8473c58a32ca0e070f79e56a0afd2ae660d80ac209b197d207105310e7437a4180ca359f0ca3961fa5152f3da49840694185e084035313a494505eac78e921c5044a0b2a041fbd4f38f8e86d6ac247ef194c3e725b7041946840fa826402bea26e3131e1b6e08228d180f405c90472d25061c7b042f507950898022d26d9a8b06358a1fa834ac4a43336f89871c9548065018fb2aea2d920e392a900cb825e323f5c5028118630d805fbe182428970613125f838a788435c90092dc19c22062c5385117d40b38451b720435c6f314079611e7632829c7c9411c43731e8a38c9761a432a2722dc12338387dced9c94e0a514a29a594524a29a5b493b36bf5112ad77ca9169318150f1a928f9157d13fd982aa5b1a826da2c7dcd20f8c53d2c010acf3e2d14b1e601d191ebdd401d64979f49210ac83f2e8250eb08ec9a39782609d93472f01c13a9c472f21619d92472ffdc03a9b47d75a31e78658f28175481ebdb401d6b11ebd748475aa472f69a0d44389072fb55847f388c5147f32a297801a0655a7721f585e8ccfb830fe32e3291a9741e328138d97609cbb46b887f1d335a27a181977528447f558e66d6f8874c6d58aba47f41819ff6944b0852f151de62ea1e96664749a0ff826be745a177c13539dd603be898eea342ef8269e3a6d077c134d9dc603be895ca76dc137d14b9d96856fa20ca9d3b2e09be83caadf3a4d0bbe89b6d3b0e09be8b5d3b0f04d74ed6a447c133dbb9a157c131dbbda157c13fdba9a0ef826d28fb99a166c135d75b52ed826d28eb52e98cb125d238a3970f220143b8d88238dd788624ea9a511955a1a91469dd2156d1869875ae2db4badd9a1a27fa5164cb564b4160395d1a645531110338bc6c3044223223941e3a154452382126554aa1273505d5248be4b559ee8db65a2fbcbab42c209dfa82ea52a28235510f40f651491f00de4785d14179411c74e65248340d39adfcdab9ec15326d049806df99637fef6018c045bd1b79f80d83e70cab215d1f8539698038324da34cc290b4cab08a635ddfd8369cd23b30551c0419f475abd9add4432b304104cfe83419205a645bb1921bee9d96af14d6348f8e622c2805a12e6084cab0b0efa07d37a2c02f50a3a4d2b06b6cc1c4ee1e4a9b50591b1c1290df4d56e18dda057ba6557e6bc49672e7e50c43a953d02b18ec6de52c0a3980409d6b9d86f502d01e3050c086444f102c6cb0a525a48a85a226349afcbf0af19195f61bce86040f0adf927238a6fc7b41730fa6505584a8b6fbfa4949fcbee6ba2a12a44598ab6b44ead53ebd46ab54e9bf47adaa00f6d198a3f6606e705078252142430482ba8fe9996f8e69734ac8cd78c2ae7c5b74be8311b7ac881e0db5b96a2803f49609056d041cda4851a4a0f75156804bb74ab91345027815e0512c12cb0086e4101a1805040282014100ae8db4f45a7a253d1a9e8545474123a099d844e42272178e16b34afc586c4cea016210655487cbbc9a79a974a5dfc1c4a1bfcc1996c989da9fbfa4bde515e73eeaed68ebb1f7c3afdd35cf3d9e55072ead52b855f9a5a3733596d00cdb7ee13a2f9e69a93b6feed6bff76913c5e5b4ff783af914cdde5248dfaa5512791b42d88e6395092671aed4e235dd817a5393f0cfcb66d5eb94e5b8add73014abb2aff5d2e3721f1eb06851a0afca16d5f0ffdc7d3f13ff81548cbcd8652a77594bffcea7ecd69f77594bf9ca77f736df3ea54f3129643b99c9457a7b7b45df9a5bb39dc6c98bff9ac57b74d0712b99b79eaa2dc5eebf1f2c328bde49bd67dd84b4ddb7ae37c2391485d7212d72412a9b4917aeb66daadcf98237df3e9f69e8ce03e1e8911b14f72eb1f7cf29867a82b89d8dfdcfadc82903c8752f7c1df3c73b9d9609fe42c59da7c4a52f76198c76d07abf37bbbf4fbc3c05727e08fd9f1c82ff9c7233f1ef9249f2e7332019743f5cc37ff88d8277576db9cb719aa67fe11b15f3b36e9cb40f5b80999f25b48fd4cc26ddba2fce6f049e368be39146a1cfecddb09f803fee6bded6c1d731de5e9c5b85ee8f35e5ac72653cfea7307aba46e46067e9207e127753325cf81e4d44b24a74e2af9e53fbfddcffea6dd8fe7352fdd4ffee69b66bb0f3ef9e9f5febc7689d8cfba7602eae8952c758c0aa6d3340e8f7c1e097be05acad8d95eb58c3e70ec9fac810d8015423138592f7fec81fb64addde5f25e18e63e5f7fb228984be9bc5507d2f0665e33bf5cdeec92de42bd92ed04d421fdf27ab3cca5acddf7f3d5e5ed2859cd5cbbf0e1a651cb1be6d9c51c72996399038917b828f5ec76945e658e61f4caa4e6d3e5e5d764e60c3bce39bb8f876621e7fce8e595ded9e530ddc6d7b6f1d3c6db780968cefa8f89e0d4bf4929f5e9c11cd3baac3be2c38d9739129487fdfc256fea3fd433eb7caceb93dde7c3b35b029da1f0d1db04dbdbbf52eabbbb1ee8aa68937acc3f9587ddd6111f6ebc7624280f1ef1a13dcc91c0e0b56717090c1ed6e364487b8f0264883611f351a56384ed10280341e51f1c5f01eaf2d1fb610a08eef36fbef20197b75fb519007eec689070d0a57fa9ef1ac6d87db3e32c7cf8379a8b2b3d64799ae72b3dcce021a9b25e5ed0b499e46fd55be8233d9d3a0cfbd86a9b18c4e3a7cfe8a0c621e1903e26f15628417f9290d4ea832053823053610a00dc420f1f1d6a699bd82dd611bd8873adfbfc33875ba2b45bb4c0a2220c72699b18dfe45f4de79cfc5bfdd0fcbb3f9d35ff00f0d39d75aa4faf5a75534dadd5a45552fc6af2e8dda4a5cde83e9275dd9245f0f041392045117ef0d565b69dda3127bb344e7fa99f341f392dd33ccb3453f7f96bb04b0c4d845d6058da85a40b8b1a476ad9723f225cf422235d46422d9d23bd28738a48721911841c18e1a7a34acedbce06e4b38f8b8a4cce8a392f1e310ceb300cd531db90fcd4b1344d730dc3660781ea6af5d33f968f929b2e748df5d1df08fd0fc8671d0ffdcb51db0ee4220d0b1660f1574af5b105bc629c0204c105bce2375df74e8710abb5d61a35c0f1f36f4b4e3e2f8f29fba3f3f0f102eba7cb8026930b64b0f385941753c7e0fdb80283a19677ea2ae6401a8915a0b0ae058ff828735a490799bacf016ff266c239e41f2cd611dd624e72ed56ec3693568205f52adae995d3a4632c7c74d36d92ef9064643a688345c02276be2041dd80306ccefaf1ccd75cfabc1f0f7dcd4b58d62dd8cad285c8a8ca96a1a2d610922a405ffd63a221afb22262d53a4bda0e3d7f5d3bc4bfbcf2ccbf5cbb1d563299ae6081e90499d203bee1cf4cefc3210198352f8f17da267a077510048240acd332c9118d233f2a3154b4c44fc7e477ab71ea47ef239dc3a36d264edd910209dbf0cf1d298698f5598dc3bf6d1f87d886834cb901dbf07ff3a7d3ed211004627d8cde42f5e83bf847d59958cec92396a457d16b6c31d0a7628328cf537e20f41f8bd538d5375804dfc48baa4b4e8e3a29e91ed1bb7610a8087844db44afb107ae5b9107eeeba0a2235a41ac239530e91cc8a5ce0b8d7a1584e458f7b11ed3ba387f3ad639279f9a5bafda855d209716a7f9078d1e1af52abae640fa74bb882439acb6893e6111d37b0c8e05e4861afd051a1a070be2e183003274f008edf24f875cc9e7854062446f31b8af5951e038dac08fd325d638a6ea9365ba2723b8ea999bbcdaeabed1f0f3a6daf108838b9d44db762676a56b9f8f25272e8d63fd93457c746cdbb13478e45f4e639b4100638c31c65f4631878718fb53078b2822494325d5bb8f67ceee23c27acc65d67dfd986b5eabcfcb5578c8c78b7f2c267ab9cc2b968faf27ff309433e7a4fc53cd10c216419fff4cddca5c2ff579a5633bf8fce5d9dd01fe855d7e19d65f2eb3cdf0c2022c58f05707b3402e9600832c471002079698e28537318c6ba152e33851d906c7591afb0aa1c46c4be9bd7a7965d52b685947c6ee2120f615c2e8f0258ca4ee224a8634df399ebd9dc07d9662dc389b63d6fac7e168fea5240dc9bf9a974e9d6403dd5e895ddab6232f7be1d7abcae89ddddc6688b12204267ecad4653b07fb1b998c3db79e5354ae25db3845a6f46c079fd65f9d8d0eeb63da67d3528cc6d94d8f3fb367875c703f189c5a2a574671daaac4b25a75e8aad28b437753caa1d79c9c077b95435f358e8c43c7d138341c3aab718aec00c7f819ce837d0d8737fcfb316538f4863f647008a1738dc3915033ac43b3028e0a0e9d008d63c36f70e837c41c1b1cba0bfce386c3fb5db7922ebf8ee7ee1204b8bd4b0ee0381ca0e2808357bb914a9ce984c20187cb1e020e20c44db7008eba3378e9d2e0a46b009fb746a5a10042bcd670b16843dd0034cc50002135aba741b2b6642d67adc9da93b5286b53d6be585be3dbbba4069fb9964ba9686eb49b58478d1935dc1abc965e644c9fbc636e9dace1b267badd85fb56dce5727c2dcdc9cd699af334276aced49c2f73ca98f3e671c87099756c167b4e31aff015b333d773156e90203ee30ec0632e011ce6ee38e9c670e37702438d451b08a4dee0004d869904689eb9d6294002d16609175dae808b1e55c0c52e2ae1ba0b449bd6a1ebe129403ce109cf278ae139894a38e83f5e3c0107bbd49094a0ffd88887986012c68c10f9f1bf9f97ddb495c62578e00452c800e88a4b3cc188c3e6104964a10507b004800956087999ff78ad0be87002ce723b3140783f152701495d5e22aabf9c24c50842f07a32f78155ae5081e764fa8f57b59bddee23a27ae9b4c26d0793ceba5a373344d3b2dbcd0cc9fcc7fbf180448dbacf0ef0bd4bbbba0bf4aa9d27096e4a971d06754007d2746688e6d56bd70395a02205483c4d1ae1840a02fd008b91975d6984131e6f41e263bdca6e069278da6526f24194214fbb3340f06a3fa106423009450461a89398e244cbd3ee0c27dd830efc30c4720212402db0a009a29921996bae753db3c996202feb7ebc9a611746273633d290cdab4efa0ffe92daf5f878401a32018229429e7667aef02a0661f06188c8cb6eddc2ab9099219ca4ef478716535a9965d8eae28996c74a58c203431efb0c26bc1948789986a580632d5b54918507a6a20a0f7461f94082632d8f821c28c109c6f0b2bb6308af4eab5d5f0571ace53f7fed72d126a280632dcf5afee332259e0f0a0a3ca678f2010f0e418824bcace38c052022a06a6734264984bc520b86803c521843439e766b0fbcda36bb7c8321292dafe7f281e7936a81a779767b30120ca1a569ee73bda00a263c8da360e209208fb1d0c08b20af8a2c4300f23e90440e843cedb29e7858940082249e766980f0aab51be041908785c7cec11f6d621dfeed8798a343059421dee2b107b1efe719193ed869376e7105172a435e76b71f74a88032e46597bd4ab2daede9221100793e9b12bcecf6f015cf8771e0653e0650508f4f05c368c8cbdc87adb0e2022feb9ccc40ff79d3911900ad9a64f1b22eeb813cc060c8ebf1d1a1c5949697b90f8f2552bcac8b39c0c2064924f1b26b8fbc8ab2daed812d2d429e4f8be171f7f85217852f5c49dfcf11826062c8cb6ebde255ae9039362c955ebeec3e23403537e8978d6d1a420821841072dde43ebb2a06cb6a1ae7e59452ca181b87a95849f29ffdee4e418da48172a485480d16a7179c0209469b609a5b4ad20b1ab5a84daf8ae3482507562a9c7309c63107ebb8b6918e750e7bd2b9ce919ebca20323bc7c59f3d2088effe3727828765c2a7b9eb2040aff711716d3950e7429310fa03c31020f47b4c04a142c2879c2500e3c40840e94683f70369eaf74c08aa7cf573a30851256b8b0f26c8507555878b6c203113c44d238995f0e85986d8dfe9ad0ff46e70d2302073338ef1129bb6fb65cc0cdc0a219a8e5fba97f1c46909e5e5d100914a257975f0e9d7059763fecb75cded338f22f872d78c4a743bf5707ee14c4358c974772975f4e38cc3fecafeb72052ca071a45fee00f803fee512681cf89713691ce897ebd0385c3f5be141112f3d3ad619cd2358eadf0b569ebd8fb13cfbf441808945055cdef48c4505ade7e67e42d103237e3a8d370e53a9f2819f5ed3393bbce9383a477a354871a509308cf0c00a80f0669115536400042b5c1891c09bddd704c7ffc91cd8b20d6da0c7245ccc33152a5dbe3e43b10325fe935c0c206c082184b06574f9b1e381df0ea180cfb24810fe1d32f09f910fa27a8e29d5c48e4ddcabd87535bd628790ca14430f39092112956b3ed87ab9721f2cd6e279b7fb70799d0018dd87cb8bfee3c16da71570830c408624d105749080039ccc08612d0f9db53cec6cafa4d7485271f1ce1c80bd1f8fca8ebb87d7b0d51db5e81863e49b76d8d04e80632a236812c685f2f9ca962e49949cf80870a467af2fc856208f50a50b7daa57dcc95e4d8c6fda6528c06dff59eb19eb70076a70e3c56a1ad6a042e72120261102290121dcf85abe698f37029ce5c1083e025ce52b4044ff6130f266035bd370cb15a0249fa2e921ba3c5319c111ffe9d0b8eb0ad096672a26f8e23fd5374de71c7172516f57d5740ee74b38afd9bd1c6e41b84bd336ed9773d709850047bb1921d42fff5458e69cfd68375a3fcdbaaf3ff3e8dafd7c5efb2ce0f39993dc5667d629f9d47620b976ddde1c489d11f8d44b4f9330799dd7b618b28ec2d67f9ac7195c78adcbfc82f2585b4869763ff859841bf38d74cd3aa91ba2ca6309e0f8ca10591ef352dcaa6757f37a85e87ce654f38ffa052496ee0c9695ea6b9fe69ad3aaddcd99757220a2f35947019dcf3ceb8a64ce83fd75518a39954eedfda6cbeab5de4ff32bebf8ab675a777599f36704fe0781341673b8568bb3a4c6c4a85cf3c9a28fcc17eb21f310248a36d1210f713cf4409a2846a7dde743761f6be52a0d1241a28f1e8b624efbf0d140acd54a28da4455033df7dd87fd0bdc8fd7310beb4087ad183aa7c709ec882011acc23a977f97b7be5bb007b63c9eeb673b6c7d5f3fbb1c76badb81c7f87623d777f7fdfcf002c79eec3eae0224e6c096179d50c5c72718808829d10044dc80a7f8e81facf21088e7221c7182953142d00422aa78cec59ce8ac9813dd47cc892e8bae2b2ae4986d448912258a95af505cd14473eceb970d9f8bef2d48f79857e8c5d7d6f2350ae1abdcf21f7dc9031445e08275308f1dfd19e61b919f39768b1e8a2ba27c3bad57474d6a9a363588c990be1f258f79bc0cc5153ef8ebd68f3aacb5cb817af51c48d5619016d2a44d062006a05ebb2f3addc1d8ca18583e63e666a6576e34fc3c410d40312011485f17ca8e3b180453276a8d1be08682e2111d4a37c04dcf5c7a6b9aa66914ca4b9f515e761f112bab3c47b809b93e63f6392bb3cbe696cedd7781e748e58990962fbe969899f9da76a8531bb097fdb3c3f552f6ddb2e5e5366f109e891dfff4befc58e344a17b42e13f29a5ec01c63df88f9939f2bd4564eb77e8e1618c6f8f4738e8fc4de975ca2dc8ecb018bad5ddfa7879e447e6c1c397dec5db925dba0d9c83e41660a1858b8c0578028bfc7e828857fcdd6153f6e512c6be441190c032d2864c7f32b8a1a07e18c3f698622ef5315c5d8bd1dddd31768cb1bbfb78e44561e390315a2927892f28add05891b2a2ecb4eec9fbf3952ea2f88192673d5f29e2f239707ca58b2749defec935ffe22bc7c0f1152fbafceaf98a17440f04245f9f1425ca57128651c77cdb48ce9d8322751fed3e48da6ca8af6d5b900837d7f835e72876dbd9e8953bdae5f692fce4cdc2b6eea3b506338bccb4d0e2944516335860512314b158b9b5b78d583887b03ba2c9039a40e0852b7e04e00a175cf08123000108400052881021422014042000010860e21ca6e218c00006304088a8ca109408d8084460dbe16d8894a08fc036448a12d8e174518c90e889e0e8a0478e0e5a68810556d709b0acd0b1c38a9b1b1c37a016c622db06ba63b1d290e621c4c1a2c00d229b15886cd850a1c6a8088b4515a8a90200295471afd3e82410098615b17410a241c58c1a54cccc70980ca66a22040546698e7507d51598982b2f2fa95303c19603ba632bcbd722365ae1be0e92122425480a932a2f07fbf6691b228549e33079d2380d7d8814a15ef1cb1ba7e060674df076935ea1b6ce0127e54937440a935ef1f3d7d21029418dd35282864811ea1c6e269dd34252944869d22bfe4f7292d29e43a43c91222445899420d236444a9094235a488a925ef10369bbcdd05f37f938444a50afba49e304350e3f165931c75da5e2b88e31c621062ca9cc10e208d2afe2a49cd3078fca7fd129312c36cc5ed861e768d8ee4c49ea96828b55b8cfc6218e672a0a8eb53a82e38e0af7798c824bb1431d70dcf160e180c371032e46c1d5d880e35e69a50cbb68694a929445e6a414c3b24cd3583b75c56caddcb83885db61ad616c2c65bbfb46ce6e24e6380e6adc4e7fe50cbb348d6a53464d6b482aed008a308e78f204097311145cfb10077b7277c71dd62bfe796c0738c6abe2131cfdcf9f53bdea6a7ad54560d111685cd52ba803ca1c7dc3ec3862645f61dea036578c31461f2cb0a165588aa379975292a053b170c4183f6b4dab1a966952332f988c8c9814a762669918991966a661d5a9060d93d7b0ccccec920606a5eeae481d848112550accac4266035b598e31c6688359d8bae192053e525c8c296ec8076be537ad23478c91a563db616716694608eba3d38f3af4cac7085250c1c6ca06b6c210329d50a917ee980b518cb1a36c39bdc6ec19cc16dfb0cf8e1d430d09e5086a77773733737bfcfe1a90c4a74b0d20921825d2a0327777b74bd92d39bee11ab014394687ddb3fb62d73b087ae87c3f2e6a672ec2a132e340ff66777777430dd438e3a4133690033e7f86b0578dcdc933a32841ae55b073660cfbeee85254e8842b358448b8540a25619c73ce39e79c334266292177df9c5e634aaaa4521a1a6337a57f734e0cc2863ae6e418539152ae578c33a59cd74ea7734e9f9d9c702a71f947bd2d335f2804865d981093af4983a0f4422450879441401dd22914623a14822ff6eb46e98452cae22476c1abfb28a558f761fedc4be6d8865d5e8ec9d9ab4e32a55d179fa851ca6e4e0947d944cd2886b14bd62ada7013424d34b1921b76456d62f4ba289c32b625cd6ddb615226a594918b31b294524e4c66180d4cc565d681348fe8449c414d4a09ce6a755535bb6d16f260e1586117e552a6694af5ea04c523aa697aa50407bd09dc01073dd52b76d88483b6ab914529a594d1ed84cc1c779a6d326b131c8c24be5693418a632aa5949226062f286161f14914432f28e194c4264f7844264cb16124c94e4a29a5942c9b95300e061c744a8249657c6927d5f28d4b70d0a3904a4a09046b6fea4f281377e24a24df64a4381b23032b849cb596c9a8321929eca2e156e3aab763575e8f9d0b38e830bec0a62083859ab270c33a1d7e467629a5e4287164ecfe218bc0e7e9e731c6782979fa999923c6239f27339491e5d2afeb7a7a5decf333bba60d500650077bbcf009d4217d4e8632803ae494377339b32c5ef162396d3b980e4fe5f4cbbf397dc60b8bee327a7401073d3ae1a07f92339da4ec20aaa652adbdbc64ed5266322e988eaa148de1b12f999959ca0fdb7658029f8a1d93a12c709287ea70bf1411959489334dc3d02109382dc3bec2492facb2dd48258e4d0d4b2569a4b3b6e1873a239f7e2d113dc637ecfdddf30dc3508bc4d2d77fbbff81d0abf079dbb98193542cd54fbfca49532f325fabdd2ce9f495b37de55753ea67667c8dafa89389e34aa46d3b35641910c217200b769433369c1de5a472f2880d2f9e240921f6ed34dc5f5fe935bf1d9b89e17a6eef5675d645723c77b00855e1bb2bc2336720a6a25f8467fe0cfcec7fbe25f4f84c20138e7b155b10bbc884abd33217b17fedb05bb57df6734e21fdad3dfd9ed2b90b8347afbc618c2ce070c49f8f33b8ecfa1ad55c5ab02c99152d2ac10d41c8c2b1aa5604dc4c11caf593e7e9778e89493e7747745004dca9c87c224c003d4f7faa8322e0b86dceaec7c7ebf23cfd35cfd70ed3354dd2943192480d39cb2c33f7e921845cf3cc973f4faf1ba20d3b843ce08a6fd87f7ee600ecd1e918a06e92add60c84b7bfe368f3ab97afb06ddf6ffefd2a2749f20d7b7661d0f674edb56d88d4b621b06f7c6df6edaff56ef02bc9f6fd4ab6ef077f4648161ef9ddcd086987ff753723e483dd67bf2d11fbdcab867d25ecee3eecf9e76b246255935e9f7c2c63ffd267e4a16c1b48804088c00359804199b8228a31a200946508472eb822055e8f4f1df29cb4fbc8304ae039813d8cf0d85985c67a159bd48e4c388630888e537a11166da237f650764744f5b29d025cf439c038458bc0074010c2f3c18c5002af7801172ebc1e9f79a50b124a7aa4e0024ce2c8104bf07c280f4478ed83e33584abca31a787ad84a18222e93d3d3ed17dba28074d78d27d1a093008a267c7105e8fcfb5014f764eda7dae0d78b17302dd868ab114c09427803ced967c60711123280843b46080032a47786104196c814850b10328618811c5d015af5e5ba638c982d9ecb2154640d1ca584f3c1f54965e428b1fb278bc032954dc8ee2030f803cede6f8c2abf504475250e469b7460aaf5a9b5da8a4075b84362e4bb668916c76d90645404290a7dd992ebc5ab2d9ed265cbe08f2b45bb303af725570c14190c966b7472546932c5ee0f9c02ca86030c0f07c805ed6f53014da1649e0d2f2b45ba2e2555415217c3194b2d965296a3025c8d32e13618a575f64b0c42002086249120223e098228b4210c4f4a18b2164a1500441d0e20438a65c5486a0040cac4a12a02421a3c204133d68465088a855b0a0e0488d175e155766108597f94f8d179ee63f1ef6fdbcec6184077b788a2bbe10f27c260aa0c03dec61042b732d0a195c1103cf471e1de165533891822b3c9f5902202fe39eb8a8c42c1785450c19a219003000000173154020281410894583e160928579a40f14800d7992485e52349347034990a3380ee218460820c01060802188d0d08c5401964c002d4c17d2bb544f46cfd03da53d32f584ec598a1eebde56f8ed270749132f6648a8d29035bd16c66bec1147e0c93273e147107524f6d3d0b0c9b15c6881a4e304db1c302b24f009461ea8c1c9f493b0e3239a34fefbda471c7ce1e1815323a75b582867485f2491f4edebfec75a5857579b4ed0890cbdeb78d1736ab9b119a7df402ad7ebde8ff00bf68850ac95620d33ff05b41c9a59a16c158b3631448dbe25540419e63a3f13562885e5ff9420846dd191a43ce6a762bbe984ce0ae51f849fcebceb20db59c8424a89bd8258bb89a56f76b888327dee8722cfadced625e9407c2801a5ea01f654d44daf3f96e18b93a0aa6595d4485705d32349433b30e896171863142baeb4b70badf10f5522602c005c4ffd920c24d0e3684bf10747ee9e520cd6bd432a156b88599c0cf9f5079ccc33f26d027ec4004a421e7b4088db5fb44e5add768cf3fba0487a297ca1a2ba16b66c97ee82163a651ffe0d88323d79fee10a5456a184b4c0b8610bf862c84c35d5712002b743cf08e57da4a0deda209a23ccc625b1cf7575bcf06cad98bb3f83c27f2e5f0136f4a63ff21b10e57af346478a04f114e78b9c2218d12226a5d6fa0badb1b14bec5c4f611cb32e6c992aa4e5f4fcd13ab9dc9db4bbe51ae7f74195f5d093871702324b5d3e240613bb488eda377d18a970e18c401f5d2f070b10e45e96db99f1fe7da00dd24b85146071f7439aa02ec9a53dbc4c7fd9602c8634e6039403e4df7c3895722146a3882c435e99944796cd368e647e28d0eb0bff4a96fdbd5e681d7e745ca47bb3dce68cfb77a0caf4e4f5c33e0147bae5ab2660b2ccaabff9790265506504a217cbe31a270aa54f062c0e704785dfb5b78047450cdb62b6cb1c8267a7e1ac63c79208db38b52947a7b0453d931d3b4412fb5f10d1c735a72ffd4dc685fc363da953a447eed332c2c25a7094d1f2603a54eba6454f0270eb32df83d5adf239a8b8417a405d536da93e1f1b74b3c8c23f1f3e88ff938a64f563728987e27e8a9eeb40821854aaaef73007577e99818555fab8810f96e18539b45440ae5e83f9b848d1383c80fd873fd6ea9a3a1b5a6bf7ca02a9650bb8441afbb63ee912f83afadc3b73378d5e1042d046fd9904d707f1e2086c3a382c3d7c254074513f7c851b585c2c6e8cc415cc906c737bfcebe6d99e778e436bfa966df6660378225087b452bac477c0a2795d94654962113016d032e9553fd54a99e915a3ebd3cffcd14435b212c668194565ee579f92248583d018731dfd0f4654543e8dfeb4676afea6830087c4218e657323037401d604689d347e6ba742e226a961a2977cfb2829b155e48e26d36c2797bf9923711479b7854f0e04fc9a0c28898e4ece4b837fd42ee98c23346dc21062e5f3d20b785f0140a85afac2222fc801d4a7cdebae5031c2c8e8c42c2be342699e68c39e287cc1516d475c361a0061368010fc957a2a92fb43a7f562463fbeff7ed104aee38b1a8dc21e3f224b52b162eb46c0bc309632a01b30c76b58b0192cbac8e661d8307b2245e6ba6c362b81369ae670c9400c1db60a4982e6f999e5157305b217299b9b31c98ecc2bffa0529fc9f3e9527bdfe5ad71f244180bfd184cb36adee11e55c174cd8ee8bbed3631585288e0647996f6a8d3f36e5e72facd144e0ca6258e0d63eea40667ad316afaf0e772211bbe9eea361e7bbb3dd726c8be3a9326efb7d2ba8db6eae433703c2c30418534633b9a9c0eb185048dcab8b6c24a781683140180ec2bd7721d6974b2e798a2c48921b593843cd7c52fbbd0516eebabc27c98fd13666cbfaeada7b3dc4ead0fbf52d1a79bb4ed2c0fe9a71326210817369b5192dcfb9480d9b382df7a687ebff1b2cb84e56a88c7e859d47131a09f96ee043ce2061135078ccf93a55e2488b1c2b5fbd380e52ce73e9760069373d5d8c3d2ae43b6405a27d5bdb83a948cbfa43bb084a16f74b14375c54e63afc1e7f1a8914e8438f09c27ff0c9fdb9c74559904474ce32e94755e9365b98009511e9bc5692b5254965c1d17a4575988d38f05d10ae0d5fc345202d6bd6b894eb6c1e113034e74e9e0e886408c6a1443046d982b971a2812c0de06a6b62f332d3b89d1f315a96a59b0892f208015b7985e86c70f7764a538d18780f797ff4103742ab7b29c5bce93e219fdcbf7f7cfe4b9823b68c672fca403bafa4f220baaf52f09d759687fa7584cbd2bcd4a046919f4ccb2a8fa2f8f46f5162eb3283a296920f1bf5ca6d42f87c861dd743d23449b94e2ef2214d938e9196eff1191aed0f2f44c50fd116b22b60e00606237afe3386d00a6d20b948752608cdb5df80a29b1dc992165e3581552ea87f2965dd5c49f2fef03a1b2ba179d6c869ea51e0d92221c7f560a755c92b1e18ece3bab7d747c882b69a90a5a1a4d81d1fa8eb0174f03abbabd2a60fe342871c242152e4e3a1d399440f15b3d86a2361f4c58c122ee3924f1adeb01734e64ae6248e74d3c2871d82df39eac707020b99213616b38c990fc7ba0bff7af97edbabf4ca5582e378b0844fe2788fd3b6691e924b3fe3cd4af4600d4a4debd4d604e34136fabedfc122d3c2333de2585379699b5d40bd3aec08830ab46a07c34044f2e01012589524115651d04323073cd5fa06cb9428a51906c606a8c8d720d8f623e00d7c09498da21fb3795534b14139f6b832ff249608f681a096bf08d6775177a04172f7d13dfe9cc4fa799a91a12209497daabd63c2b1a627bf1050ceec0a4d78722ffd3894b03c20f140b50670766f944ae50a7dc90f6078db8375180fd119d0f4e0c6598a5e87d8591ede265592ab5ed233bfd8815028b60f673d0164a717646876a1d412e6819bda2d5b2e897f6e1b748068ddba65075d1faddd1fc2ef3590fb9299fc36efaf7ed03610647fef0c5be0532c8dc7988d53db5bba3f3b54cc72a70abfe0876f37386aa518d109ecb51a7a725ac3b36daeea8f925011a38b7ece60af38c41d177f35568a914590d6887e7e6d1d2181c518065f43ae44cac314405fedc771201c3cd2aa1f75ce8ff01000673ee7c0daa65915b62471e1e8700db4abd51120cae99f03ac4450a87f999b6ff61f68feddc6694b11adc80beec14e5e78e610986020e0d28b0bd6f8466d23bd3d93d7d01fd1a042e11fa82428784f7277515c12fbffa22ff773df968f6f13646dbaef3a7513fdbcf245048a5ec1af3c1f51fbf5b4d5a7d40b1e7b060d3c046723e9f67eb7be4686030d9c0eeb5addf65d626ad26044a25b738fbaa90913248ca308b7605918ea159a35bd1b6a451744f51da7c0cdb1cb754677b00026beba99ca72d1e2a9036317c4f321b48f061a997c8e9b413309c7430487cd8fd7858c6115596bb1e34e1bdad7dbf7364f5e5e7e4d58b67e014404abf7ebe211a5f1890fe0f5dc08295e1e4018e974a92b30c11ec0076db9d26a240c74806682cfc8aab35657ee4971c3b2c87fda34839792a1a3eb55656c781afee68434376fcc38dafea3af2d3ddbe1b455f04b77dc609b7a9ec4c67989306f47557fcf5410e02c2be31e26529fa9092ae3fa88718b19121cd695f9dafb3d7146179379439c6bdb659d8791c6020522810f12c5416384ac658f561a06e8d6f998662f17f4b28c9a9c8344ce0b2444e263abf41589ef1609258289cdb27cc1a743a85510e8be01b90643c9e9c68b71f0a02b694145784a05e89457b1cb4b1a60177db6d136dded40a5274bd9897bedfbe22dc736b16a586df010b2663ca5ef84199b5c99d5e732b753a3e5faadd8abfc1798a496e6694e9404f7a571d83f6a5e6245e6e75784ae9e2253aa3ea6f008d669aa9a1618443fe13cc9a7fc663f652a03817978b52064ce46316711002c88cfd0202006b10f3f6b4f71921a1edfeca1aeab81bc98c8aeeb9cf67bcada1e310f4616fa2df4add63e8d85a8268fe68049ed585ac5b6af39a3204e3f4a108e8ece61eeb5b5b2d98df46a34e823bb97b39f601a850362b61cb602fc80a9ceb2ab919665316de2550ef67eabdd4b374f043c2fa6a4ae16a3891f7ba1545070ba276c20282a75cb7b048cde52c30466d84a847f013b778cb8f11d59eafceb25e1d8dcf9cdf5be5b7eb29ec38836b3b7ad45c6940d408347fd02e44f2f37ca806596519d774fde23d73081855d344192dc0d4b69705ed3c33869720b0d9172d1eead465413019d8ab9e3490139124bbf91e2922fdac82923244d6fe470c3057c1f96b8bc847c0fee1f67a11d875e16c50a635602c721416d184b0310defa5292d2417d3f035e886d2a5d9c748dc862a0afda5e46e12ef0aed29ab12c2abc52b1fa537df578b86b52aaf999285ae6b1117d0919738ebec81cfdbdb3461f4c47688fe921d5a4627a5b760636003b17274b531fa42cb71aeb1ea5630c42b8932b8510974dc28eac633512c21bfab5314e30f1b70d1e8cbbbe079302add1eed65a4b2030e62bf0dec24ed191bdafc68778dbe80267e002ec1a42546ad4e8806674e312e4f9fd4d170d1975f5615db12c76d47cef6c7639741b7f476765ba84494070504bd3b437be21bf516cffa52a2addb2a24cae1b7fadf70f3d74acb4d95478e671bba57593583927003ca6013175a1e30a25f128296666fb7215906298986ea60a794d904ad964834be3b7503eb21bea27e3df4ed1ab29d120e1ba490a51b32fc83edd4d55d543b303a93cd3077748b0ad0ff7ce0f923ba64103dfbe621a5189cf0a24189519f622c62bfc859af545b002e488dc6f75e4512c635a3a9f736ff15e889468840d1212c2417583769ffb856962d73163802baa681fe441d7ed205a205e1c962277a886c2aac7ee489637a6e24b77b28ac30186d42e3d5795564cb194e14300fbba7f889874ecda9531a7bad2c4e49a350250f73ba58c965b7ecc65a0078bae20056343ab3166b4519e5e0cda6ffeaeb5e25782c2baea4dedd44a1db6f4d65910d39b250d1f3e594ba040da47c9270bf1b6b0d891b411cceb48b2a4640bd5e341b83e3877ffac1875dc2f3bc6333e0d56e64f320ade7591bc2d45e4335f128a1a8040bd674f41e7e62523a2720bc24d615e3e8485d496287885f8a2004c792cd12a19915bc55a2f62d5927c51144520ed83d105c9fb2f5196a6dd50bf7f44646dad2d0bf7d8fdedee0491e6546a6e4edb41da5aea049898fc27d649a099638ded242d7e8c5c639e706cbee3428c6127176fa2a99fb8c1644afbba8169502252c40940eb29a433f8b1c23f3652b45e9e622edf0368d1fa6ae9da44550c6a2eb8e4269300a5dd92762588a54a4e6ebc620b1d535cbf304a61a2608b33fa767a5e4ee3cb064594d6b3f2d44614403a140b61f22c05c60222fb0cd1e33bac13749767a5be6d5e381985400d2b6fd1eae2727b827c669929edfba0179c9a02c87c8adf80a3c3ed6d594f03109b329a4ac8b210904bd75ec077a8d2cbcc61931490a57f33d9616d3a65f41157596858c15cd58f40ff607d142b2a84c8441ada57049b62b289459c56203292ceda2741f0810f308d5f350e0c12ae52f70e9202d5c12643b1e92b14d21366fdba8767bea171f5bbec296f060a2faa6113628abe1ee07bf5666dc7df46d2eb4244e48d0faeb810577dff6e67a5736ffe1635aeb2225821e2c162fa97f6ec27a3556337e8461b55ce431b33cb143fe1a2dc6201fe1b723ec3d22e78cf5fa244a3b4e6eb3d1a3c5c17e9e73f7d05466e3d4edfa68ed48e9f66275fbdfab429afd3d15c58e4a7d3bc9d82ef305e9b227132dc94a639b71ae8c6c32c8b57897a0c706e2a50bc6242d1d9d8191c0b0b9288ef92e8c83c3841c76da99e14951c1baf4a9523a197de202d20646977651a710c7b902deb19d49fa1134fe157ca1ac4b3b04cc5202a6879c091ef987f5919df7e480af466d04866647e66c8ae99ac765f775c88f2816407ff4c8387f4600016f43ea3f1be53bc2b349977460c862fa0b6d0550f077364a60cb9f3c810f76f163362ecd0ec6887f09f1d14faa86065383760ad95f8099860431592ad4d34913bf4ea27e02584cb9b57972cc82aecd45aa4ca2bc1f40cc4b62263dc888d9299a985fb6fe42a9b45d582a48d2ca48d13e4396f0efec37968245b90348f1cc4c80b32a574d66bf21be6a8b3ef829f98198f1a6465415886fa05fb56188305b199757d7bed7f009707346b6c35053cac9bfcc677bf9968aa06fc94f64877444532e0efd443c2357c610de2990490b3438c556251cf83e2d651eb368fd61b63827ccb67d05f3f4894ccbbe1665ac5fec0e179476809583cc43aadef18a7bbb630e7c23a985222e77600ad54af83790aca42750f80c1cff067502db0496875796800fb4203ef3359a03948dd931e42550846b05b1a7b8a6c9e1aadebde5b45bb5c92a06187f692d739a065f61bd71793946ad32bd7174c98202ea7876404e369d7fac85d881c62c2375cdc2c637f484ac4ea8b341137e05ed01e8880c339354fdc82f7e87e2fcb80587dee3e79854460f1568674d6cee225ff598b76d93d6872ad7c65a6f955b44f24a2475cdc495b85916fcb1c9a0e5d683af49beab7f5c278bb5459750c1a0857e83157272fb16282f65c1f45547f811e1beb61d0c5e86eacf29b2de59b3eb44eb70e9790f65f29c2a1be890cfa026cf595b9e73449f9219ea680f92c2f3c5b42209df68741c27b0a67266ee9220240037ae45d3436b5324f4c414295fab598ac5ba35ef98c8fcfea3e40bf591ef5679556b330a3e500178a0f1559dcca9dca8a023bf251681b42a5306b676996bad07bd7a840f2979e6de5efa5cab0ad8ebabf47a35c69c6f931ea83430e23426b9b81efee074b443aaca3a279ff6ce0f8ea40e6d91053487f7501448a18ff287021f3877e5c220b8b13df0e1d3e56928648f22184af163c4d111aeb7897d429fe921a1806bd930c35645a262a2bc773d94c6dfa45464dd9aaf2f1ab686f2c4b5c5afb1fa25aa85dcc8659d327a140d2ca1babb3d744e45c3cb463cceaf48965c7771552fa42eed09905ec854a933c96c4e464406018cff2291f3e6d68246f9330b166dd8bdbc0c366b9efcd698dcb73b5fcef754c115443cb223d06291a0fa8d44205d234050325c31cd94095c018392541d0cbac3c8b83e91d7b964a0f09b59485f260bec1b85c6384a923af4b8d2749c404d9e35416b7df8cd5883e91de5cb8442e734b58d5a177ed8dbf71cff179b3bb109f6a17ebe0734c98d144db15ff4a5407260ca26afe95d7a63c13be099b2630ead8a928745b425b71894f25407eee51dbbbeff2b6da80f643515f72c6264548fe8c528bc4c0deacc5a2bffcd51a1ed00c4b6d2d44651fd6bdac8cf77ed747cc3c82f36331ef733cb09f87ad22c15691b0a063d7a4cc86f4351191263ec16c5fbe122baaed47940efb45a7674ddc00c8f9100c37822158daab77af24edac03c7b44947b3516c4d4750455834b16568b1c096893fe68e24cbd249851925887481044cbd4c418cfa2497721880236defb8791ce759102bdaa3eace4002948ae842f8cc4efb06e6023a2c1540e1e94d49240d34bf2817642209472f848a2720525a30dfdb745e8933279ada141e22176ac7230da9bde360312baca62ab15d92ecb07fce14eb7cc243a139253e6415e86c2d5fd4a7fc22d5a6f41ce12250f6ffa43509b5d3a04771f16244f856eadf26d3464f64a7b3587212050b99511c7419d2c873d04b4a9feda1e4264d2b8350a4ead215bf91fcf8f0b85b07d0e1a34958b38e242f9e381fe999e584520f6b3522fbc273778fc3ddf994c4be8b845c9a5fd39905323f06edb7b307ad0171c22974ac79c3eab1eab227fe30bd00d3105656fcdda865f150142f90540907d65813cf08612bff9a1e93aaf8984a275d648afdd9254f2f2bc9e7d7d4a9402019118f28c32191ba9f21b44ce3b3c12729d069adf88eb4886123a71f3aa478e911c02172e8d30a2fa601c2ed02cfc19c911a58a2dd78393fc71253663d93d0c1a1c995c7882ace05735407baa5a8f4a12f547586c826b95eb274988462a791701f5b020bc0c0d2248d034f7c6ddd823eec4a026895295c1024178f2987b9dcd1cbfe91cd8924fee462c3349a0a772b805356dce30b02de9cce804833ce603a720f59b823195200142600274115b0549e05f74443f2b125422471669b42b6eb064a4d6c4f167a32623789fc3e5951680197475eb43be6788c075f6b1ca4ab43ce62b7414f247839ca093baa889b00c4614682ff24db5052f3d5ee6f194f9467edc32966a5351f0e10ffaab230af2ccf047d17ab34b6dfb81e3f3acb224ae7ceb3b63df4bd586a0043fb20d9c11b9d6ca084acb877a65784aae71357022695c65302be5da4bfbb81b21d81596ed79a57be74b5e4831490a20b6da2cf38dc0e2d13832f59f5a49ccfc1fac643877644029976deca69e6dd27b63591373d25f159c47ab27ce1958c8274dc696ced1f68953a9a9b0aeae217c70bd1731f2ceb41e2d825b858618d90e2e89bfaf4330c578fef003544c87f9ef8dc009b245a2e4030110dadf5c079f7e992def0402556061a58c128d96e042d0e87c993da560103645d26daa75e1a99cc6769f47c0d3a7fe48e2181d9c70aa9086adc8e7b8b67a1c48aa31a80db1f064838d93217691156b82297f12dd743bf378d99b4c8b8fa4f83d41f94c2119375f8f7f456b4c1abbc9ec0db9d889f63e3d9ac708e2a514e132c5e482a275614516d7a290413fedc74ba8e14ee36b67ebad38b18ca940216c8684a593ddd229f5c8d39b041a4296fe40993f9052865a66bfd3c6e26f54e97864985fcb3ffe0fbfc9a5a3a3513f4051f705d512c5e95ae551fd3cd4d283049529a2d5454e835c053ed4b5e226a3de655c20e2ea90c2f446133ce3e9ba19b3b9774da9b484170fda7397fc791a8d9e799afd09094698bbe9c090c0e516dc2c1010f00d8befd155ecbd774417ae1979fb9b1c228c39c531674a10c8a727bfa33558e135ae3f7c730749a620b5bfec5b9682f59b4cc4f5627f4b17c7424daeedde98f688260508af49d19bb97e8dafd46333f758623c5bbff4be121a7bbf0c50bd7266730a59c458878789e8380edc00e357e79bed31418c2004f0cdc5fadb41d745a86e89f6a680e026e090f94588693bc8af2783f2179ae44ecde3a3348222875c1b0162659cb484ae6745b9f0c9983a5bc1f03bada53e9d6eb5cb3e8210887eb33a846a1ecdc1cda3104b7b937b23fcd9697e33fc7f4f44c32709aeda400ee3dcc1a0b7c1ab393e62839b3fcc32d91d6c039e73c6d38dcf10991b4a55193f1fa69bf5cc1c255cb166408710f04ffcf228011453c12a9730142be085f01fed751bf7ba9959078b55e1904b4eb5a82302d4853ea416ed13729a6aafc81002b07eb8ff2ab224087641b7435ad16998b4868132a4e527282da505214b7f18f5f94ffc44d92c54e55681ace276364c47e07bb7896106953f4859c8f3f7a06afc7a77611addd280452aacc8ddb6cae647c8965f017edde527545562f5a2a14a8906960b5123798c466d973dde8e9ccef313ea0e29e1f8d90ed90017b3f41635026664739f7aa111e85f71f57b0893fd5b53ff06e0b02940c2ea24468130ec96536ba00124bcb03f793c81142618d8714a0d45381d48351806eee66fa7f5439906e4a22b4b949342ce33c021ea0bf4f50e24320c0f332665aed552a5d231cf8dfe58d3cd2f0b8bfdcb1d6e68239fc7a38c1e18e69b64942454cee124f79721e0f239dce4b6e974bc0f18f312fb0fe1cb6231b438e93459f5d37afdc63ef7d799283a8a8d0afe090432fc4f56e1ff77e93a483b83bb9908a2a02e800e7ca2ae92bed28fe041987cb5b628ff8016c38321b09559bcd11bcd8c3cfb01c419d8e2a4ff012fed5e3346fec627255737105fcebc31be40cdc0c6d87a5c2e119da95a414d19e55d087c46d38950f1071f91bba5ec2f301ab603f699b3f7e5994a62b55feccf20e47b3de9830dfdc5963a398c835cbdacd29ff9056f1cbea8afd908087dc83861b173e40232ae54f4e91b8e917139ae6f238f869ff47d510cd288879035ad8b6d071f0a0084083ce6c277e61b7ec043a2be4d02c9d64a69af64e362c57e9cf99735fdef82ab13f73993eb51ee274f4aeaa71add604019fa5854086a0b8559b0f7f571c0bd4bfd7d17e83215655f064f992d297ba4f33b5180fdc4ea0a4f3f22f7f889c60acaf5aeb5a59307c4004f9c3b1cc210bf619377b0838aa32aff619dceb16834919c01ed4f12ba10eaa11ef5ccd4c4fcf4dd5b5ed2f14ee4f48821471535bf0b100750aac2f6fa1a923cbb1d2072ba3874c2ca6dc2b9ac13a8d3c8354c41ffdd2eb658584c3bddb39b391aa31d25e2dde24172326d86ab2500fff8701bce0eb719b18f55100eacdde3dabd6be6a29e9e881aa1021c268a7dc7179a8066f84db34d4fb3a68c1f1915871d7cce3fe2be9e5529111fb0bc411c45e309f9c985ad807b4ad9de7bfd86b0660be7561ed8e92fa6f7fa136945430573481f792ec0c497703eae671815ca523a72f0ed6c0202dfbeb8db4b1cb24410389208a879eb0b097404b93938ecd8e2eb045d4963340bdf2192a902f4b77ebad98476b9bf9da5788827b4c2873f9bbcc1e36292fbb2c32ed9ad01f8eef32f1b9ddae623d0ab61e0d296cde5f01261ee7804cac0a35c179d7a0a323e4226834a1b0887376c77f0efd78e32b6a9084f34c7e527df1d62f432158c248ec6e6252005bff2c3b824ae9ec24a78f42eddab878b4651c50ca6eed5ceb9ea3e622e2c224f16c3dac79ec7567a60f63559c4291402f9dd54eaed68cd6d5112a2c0d717d6c11d0f4969d85645a2d2d9b4ee219bec7184124a394ab5d3615a80a278c7d2b84d198e8ec1a991fc9a90a9bf73970f8c9aa4d33a9d7cd04bb1216785a22c75cd592ab6c851ecae743fb24774b73a3b69ab84a3b91e42674171961f7721cc1b7b231259f1ac1664eb45ca1901fac0c505c0e12ce1049c3f0d8a0a8616384366b2ff2aa84432398440fff17a6013379386eb9395c4ecb75cee9b2e27620d1827bf2f573cf46ab98884417e65ad382fb659dc03dfed6f7962c597c8ddadb0e23b246566473b619cf6968b70ee82373cccbd7298333b8e109ddda9457f23ce46d5a6c5a6130d042e13de467326e75b5b81bdd5426c0ba73c43f73ca38a6dd5c3e6b528d5ee01fccda08b0a6e75e86fafc20f20d9c0f01efea767e15af7ba1f38e0eb84f087b1a886d58170d6221927bc00315ef95324ec574e754d626df44674d3304f4411823ba4af9c1bdec24577e6c6a7c603a7ee24e368d7550d26d38bfb90cc5230e056d3a21718aad7283b0015f16733e813526c55370f3603f633664d9254056c199992c83ab1d7f29c620c7a64e632c4ab9846c9b83f990800f66f1ba04e13165b7b944aa8877182f9ecf934536da5ca61bd818a8397cea7758811d6dd4ab274c2c2210a3b08d64a32873c082ecda3ae82c6ba147321a2e8056b21eccfc7511bcca8e4ef1b8603a5b0a1653f4c47c4a0ee999aa537d417195e414e0a4018aca321c141efc7cf4c3f85985338b48de3a759efa52c1a7fe38ed850ca3b791e39a8af32c9ea2e5111f267894186620f2023e63ff08468b46aa17c713fe07f5ef367ad744187f702c01142ff59c3209186807ce3c7e21abc489b0f8f3ed335089f7c2f0f72a9974aa93b591326507aad61c0c61969713cef0a5082200059365dbc9f020cf6327ff0240cb31c14f530dddc82d65abc5df875505f0eef8dd17b024d9297e1ebf3b9310a8b23c86344a11241dfec1da8076372c2442f3714cf198a427eb11352704415cad99f02f352c060a26b08b44b99fffe8def18385f3cedf3299545182f7f42d11e4ade339e76ad8350e61b6874de35ce81f2654f5e671a64561d4a9e7c296ec9d438531b99f4838d4656ec4393d49f0a3b31a24513882c7ad9def71b03dc438b1cb0ad5f9aa3123591212c55500cc85e78a0003c95d573e001c9662b9b8d18681575e3c736405f3eb788938a19c4c6cef569560a26be1fa2e4279011ee25963441a43ae0d0b7b8924ab4457fe45a743c6ae7d49fc06fdf5135ac0a6d9dd5797efc4bcd788c6b3a7d292a55413ce34cad7a990f594370b20591fd10f1e87cbc5d808b1db14c5f4fa5b035f6d5194a94687c3b21d9302b34483020f7453f201404e5135ce28a1fd82cf29d24b40a72a5a782bad814901532def034bacd7f97948a6243bb1727ea829790b8c751da2f7c80db76d105a8fd5f5057467ee33b27179078d6531848a211128eb01f462926668c2463524e6be7c1aec5cd05c5391c68b69de9a36e59f96839b1a3937512d6e16ba0b1d6c73a3050ad99ee8cabacd852aae937970aae624c387d4aa78f781d9b36288015d3bca9c0448f69f9af586fd66de013e3df2bf45253e3ea6fb08d10156e178f12278641c7c285cbb6286d4c10b20705fd6f5a3f11a5b94a3b5b403873aca381add236a16a94b9ff0d8b5b446ad226ff0c540394b6b69e64f9a359b2cff13353c2563b13c252097461e7bacf35be6b26a7c7a454a4b316e04e628d8803f82b21421310339bc8bbbbeafd1d625cee2cbf7b38f88dc6ee1109a197554a05076b173437aa8cf8ee4ab473699996e07236ffd9ca604a490c82a244e751f1010aaeab2a0cb1aa2c7879981147fa846097a7c3d16b9258cc070013912600d58fc8b07882ccd59cf5ed32919bcc240470c34d8b6938452a34dfc193ee81c44fdbb3e7ca1a101c18ebc12852b7b9741ae440d46abe99cea4f1d15f09a0ab19dbe54110fe481ccc00764c0ec40675c0fdfc057dba8c2db509d55ce518e136c42b7fd50f7236ee7be7587827143e084b61c2800bcdf04a08ca86758c0dc5a898f7bc80868e07c405322e77c764094781624a505b90a6aa91e897543c100ced8791b6bb13a4c21c10a0f96be94ebc34d220da14e47237a7f44c02bd69a5b63850fb8bed09589e22d5471594a0b3d7248a5558fb9b3233af52d78ca3d81b61e56e982c1f2f403c76db8d152ecc2e02647bf5a51a7afe25b93a181b94010892a0e2382a9d75e4e2e80383b0047b97677bf5368388b9ac2303a66924d7c1e9bb7b0ccbc819acda77a01e622fdc9cf508e6c098347ca10186d18b8daca162c88d1a63b0be094f54ae4e3d87f7dd90e456a0829e18182c2bc3490c32c5a92f90a1526914541bac6c964db9a90c68024625f4e2fcf086a11457d3e4b49a88dac0545ef1144ab71f512bc232a326f9f2f766856138051c1e87c32078e871dafc7a0f4b65bd1eb204e0aa8b5851a0502ccf30715782f1c734b65ceedf3d46afcc0f73d58263cf85f76a7ee5b083d8325e1aa249f4e8e19f0e2180af9c61dac15ebcd1f5026e47134f93af4d607b83383ffc43ea81051fb8a5f08635e68c438975c625ace557a7c37d3285799990b81615cab4f16be81824594ae3c7ab22e631cf236d6f584182a43e26491e0ffcd96ca75b3924e2c225067d2080d861191bdc7ca901a3992d3d33d74dd2729dcac28c3f4ba5a173878fb484d540b100e0eb1abef0430df852ef68f9b3f5fa86b6eaf4c240ea0cbaf2cfd6a52842a3ae63f36cec1e7bc4306ffe4ea1c5675b19b134678518292bd6a4fb4180cb7cee77e6ce3d3ba55af65c69fc4e9e7904021324296d974a08d7416393139c479b6aa64bc0d012ffed5db95ab3d60327efff65320ddcc7ecad01c81c08db84ecc7244f3f279c1880cfd32813fe2dffce52195cec08774fcfd183b18f6d347c105ff7c80f97350cd894e2766a4af07d0238ea867ae4dc00d7ea9edaaf311a1b4f54bd0d36417e4f9a9cb85f8316eb300283184c1f9d8c7f6a1f2765f612b6f6d5d2d85311819882b289d42b2f513b7501f9b72e96d008019ac12943d71f3b49aac980256c0fa897b5af8e3f3440117024540a36c0b2233a8c7a838645ec62976d88518c15a51e0e54cc38d5a0bc8197ceb72004beb220a83c294372f39948fb0a20e18dd888dd5bf5b84c315a6523ff0c12cab299bb3cef83106dd67332b45c912a6780d0c0ea1351e018098a2a987a744d4c6370d40f42459d86765476c07fc1897d727e1a4d1b986f59584c6c10eea240f5ab763d5b79c4e12301e16c61c76bc9e5a01a177752c2d55dcfdfe5f737858a86b0c75bbe0aa2014d91ee3424201a7db2006116b7ab4cb3f1c0b5e81154864c0222738850a7604794393f69769dc7d30df07c9babc6608faccda4c618c9ac472ad7c00594fbd8e3e72b6685ca234d6c91db6da31cc44a891c089c1506ad8d0a815b488635259859b452f5c70f4789007d9020dcad22f58e3c61e4783f38da1b0bb5bbd52e55e020837c2ac7ed0cfc48e1d6e71ce7b8bc639345da1b166816015cb19a7ac3e76e9bc2902d6085e96295a838418de933590492f636248b4bf1a02386def01a096048b8f19e3ed1485554d2655277e49c5ea061b2eb4051ebd9b6f0670e3309a3a2b9218c5bdaa75befa4ab9bc498beafbf98a41704fe96e62cc7bbd8b0156ce0acc81173e36a3e561ca8e76328592cb81c111ed69dcd57e830462b6de37ddd4c71c7004086f684f448bc8df0feaa0ea83d90fb5dcc3b417c7471c3523aec0de7d654a5c01e8911834a385c53a2c0efe0511afb6e81848003ef9bd78a9bc30223afbbb699fbdc49555ab2902220f51a6e59e65306d2c3b36ff39294e9ff52bde937bbc0c995ee3dd4993fdaaf3ed1f34546a03f370afafc195d57b0660244366563e0820209d5caa5835f417d670a85a17e02d9dbf60c148690a4392291d5145bc2a4b13cf1a7a36581790546a078a534c7b02a0ebf614c2934d66156e05d1144fb46fc5ac35ce57ff1bd021088992e694099ac827b8dc716bdd4c479b06ac088d68f752ca55507e15891a5256b4bea6795c423582983cfebc51449401e0c8512cf9f380c1572f672249cf828ccd5b4a7248011d46ab568d2027a0383fbc9b702b6bb87065bc817f04234d712f024ece91630619dfe54c42bb8a3f7c5cefdc21932bf5f98d18e90f7befab52dead4429313a00b81132af7003aa9d17ce61706e05ee69ad46be6982ff8a35f182089b735e5fd8180d5086b267840c7139a046ee0f497bf83bdc858046a0c1df8c3bcf41144d7a7431006b8a2dd7c36d8d173b51f0906284d42192a7d37f7d22ff8c51dce68af591929c3fbc30645da3838e90df05117c8aa75183ee1c3a52df42327515a78c8ea02055f85b7ebeed907dce57cd6611012d7bcbfe0e37e3bf925ddc4e53d0b8464e7117ae9492b8ba298ce0b1868003c6e1fa277358f7f9822c2c8cb60393ec12911f12fe68e8f530b6fda679c4247e8f189993c6d792441b0133e487e0790f7ee841ecb1bda59b228051d25213881b12bd1438c8e5a76811f18a18d03f09aa728141381ca74bd495e57ba934ea193b200c4bb5d7c775de9cba52acb773b0a2607688d76a09e75d9f1e476584ee95e55a8e3c4eef6eb6861cc6213427eed06c6c20d3e263f58097340b3f8ccb4b57b788e9bd127b2f96aeda1af8c6c67c19c717647ffe22ac56877fc5d7f20ea7cc7f9f5eb143905eb43914f2661331041bdc23b13b81daed78b574f9d6f4fd0bf927c26c01ae0f6ed0baf7e34936b0df09d3c8f4056a2f2ab86ae4ce52afe1b5ef80e09ad3572d3cb10f52cfd49aebe90bae3c2113deba714140a785d65be9d1be7c1989003e59e5cefbd026920788cb580109ea3a2ea7116936c51a117d83fdc0e702dcdd5df0ecdb586dcb7ef70c3a4520a394e2d6713f7837cd891aece79ea97a710ac58097a96484f09bebf63540cf6f6a7cd437422cc8b4e4b5beeed0aad26121395760119378c3148f14946ca35d34ba0aa6fe0363e9f27cec582782a3414ada834e32265e9aa77ae8ffa02f09c2af31ac62f0243f882227f490e70257e97ff8f118e31a87785f81c5b8bae4efaa9dbe1c8bf29f9e6975aeaa35089b1d94571536aa1cec3d7969aa13093e7a322586ee8b8265757923b74eac9cec780c1c55044275d39e3e87eede93f490af88d2b179496dc48bf012eb2e0c991b9db81c1d7d20a06385aee70c698d36f3dafe2ef556284200aca68aa0aaa005b9e27687279c8738d16aa19ad3d392624b2011de059eb71b7d013d259f118bdb15de610041f04a68db9fbd76fc5671341ded16a618915dc871fc07af1000d3039fecd4cc048a4aa41268a60887e9d184b708481bf646ba9751429429246e408039092a1496c45b09c91a52268d15cace08143041f61f0331dd363d04368255bc2b8ee635a95ac98db9896566c8564ccb712275dcc92b1468158e79f06dcb08a471f903528a3ecf5240873f68671b2f3985e3043762cd36e88bb02b031c611f679aa85ad05aa12624737035b25ff15912dff998a07c48d8687def1af0c11f00c98601ec60c4009e448d187a71eb62f5588dbf09f2c4ecbd92da0a959d7ed515ef3124f8f7e790e4e1bba592d90cbb6fd5e08759f05fa9aa503df1cb9b67e469b57f65f4c18432ec1e55981cb3583dec10072313d070256ef24b361ee0bb5b8be5c48e20050cb3d34a23314c87fa81885887a18e059ea920efd7abc836232436f9ad5088cb5b5c28d681a1679a6758613f260ad62ebfb008b61b9dc032d270ef47dad26dea8b2bcab0edbbcfa3e03539c55ee20bfffb3621ec1eb3099158199466c50b438187a693051365ee296e3077bdc231e52c08683bb57830b0142b72d9dc6a3a45a5af434f54a1a21f69160a7a9829320eceb1bb285d8a7e922ae08e60b6873a710ffc0e23b4a94ced12f71914cec21812981c6b80959cc60ed404644de18e2e0644fe8f573fbacf219efb09c368d691d68f9178933b009e30906ab7bc1da163c2911c1466e0417bf3fde4c4f76acb8822c4bf8d33ecf4fa6afcd16aa97996f641a15b926ada03bb56984a23083be752ea9f419f1affbd7060e3c24ce9e1c62349294444be1aff9039ec62088b29922eb73c45002bc6a1c752d2ba79040e002445236b9d84fc76eb6520599485f2e2ffc8f3f5deb28a47740ec1c207295ad800f476a5245037909b4c840c9782e526738971f7b5c1b0bda442d020ff12be86f537680f404e1bf505efcf8bbd13bad0b621b15069dfe7dfcf03fed1783bf40a1004672b1d1f65c6c4a8da698ac3baeca7840290f0350b426a3353ee232a36e3f6e46dd0adf8d1c22afec8a7254b3655ce1df1d95cd06a6ba34ea11a756913c2cf34be4a8fd8b4e5fd6722ec21a72bb0f98f16c181780d0b0d243e274254e47ea413872d23f57e1a3207a8750c6e36949439e2b8b614ae9ef9190d4be0c78968bd1b0ed1a9dc3e849ef1b208474b558468deaa3d2a24369d0c508bcb56286767d0809b7aee6dce61ea1941ee2c41840c1b5f1101198a06379a2cf4645fdd984f476519b3c4c28ce132bedf5c9155511aa324d436ef96d50a1d8094bec50bca2f39afaa722d21f89e1f473f52606e80fa8acd06140a2e88d125234013a73d83c4535919c62f561f803e7a0a560c36d0c7fc4a884ac0cea5c764c7764787615c7af15a55e461fa3a8a2e8c1a2ddb5004ff64ff18736f22eaba8015d484257927096df8d3fb0bd70b4d818f30fc92fc422bcf3dfff24668d2a089a64d054a29c5dcc795e9025688430912e79be78aae18839d70c1f3983dea6285140fa25f06b4cd6282e6bcdf1194c38ea78ecf5732ee61765cc43da3b04d08fe38c0c931e14c13fa741f9d758969c4996761c656422b4c54f108b634552bae8810f029d1ea841ea98ab268dde1a94ede48eab9a5a45b213b5c8c920060dee675361262ea1d742728d83dfba6728344f0e785adaab31c14c6b68ea55319c539cae0e5d007c4909d2c87d406481a4dfed2b103d576783ef782b0f24121792cc107654e42bed61252f13541b4ea4ef44c406911ae718eb537ce75c8fd8eeb54ec51a34e04feb1299dd12f47042d53dab55b4fae89f974ebd903baacd78d1b3918f7f6fde934d8c64fd9f418f378d11d63a6054a0d725eba7ce86ee6b01c82ab63eb9743a4e1de2d25fe035741f4e124849f13851c069b2aa3309006f85bf751065d43fe8a41a20bfda2fa4d55f4885d6913a7217fe84c310563150092b09b59c06153d7987fa404e89de7c043c062670eb0539c600a2d4a186e76d16a1639945c49620c4c4215b1f0984ab9afbc9b0b49ad25b80dc4f1d036106022e823e023db7724455791c6ecb89dc8c823e70dd36ba2d3f58125e346cab54b414c1dfa4e06f0e24dc3e5b406ced312619ff341547e820d4c4044c0d223e0c22b43189f7c72e8a9be93cb80c2e42b9c5fac33504d68e8fd100dc59968302512ea2535a1363c1e65df9c36bda2895afbd97f372bae26776942976312f14183c1a6747704fcbc800a93e282a311c9a7452f8db3d9e0ce88c046b8ad50553d29e4ef8633b711ac4f69f24fb7b98ee37e822a9d1119c33914b61f1fb1842b755b77d31b3465051e803976cbdd2e5435cd51822fb53f89e8b6d7289fcf9f721a663d4b78146b0557039238f0013057aac82d20902c9f1b27508853754074cd2e0b0a63528bdddb9012d29aabf1c9f68e32abe2ee5c7a89163ab5eb7050a23824739d0de2472f8538bab89c0c68502fdae470e629342393569383afb864f3efa8ca22a821dcb487d146936e61e80b7fb2e1869b9cd54ab7add25e12d64e5220f65cfef49d27e2e44f8910593f32fe943afeca48854e57afc6f4b6f37da8c5d984b099eed4cf2d2bbb0728b739c60c7c00bccdd240f3265cfb8485fb9615ed611fab2ba074833e2ed5b5c1784b5037cf061326deb9f2b4ed444597142f5d645de5819c15a8c9dcc9e6d6ab0290c115191c7e39bef68287aaaf127712b1b8273171c6353a1bb700a0e219810fd6941b72384a73ef71fe7ff1f1e914efb032e2fdb395a45115cc6f170227d5782557e09eaeee88b1b8d6b07a5b1f97ab6d630fafe4adcd3fb5c7630b93a48efb099396d818574ffbdd01a6866bc92778af5fca4a1fd2950ef14a2878efea245be10881fb2a0cb516ce438e1f011adc4e97476791d8f0617ed9c6d68f669fec1a842d67ac0fed0ed21c1ac305350f175b12711ce0b7c402c74e00c721a8b5f71271a468d0169132c92542952209977169d12ac7ffa7a56e1d1558ca4268ac9b4d5e5d7d893496ef6a750106a1c67576660b739f06d127833013e6660e739d0e908e0180740fcca8aeb192eb4ecf4a806bd60b25a2c96cd29d7ecc5830613231f79330f2f1f7dae456ca4140d1ae9c9367782df88fdb91b1cb6488bb49493e80b11b6a463d2788d0bd480bdbdbca3681623b094039f0f35bfdb581c3a07e4642d5aec683f5393f6ed35b57eece2d61506cb668c6664ac3b8bae08d14b1d8be2eea82f75d81845394820bbc4d8bf9453ee29184496711c5521048d5f6cfe462bc280debdcc5174c5115cca0e0cbb79b0bc81c984fcc7e24e2b953b90abf089a40d235e3362c66054ba9c054f192f0de1f8584d9a2fabb1f9e4729329be341d9b7925c3296e82ca6b487f2ced3f9de336f99ddd6ab3bc29dfe6ea7d7e4493d955b1b19bf2a8e423f56cb98d2bf297fdcbf391c32acc1cac3c6c9625e12336d9e143d74b111ac6f204ae6ac42c83f0a2688536bb13a996956262d4185aceb833593b97bec41578542895ec61e6e0c8968239c303bfcfba73e960ebf72313dbbd914c50f8672faf85454e632e31d1cc574ed0b8d90b0bc0f78b1d2730b768a5e067783914815755398a9cdcaa13ebc3117aba8cbbb0d8783f8e28845d23cfb172c9c7be6348823083e75c542c0208cc5e636015b622f063de724b9d8373bc1604382c9406c4148e60790e512f1c62800e20a34a28970ad1beb11b03bfda1f9363c0fdc18ba78852d5daa0170ae06a15d8edb3335779a980e5e21960e4d0b4edac9e4f1d33dbde03cf501a6c40959eaa2c7bcec186c5ed5cd2db384acbe618bcb03613995faeb72f865d909409efc4c0d1b73505a1e53555e9ef47794c36c559a61406a81ef99af83a9c66bd8a6057a5c8509a240a13d0a2a7ce50732cc9d1920a832b8d3bd5cf18714fbce9788f8d6e00ad181a6689b2d6868693a754fd0faf1a3b1f111cd0703cd33521b79024dad0088d192cdf5bb2190f9776f5e9d94f7f1ada478d7c23227e68e81f5a90cdf7251a05ea249e78e920fbe6cf008fedcdaba3fd375146891c67b9cb64643700570c0a2e539f7a16bcb8800c00ca5422ef96842961af18bc11f0b0c5659c862e72a9ae7af42306ac3a90d5a2a342f9ccec97d591c6fe1546c841f783bc2fba9b91655db93228a968677231a29774105d112226b02c325a33a593913424e8858880f3eae8bb135ae41d192dfa374a57133d98f7d147d2def30ec75b6e461e1c2b186eb824c11e7677cd49059b910c9cd855ec4656d7253b8bba10424b387e540796534223a2d10bacb72f0dea9d40f450a35fb4e80702f14786fa26077dc47b6ef0bae293f167b45a7387b96511ca25319a4542c93323bbcc958fa7d2887071df3b33cccddb79b2bb7ada50bd0e2e5ee0451bcd353c6e2b9b7358808b5e593f091519427a4b7c3979ca507e73ed7159482fb383b4da3de3e1ac45cfe4c1b31a23d2255731d9045ff23ae24a3150c9a939c60172fd0ccabd5acadfa9e6de53cee2159bef9dbe8b41d4aef1464e6ebd52348963578b13ccddac1a1ae48c6612a525756fb026edb89837ee70f4906179779507aee5e7d518c44e7cfb80c8f8e23047217efc146791244eb1836c2b0f51f4720c0e795d20f09c26906792f1af1a286581aba0fddcbadd4121a43e3d0e7e58890dd07f22faeca7775d444cef39de006781b9e350a1934a50c4f5b8ce30983136a725cc7dfe29cdc5fa8e12ec339aa538c0bcd4793a17b4d41cc0e96f437eee91c16700af1de0070fcfe680a470183e10db2d93dfb884e99694bd755cd9d67c750dd7e8ee7a6c1116afaa7d16f23627508f7db7d6212817af8874585c288ebec51bdd2cbad48280892ba1b29777516a6e0151986807a4ea2a2102e1f2062a1ae274d662408cf05290bd6942401111fba0e33ba507d0e8ed5bfff625ca8d87cefc0e6f94f9a9335cf7b0d364924479d22b27629d62cb0f2c1ad06c2f0e3a711d3b0d483fcbafc1514bc550847f721a3298ff5e777673587a9e392a024e5e2220ecf450a0084e98f9149a5803a880ea0484c290de5104a9e622246d9e7b40501f6d566cf627b64d11f6ebd938656a2dc49827af4caec7e7bab76d76c6f847f0d9807b44cd3147de0c50afa7ac7e433ac502318082ceea4f3e6530688ae4b4013d6687ecd5be93db137236c01e38ad3610cd35b401f38a9b2cb340bfeb33f41fb0886b0371dc0d84166d200e767189eac4c418daee11a40fae4807b86d20ffc30c8f23d3adad181eab4f6970cbfe6d807f7f5f447f20297f111712128870030941c4ff9c36ab411754a0cc775726d96635426a27633542ca955531ab81f484e884ffffcd5cb31ad1a5f3a0addeb6398756a35864e911768074adb11ab72c720f19a4921fa55a8dfc11790bb193d8f14fbd20f538abc1b7421ad30d263301a0573caf17029ae7f1cf9b154d207b070319ae06555a70bbd4851b97b48f8e5cf7ce24c488ba212e58a8afc036370d4d28e715a8b4eb2bc87907757cd30ec073f81550804796537440700dae8d1b131958a12b30a231cd1463be02fe93d1450016f0e75d84fe774feef608163cf7e0b0a04f2413f285ccd96dd719e461b3acda6e3c0a61c1e4e6bac9ffb460415c6a67d6ce8561013fafac0e906441029ad7111044e34ebb650584e3c502fae615105d0ec30274a6cd5178484046c202a27b801f493a0bb624823779432c884f7016e038a35830c7e66620130b8c1cdd851967b44d9a04ffe55f065fb82c7c697e1cf02fb10d945802e0fc68229e6e3982864622e8ed165b6dd10be6da7fe2d0d03619c158c2cb89689d2c032781abc9bda42db13fda0896cc52411145801ec7df8bcf3c6c6092b0a4882bb48b18b1bd17111d6ed4c750fa7101b4e224bca0c4a94f868a38a34ebe5ce3f892669650e1e861c07de4ecb0d53d21581185c54e30e29e8f1d3e8085254ef1ff0b1977a948e07c8515712af92d18770d7e1ddeb4917fdb1eda6d2759e2362c6264cd293928aa7b9bae468b98d7a63bef08211608b32cd7c2e947b32e600b02b898f3a2ed6031af7d9610e487f01642ca4c4a1ceb63b4a402fd372a90774851a8e211824348300111ffc321507df42ef144a91f3a049e104c889030c122fc142250953ee1c1add95894421111c10454321160cb3ff927675233545b84a8f04e0ab7224096175c1120445fdff6d7db75aebd189930423869bf242b8c6045e2e472d6c85c54095ac608da3b2c3227b546f8c335429b05ae0b68539231e11a61e86aa07c009b60014a0bb7e8a3f225cdd5460825976d2b2cb060e5b427687d9c6396ac7f813dd8fea02702162b3b1b9ebffa9e90d7c15e796e88f047c9e06021c02dcf3a024a77fcd74bef13fd97bdcb96779fbd32748b97df7bb04c08faed5fbdf6866bebda33dcd5977ddbbee1aeeef7c765751f6fdf1f5cd5ff5e3819e9a9cef7c6757559f7ef05b7eacd6eed1d6ea1f471e52ceba421e2e6091d823c69b3d2b0054512e777a97fc726a5778d4daa7f8949a257dcf55c68a4f45db1a4f5eed8a4f4b6a51e57bd4b0c12fd7b4c12bd0fa5ef4223adff8e25a51766f75cf52fb149f4ea7cff85464aef189deb1e8feb35cd1dd24d7db2d130e33a91bb4379a398d632e7a740541123d794361d7e86ae0868e4f07482d487760cac1af27623b842d2895a5f981837c9ab8d8f27693a514249e5640dd383be25bb708baa54c948cd0267ef1e2dca71aa3d21cfbfc6da8ef421b3a086274bc28c507b73f2e3365e9654804b6a734f062e69edb435ad9f7347f53ca5044f869cc3f8510504e22c0174c50e0ecd726d35700f194b53ee8bba8cd2332a0f23c8d49e11972243efd8f2814e94932b838fa28b7c7c81829157b21f610c1dda97fe42eac37c4a2c788fa9e05a194af7a82e30c56cdc0392aecfbbea8f80171921bbdf310edc72f04fadbc13ab604ee1cbefa74916453404894e91284f771a5e66b23d3e11ba4ddd81034d84babd0377d80c113c52aeb8b47e070e36c420383d47bbb9e1d924055ed982cb421c16f0cd552fbac69a2583c61e2f26eb7c2b40d6591d2c10bb18e028b4504d63594434c61bce1b5c7974b1ada6e92d81508b36590d3b54c194546c7068930d0e4168d8af74014a19c4b096371110cef604c295435d46d90a0b73f61cfc913c611c4342f31c4f559dfbab9e542f247565b744ba86d4215e6ce7c73b1f038b4ac3b2950ef59516c926a7ce2ff2fedf48b5df674f59cddbd1c4b1a1c301b24086a256b1f62d28cc2701ba33eb3c434f57a338d2bcb38aa15a160ef0ce337136beaeae8af0a5de3bc7ce8b7a3a62f5f1fdc517a6ff8df07522acd8d98261ee341e291691f009905dec6c65d4cb1586962197062ba6f2c368ecee2e948bd50f2e0dec5a4b7a7c755e5c081f17f2953e7c648d689f5b23a5a31e9da4c6f56091252b037917606567bfda98734b920a31bdf23003d1bdc101221ad2426437533efbf81ac9ff23250ab4c93672566ed0f45e8da7ebc054a8e5e75cc981a85020d3363a7cf41b7b19167551ddb2608730c7333e36706460ba20c08e12d204c9870e35ed25fbdc1ac995f49204602b6e0d94190e208c842c9fe0ea7695ba234213c834b36af9352a263de5d1da50b4f70542fa52c133c052038732df3a0a3ab3f0195ea866ecbf98de028103a1e4bb4b998c0c7cf635d158d897ef49446179f1274281d7d7e0919ec156d78b4c5400d14ba2707ac4be14cd2210ff93a85132bc44de20c51b037303012c3ab72d0bf31a9b9174351fa76ad4fa9946be06590ace9ec99ec894ca9ecf96cd9a900ede908d6fe2972cf513137a97e350d17aad149eb0d80cc74c5b458a4ced47147d3a58f822e27e61c92c01a009d4ade87df27d7f857840f9cd7be5f5ae5908b222832509b7bd274c3027a9d432caf37f9bfcdbb3dc796769788df5a2af8c024125e9a4295645a582a4cf8adfec5fb6d184506fe5f6488607a340cc1fa27e8e1e967780bc51086722b96744d0adefda67229683f66857351183102cd64ade5a00d2519e633472893704cf62e2aa7929e67e89104861535b955df7bd02ab36bf2dff82746698b8f9e544d26880ff977b40d8fe13805dfd14b8a5efc1e6cd6ec3cb81640707dd875b3b4677d0511009b9e6180e4f0be06f9d17c145de575c208659e2d4a7e209102ad0a1e310166183d359849e6d31a4ab0e941de234535d0a6b6af4538c27a65e67d331afe47f51a2f9b670b833cd44edc28998cd83816a2cd55da17f7727bd3d94aef4ea7ce47efb218af6b6a30173f6f8e510436bf233f4c2c0c31b82a8b9e14b5792f34a0d1d62500de3b83cc98b5ae5f176ef682728c9de86e04ee0d19d94564fbdefa0e882b89feb8319e81ba5a06685fe98ad62e02cc54410276ec54fc1216aeef673f3c9ef4735147865b1a83d1a437d2e230f0fb5c535e08ea63a68580f14a0b735a6c0c3af0f806488181d681fde70eb1dc1c539f960eadea185a4a2818e54183e8eb5ba2463a9b98a97c843d4123a3eeb84506fbcbba9c1ed2f799eb7853206e1dd20e4f0a3a227f335e586ad041f36833cc69927a48b5f5638b1969ce526619a7d08a65537895e4e4771a68085e5d93a1d0cdb8daba195de774ddce051e1ce080c08066a2f52424f5308e2f217b7976a736ed88312d35431310c48c8245118c16ef3a818967dcfd3e377be15ad8aee6d0ad274c53a4ad832a380b0458e1eed3f779cf048c8a770a581b6b8148a6b7d6139556cbf286b03d2038f2988b3bb7772abd3a9779dc7ee11906db206f07815931db144ca54b23328764a97a17c4293cc2fc5a5d3a2b47b112a51827440d211c1410977e700ace3eb05342e295bf3fc4bc4770722d9df7a58fffc72fa4326a03291ea9d7da193b2da904feb3eaf81eb1d7fd979476f5fb67a27fd90b0a8b0c4c9342d4f78636d65fc126e2899052d2fce18836fa916ba1063aa0d9c9846683550531df246ee770a130639815eb00ded61c56660a47699095bea142ed02ef05c0148e8c3b0b1a5298c1547c73496d4e9da9a2fd9cdc5775c7dd9b340251902167ae130e3f9b85e90de87f1948e46e58791b06a9dcd77f90c75d643c50be9d4229bc75dd7d7677a3af49d1e6771a5efbd827f178b498c8c8a4ebd2fc2c97972d39529b33c5e5b656ac22e945a820c5041908b8b7e5310cf07b7a8e53fe9f7e77f9e4c0e56b5a663bb96756c4d21e04026c9797faec926b9e2134709f21f0bcf0e049ab42c04253fb7a0242960a83059607c1902e96bf1461a626197e24154e872c7caef3d9065f36934b56c37d0a67ad5e188226116d1a6082d4daab8175648e48d9ba2f19a422dc92e071adaf484a6836bb959756a0600ad338d8ddd9a8b4646836eebe2d54228a7cb0b1357c02bd26992a8049b5b2e6909daf1c024fb1d949e7478b09cc351711f5bf15379dad5553e8ea5a3d6d6fa0a40c7eb94e8500c7db8acedb49223991cc2d85489ef525e5f5ad9f6997b807a8bac4cbe7c42ce309455ee4ec72089c54529250dd51daa289d064b0249901e995029cda1d55735e90d2062a486f3a132ec8eb2e3c1c33edae1eec10e7fbc5db002f436c0c86860397b3a8025af6cb80f68d87d9ca9e32103cd27c6256d10de47021511a89912e063ec94a07c85913dc5cb2d3325cd3fb03a441f4f396b3f001968014c6304ba7bbbb15c30523a4e07971f6bcf7449858f4f9fd945a7649ab6a8e28996548b34adc20dfb7d99b665daf52bce2a9e3e82e75ecbaafe8608848160188639624e58baf007ec0dc35abef443ca39c4ca5ef8879db5c13ea271d326bbdb9632a52403120b120bfa0a3a7061441535891a0e8ab26869020534e03603beaebbdce54fb6d77d792ae6fb3798a764646278dcdda3b0560b70a246f1cf2e00042d5a10e9e0832c9880e107320882145a868e68e2384a3d25b6a4ae237928943a1a85b59a390aa838be149ed5b67a0e6eac8dc90facafb6f249e177e1567e291c4d97065b4e9e6ef22ce0b647ea463f3f5a6a8132bf11c984ee72b95a748515a8b7d8b167cecde6ac0b47d3ccb772db72fb3daa830e093968d51bcb07cdf5f71650f22e6cdfb1575702f48e3db3e9512f7fbd1ca250efe8aead747f72fbb7224fcd305200054c064a860002cae4c10db0c0d2c5115818e1846a02810b172421420b2094a840d5bfd13c75c4c78621b83c61c4506d89aa59a867b767a98d7439dc4a2eec4857e5ddcaaddcca6d4bcc46d462cf62663deb192cb65aad562baf4846330d38de46a54fc69bd9b6ca26bfc2da38ccea08e9813cc9fb2f2c62e42b62044af747acc90aa9cefcfd54b50d7bff794f0a8dc0fcf7de17296284f4a5870204e63ff079073bfeb5e10742fad27f361cc2bc375bc627167d9f245655e3672f5812abba99f139daa7c50d841768b159896555f70df319cd8f2e24e34797ddfe1f1d76fb478f75323898a766b7bf61b12d74253f3eec2c67d222d1cfef87693381beda48df81df7320dfa99adfc8bb01a76aaa3cf72781cda54fece5f63be8a4f381b27d17fa24b150dce76747d57d12ab22a5b45013c1a7fea6d249dc7e8fcad70d138a016dde8224fc0c38c2cb80a69701d2781ab0e6e9c74f412730ef307f03fe5855e9678033b06ac6d7007d92eca84aff6355303f3f18ea7398a36db61268b385be64e6b440bf814ffddec66d603fc78425b90e770b7d45c58e2dfb74d0f979374a93df845b33c0a77e1feea9e110d6e9ffe119e0140b21171fa7c17d66161762260f3f0b3c847bb8cbd4e9672ef8d45f63daccd1b284a5b56a063bb64c8b85c19c6ece34d5374a8890ed2b03a60977be891e60f6f06f3077e3de1814027387703b6a455aacfc11416287f0ed533f09e6a91f0957c312ea531ad5b0c9e3df9168b0be1a0939cb5d2df314ccf7f7cc53df8f3e9b15be2f3d0b30ef3d83de957925b6afe29679aa6116e6ab96a78ee0e0a505839f2376d0a189aadf5d9ea24012188c8105084045f4a0eaf755bfb34a3f619e85ee614223df931e26340205c8f7a43702f35d0906e68fa83e20dd7fcfff911e262cf23de98bc0cc6f0433a77fa78b78073beedc064b3e397a74c73984db4ffafe114c1eb79604763b75f33cefd351fffb6cf8e705d2bd67034ed5680253e533c29b9e0470aa3c07b982363f48a46faa46d0f1c3678623a48ca7f919e05479b4c63c09a0fd11c0993781324f0394f13520cdcf0059e5316f493f3c528ccc0c8d0c1949067884e4b1653ffc44bd3616fa559fb529abe974dd0372b8bb77a00f8782431463dceb84f4f3ab8025a1705f0228ece0f8361c64fdf96db63f72b707b4c8f1a81d972a91592342c4a7488b341ad398066b94afba9a9a4f07fd9aa75162902f4fe61cc6df3d0683c56c7018885b4d5a2489d8d8d8e0d0ef9ef4be83e330512c98a8ca4f7c601e7c75539d04f3ddb3f03d293442faefbbd0081420a4d0e647d78536f4bd9faaef8d74a10d0d6d56207de959f0befb5268040a10efbb37527ad24f15cc4f55c35afc4067b9aebf3c3655a5ce577eea8ef5941659093beff4259e030ffe83afb8c540ecf295af7ce54d9c89af7cc5414c2b3af58a694ca375ab5dfd0a031086610068df389540545a398dfef4e9a87ffa76f4cd71af231ad33c1c1d8b1246d5afd8bebfa798554c143773c7966d2d638e5a61475f51cecacc299d8892456c6d50a76677111371c9435c65a77befc22926946a77e96cd1a7dc865177eaee519ce5ab9d92adbff3e9f0dff976f4e537a1c4e0fc1a46b3f3169d6460308d0261a2ea7f340f12e3b6140f01a50d64fe1c2df3146b99a7fe2f6e7f974622a36561475fd11c3673a678ac45a6d1b8745b689e6a5927a3b5ccad34aaca1d67adc8531d11af79cd6bb5135c28bc6e50c7b5ee044009661c19b506a070b57ef24eab724cb1616438c5f1a9dfddbd04cbf916762281858603092ce5546d5fbd3a1ad2a26a8401222e76c0418b2d3248c2012982c4c8c10f5f96d0b285134f4ed0c317308aaa50f50d48908011430b7a50c31692138b52c55f1f48110314a9b6afe1f453d0171e8d9a415f68b7ff01a193211ff0940b6567996581848bc9112f3852c41716b47270032690f099dffff0d408828801104d5a3ef85084aabf054f4d1fbc584535c0e20822d0a0faa28b1548f1b2030b682852f5f3f0d4036a90d5a01586161f74a1ca6247ae7ea07ea0c5feb9d3a8c9b56d537f8b4c7a346fde189d7bc475ef26ecdcb80ec8e1240778ab3d4a3d4a49f3bab7664efba9590b3b768c04837f21a993d2c884286e7b69b16b958e400b802df0007fb8121e53a2512be4e89a8333169bb198cc7946bb56a93b75178acd1c39e60326d31a3576ec6e69d4e82d6f4d1eff99c3759ef83f6e86bd898d1e906316cda10854b9b1183373d76a1ceb5aaaf4a658c762a590963dd46da59ba8b7fc7c90773c89c8a9fed25dcf2bd51f0580e2c219a73c76a20af0853b5e58a7bf72d556faf5e90997861c7acb0776ec580eef58a3b804f97a152b8d82dd0e126ad4d0aca3301777eb443628e6eede40f7879c395d0a3bd657baec59cd53ad051760ec9815536c21ef224fd25cd41c8b1d672c168bc5c6aec24e833ae6296f79ab63ec2d6fcd580ea122d2532df489bf36ce58118bb69a2547e95404fa87a080422c16f398fb0bd4e2b1fd61fe392965d041a6cc51f875997e3a789b7374a457dbf6397ab4f58ae9db6095b0cee4b68175f869f7aa45eefb15e4a7608fb89a808118f16a543f69d4fc799b691a05c48817d37e9fef6362e1cd1b63c76def943aa54c67f59a4e15e3d0b04eff28803b5f44d91679b689bc53024771f09603059257e2bf214e9e0ee6e79b502f4cb1ff345137a69862ff081345bf8711525f888c4e3151ec03c7791ce7711c7b3d41983b5a2d2c5f8eb356668655d37e208d5db123a5345ae4291297220ca10aad798a8b582d4fcd180c15299c58c93cd531929c959ef22886a408a2796aca6a51ad0979aa270741c464354f7511a6acc86de62919190b884855ff565617b9b13c355553557f3c3555ddba2395d162b0976b6bd5d56dbfc2561086222409837f21bd824e4a4f0269232d6f29a4b30db8facaed3a07bb0da45b5aa4cd405afa30513d8329b6abc51950227a658adf0aadfa422a035be5a51db9a8d6ed69a51bad5cc49140b0bb5dabd56af11f4001260bb4d0627ff419ecba4abfda35739a69b02395753f934701b7df7b605d68b19bbf98a7feae8b89196184a22c2c95b56cd62e57bbdad5420927ccc02f4da055a508f0d828ddba12e98bc0914f0757e728068811b5d6a5e18fca3c9f7a6954e5d13131313225885fa314f3f5f0cbfde8fdfa28440106d7e1610e191c3870d06c5b48f3d5efee8c5238ee5ce69bc32b380631ddfaa3f79c866bd16ff5b0041898e6d351c39d16878cf0724b60abe6d0112b4a65ed02678b9ea2e1b147033ba0d69399d36c99cba7838b2a48a398798a4a41b5b83d521edcfe52702cec3869320edd8ae59a0eb7c28eece57691a736e0a7fea2a2a2a2a2222aa332aeb45a6b32fd8bb421cfdd256ca084cb8cf3a069f2cc2becaac569b956ae957269b1df9b51d9cc01afb0b3c60031c20b0f6cbbee44a208b33b3f1dfcb915b6c6003962d2182047b0dae583bf5c6e578b738ab5b988632ebab4d6a8d2f75319957d09e45beaeecda7027fb9f5a9cc8ab8854e4a43324f753795dd9eb9d38924e41ddb75fbab3873f8bb92d42ae4c0633ba78dc79a14cb81b717abce60e7e770e62d4dac8d3b47e0b087c7d6320d1eb72c8739bc8d654f7027b2e38c19693af31c02d4c9b1befc290a4f83ad652522b126cfa472fb69cc569697fc693369f18aebb3d949d80b7fe15a8b3fa4d7fcd45f4362e674122d851394ecba2a3119e18e1de3f7b0b2eaeac2805a744742ce1c865d58a39c2449925c808ccae814b40b59a3bc466934897b19f22f5eb8d6ae56d23e5ef394cfdc67359fd5ea129f79aade507bb85507067c29ac664ead250f794a8b3277ac0bf852f01850e2d87cae74f9794a8b1cab3f320c566968280d4777907b1a9f8e2e9c96740a83d05cee4f9f0f1372a892a5e33876841d19e63f3ed65d89a3e0f6bb9299d32479e3a493a49335e75aad56ab592b1d3b8101b456256aac2a51a7b835d6a82a9ba20b2a13d689523dc85b2b4fddfc4d68476b6134b762e6380fd8b910d2e2768e59125be38e7e4b6cfdefa7837fd6e21095d5c08e5446678d9a43237068e335d6e9bfb936649e9a4147447142288a214c49a2cae992e4862b548a98c10f2a1a5160c1850a9c5062053950f5b790a742209bc10f0f52ccb00a43d5eff4c753b5a84f3e8daaa5d7ea964a69dd809a3f1d140576dcc82b04c9eddfc899536bade596e9e486a8bca925d758b057d7bc56abd1308d40824d8d1b1c1b0f92ba9a229b6e58179389ee05260f077901136511183880c14501aa9032e50b952e5852bcde41f1abc60eaac68b85e0176aa7c424ffd4e09e5963fab4a802bf874cf20f9317984417f0aa023fa51ba0ec1ccc343d0ca704b04d8e7fd8875f7664a012ac0841040e53f4e005142a1e53bc64e1e14a14162851f5f3ab9f61409dba409f7a0b3f31718f0bacf30101704a043c4b531317810b538b0bc3776425fc1b68d1c32017a031d89177fab6d0a8b1c6ed4af2e79e2d649275fe8ae003ce24f770acd3cf2d1701a738061cd95d068263879bc480c9e51e6ec1d4611b5ea00405486c939695b0125e758aa3f06b0c3beef08b5ffce2174f61252356c22f56a2e436cbc4209cbd6e269c211ff22a7c08c7d46257983b5575eb1811eed9421ad6692a9cc2a9348dc3298f017b6bd6440c64564c1d76e28a564d318834958e390b79b1f48e2ce495a845a11833d172017db16393edd3a871d62e3f91c943bf9f0433d5a4f70f92dbbdaaa159b01a6ad239ced2b834eed875b63cc5f3c47d03debc1c955e866e65568a43b75f3ccd940321fa4bf497c8852498b5578c3b361247622c269b9534dce34d5827069cf22c7c6a6f4273db6eb56e5bad11d811b564f12662b8e0f653a605f5a97ba8ab3095db4874ac0896e35e2303bdbcd86ac3efafd58f8e8d7d02eb308fd2220b9113050ec94c71132c44b29090a7b8b09198a7fe2ec60aa18e89e288c8a84664fe68d4c8ddfe12fac45f65c670702788b3a8b669076fde18bd970d2ac93da49096ac63034e552a6825ab0c95e497773d631d2a38b565e95907733b0a762b0bf7340f53a77de041f7aa97cc9c55a9a76f5b1ae5dff173efe57012e8a57048bdee7917ebcfbd8b6119ac77d463c6f56dd5321ff34564626442283426b481e2fd54ad7ae5a99e4d18181af372feee4777eda83270f41e69f361f7361f06838b28f52aab64a575fba9a44702c245597eea27a56a8b7fa4312a4506841457ecd61a3889797f4aa9bbcbf5631ba7acf64ac64ba7399117d576bc66ceeb02b1f1ac511ecc9db457947a4f7fdcb9b43f1f130be97665d72befd5fc76f84bc1bd90672e4c30ec8abeb84c9e5ecd1c2b66aa97c4c4f4aa67254a7bd533bcc452f2f6f276818ca85750e394ddd9bb8e597e721eac3f03b55afef57ac84f5caea25a497b8d95dc984c611849fa94349e6509a424fd6954291cf996be396e2bdd7b81bf94b2a5e7e72f05f6c143eae5f7de2382a5e46dd229cbb8b658e629af34f00a2462b95dae605bd528aeccdeb4cced8bb975f12e76f45ef784115aaae8028320ec801404272a30228b1334b1454963ef48db6a5b012db987147ac93ab5e5a71570aac6a0f392d672e64411833abbdc53af4c9d7e29f8d45fb16881e33576a7650a2ec89455944e2a7ac061095509280cb650c598b6bbd52a47ba463fae9d4a56a04e0075eeee72eca25a492b8b6a25adb62a10cf1a45da7cf34ddc64fcbd67effdbdf77ec8fb1db643af6d346c3eecbebd9a86896230a6b8f9348a8b9a969b11a42d8a97cd06df0a550604a54f2e0d826e2b4aab8cd2288df22810b38769acd35488dbef402d56579fbaf23075faeb8bfedcf661e63017b741afb6664ecb5a24ddb1b654984497b45a79d7bdfbcc8ccc8f3f6e901dff7478adc9c33cccd4b6b52ccc10ac1128406a5ee68dcc7ccc1ba97919d7628d40e9de48cccfbc1199aff9981ea4af59ff23d7bbb036fc2cc4fcccb320f3359fa367cccf3c90989ff92e0422f335b346792452cd4c7793debbb094c32e87eff0ac451ee200f4cc1987152db948001d0e2b266d259db2c661a2b88b29f633adc592b3e01e1e629db6c254f0a9bf1548af9923f364b33ad62db257ae5e992e0e3b93f881c98589794d1e20e694729dc48094655e6bb129d74088d14f6ead51fdc30c824f24ab9a082fa9971e929c9fba07fb7d3fd3d01b6d3462798a722d8ee44c2fb723d7cf0e3fc3fc5c7fa28d464b1bec46db886aabc6fc34043bd2b235729692de0b47d19f740925dcb851a3060d4d2994ed9420def8712bc7addc581e868579eec76d15137ac15e31a1176c66442b398e9d96efa99ae7f75ea0e243933ba9f8a0a4007752e9a107777b798aca3c554337babdb66da6fa4be1f34183ec781cb8fd08c1c7a7f4fe363eb60bf3afd5e6d362fb167f71b90edbbcc47c84464ac411932e2c0c0b312fc3c2ccd7849b9223a48779f157ab553806d9b9a4e7b6950aa487796fa63402eb1d417064fc4b0307b25b7afa5e09a511386fa9c4eef5aa5d5d58195708f55e06c9a67bbe5e29ac804c38fd643f26ac00cbb4a85f2194f3203b1766caed4dc9f6b3f9d81aa67e71b1632569397390cc1435996262262d61eed82f9b15643ee681c8840d24473b18767c0a6bb1575b6fe527d3b2efc2e5efc28d6848bd1b6d2b298d16d15a779b4c9d9a2de44a64cec49bc4d01890ae5caf99d3ce2b1d362bcc7ccc03997920312f13ae7a153323f3397aa699b4a8ea557f9fc04a7eb5e5ea193b75261b15f332323134aa6ce698487070d628666a9bf6876e836d47c05c0edd357346947e29747f6ff4a3af3f2d569f162ba902db28d568796a7bd516e947983bd6d6ed265b0463a25a8629f6375176d07527f8d48d7224e60ce68edbcba58801a7cac329c5b497721603bacb4f31a199139b3c4d7814b7494f6d4b72b8f52705b73fdc5633876cf1c76d556ff8a930734b93344993e4835fe0eef93db481c0ec429996fd4bfa71487d11949e4bdf7d3a4a21fda13e41b02325470a33b9ba3bbad0759299b79feab3adb6d8b1ca6a1452eee4a1325a13572e45a2ca6475c89b9d3a0bddb167ff26d32ad579c9585ce6a76d54af1acb445519a65836aa922dca8455e6ce84759ce89b40ab62de5d5b39423222b7dacc19c5c0a7fe8de6a7fe1ead5c704726586d45e05f4ec14542366aab324cd426736d386c5e6e3f6aeb32c52fc2349c80891aa84a507b60a28262e3831ee103a440356eb51192394735dca6a3d5fdc2f21d7b46573e2d92b5c54af887653c74fb698bae286b2b47e4f5229ba37be629ba649efa690e94071afd6166bafdd155096c550360b02f75adbc0bcb77a4af1e93e892c22101f83e306f1122dbeb75c7edb55d99a77e2ddb17adad8b6b2bd9bd7beab4a3744515600202bed27b269476ee9ff0a540bff4a477bd9e00c18e5b79b5d89897f9efb672248541762efdee05faa5277d3d28e8fd8feb3b362bc4bccc5720e665c20ad0f8e934c8ce2d3d83e3df2eacb33ae485a51173c2a7c3fb133e9b1566bee62b40f333de031fd0a207e6e02dece8ae08b4d83caaaccaea10cdcfbce72e8f32a3a6c69fcc00a7ca5d9eaab22adb8efc10c30a685102ddcde8e3f73a674d9e7953d925c4cbe3195309459cca2e16825f3837b016598889e229a6c83861259d9c3da99d57f313ab3bef819b2aaef50ae645534c9483d139cc9cd5cca9028cd6814fbd648a5335eb2106038f41a7b4484754d6a7a6334aadd82d2d1675b93299561e33e93dff793beeeb43a0d6f0057e2eb4193dd771a311e74ef29c06d12c2ce96d40c063f4def3982008818deb8523ff28b4a93fafb7fda8ce298e9e04d2298e7e7e29d00b4391bb8dc05a7a8ad22a8d0b69152ad4221696ca827860bb5abd57ac966a0dc7203494c6f8290c8acb3c522a6ebf409fa853940ace62cb7c85340af7d01675324ffd439dd4d299ab98392de3b85797aeabd568a5d4fd85c2e520985789f990100eb38c432a73998ef1cd7324b73ae6a94a4a60f4679cbff9980cb1ae0b2b11b562852a49b2ccc1f0229635ca672d7a51a3e8cbc198282a8467b1c243595021268a1631c5a6b01629cd4f453071fb29a5c9682c65e6b0139c44fd528075123be38e0cebe1d9d407c01f51e18036f56ffe88ca8b21d9f8ffedbf1fd54aafb063dbd4bff1137c9c6721fc1ba191fff07142235080fc876fc4c6833f559c8d07bf85fff0e7a742f7f581e0fccd6fe00a387f131681320a81dc3c00c222ff375fc4c6e37ccf1a65e3a3fee847e104f907fc8152df66f4ff475436a35008101bff611122acdb3f81a9629235f2cf4f55f823b36c8450ba9f2af0c759b3014ed5873d0bc116f2ef870d1b21e77125fe7a78ef5ea3334b492ff473380e385537e05479a3120e3855a34d0ffafcf4abca521e5821dbed24387513d6959ffaebeac3951437e1ca09da5a991c9c749efc6bac687e4aa4ccca58e47a432dd6588dc96aac0ba9ab45a06a85e5d92b89bdb9b47916cef8c53370f6783700a88f73f37e135a50049c08ecad610d3ba8e4d519c679d36b736de4f675ece6717eac65adae565eabb153e7c799b11b654ad0b3d7bdf7f336cdad455e586b2b58ef200a7a1e8a4e514b5bbb09cb1414699161a698e627b8fdfef5b0e9e1d5f7429b1ede7b21d38ed098e179dc937e8aa477b0870b77b4c0e59c0b9ddc903e273d2becc8340ad6dac467b5864e3a162c45948d62d815d6e3ee980c1a2c8d5b4300c46071fee6e7f7c307e76fc87933c949b2700010fafce050e1d8f8243baa7ff0c33009a7c261197c30c4155ec460498e06003855213855eca0e992b499a365ce99801fc2edb7518570fbbd15669686fee3d36215b6daf4a8ded73f728ba69716bd467da8b0a3cf68a33c7472f3ab9b15499d7ad40150bab56c9173b721099fda6f1ae5edc33afd9e7703b6ca6fb8cbcc61229820861ad7d8c6fbfe4a422dab7b4fff411a4dd81b15f8cb75af5df51b0e14801af7f09320658a1c5810c60d4b9450cd20d0a03cc1162e78420a9718ebd05c6f001587baa272a9d75ab43cd41a35696ebf7b6914cd3d1284106630830eb6b001891778c0894f0d6ce042fb420695d24aa98dcfd1d40637caff06c7cef4d4cd4da5d5f2066c953b39b3dc5a7e8d642fdb8f7ee4daaffc87bbb174997eed420ad6d5f49efa93be03e97b2029a42dfa7be0c8ddd136fa3a023ba8c5396fcf369f5ad62bb574d249275d89fb38b9fd6c4a1a454b67127f5d7512dbddb1ae5693a79358983bd65527b13577acdc7b36250bd62b5ad08095a8fa00ac59664eff0860dd82766b805cdc7e9b19707e865af424cc6b02e79d31af0c386f0c0c7c4b4fc29742df5258cb994373606b7ee6bf1dfc328ff34d8055378fe363e6f9397c81c6cffc84c2969eeec08e14e66a14a36078bee7274d9e6d44ea9e5fc6e4f19edfc64c1e4a734b6f430a5402a96bc8b65aa509c57535aa14be10f34d734d3f7bc68f35ae89e6c71bd7148e4182dc98e79f9f8e19e158e306c7c683210068c2f146006240be161c3905fb341f03f20d47bef64b30c8f8afdb9e47cf3de9bbf7be3ed3076ab169c0f1c88d293d6d79aa965d0a4b8eb63c454e61470a83358a4628847f0877850cb1b744a7704f4d588958a73fcb16b4aaa57251bba85bc0fa8399e3c5cc01c2f6536b2079af4c1d2c33a7dfc69bbe155c7513d6223f95525858518bbca36f15ec6a24668c0609d45505573179bc5114055ac20934012f1bd0160daa311b70aa52a141b455a9502aa702d41f7d36cb1b1c1b3f6e4aa8cc671f525adb08679fe8e3840ceb53ff686f6853b887c6f7b311930704334ff37cc4f7f4c74ec6d71f3989d27b3f7a776498fdee470e8279d28fbc44cc730c0381cc8f1856b7700f8d900e71a338a4b216296d74a790ed8e54c7e54fa66549773a8103977bab169307860d0bfd8fa8be0c1f785fcad25fb7983c30d0b88fd152d2fcccf3c0008223b726a4558415c9cc0129156025c15ac3ccf9a93458b026298175061960fdc02ac306d6510f6e3f97c5a55858b9fdde1577a4322b6ebfaf9c9c79e95648d98c0f30e3ae5765950a2fac2495d15a5b750915ccaa37541f62b8fd5e7b01aecc2da65f31ab97cda7a763e15a56e7dbb508dc0cb73714dcfe4d05379cd2432153c9828d602aaeaabef3270167d9f4a8ef1ff291cb341cbd05dec46b949c3474b2632dd441af1629cd4f4da3b45495512af84465ee5db628f5f97d98a9ba9ac115e2f66f31706a725d32cbad87bbe9b0ddb09960d622d79a49bd325337dfcc7cf270e4aefdc274fdc7dae58ecd04c9a53f5dbc202ff7d01aa64e3b11e80992a0a5fe76d46a9f0e0ec7564133993957a650d5c27f1386375c594c5a6c19ece8b3d565d8cc49aed56a5cfbb7fea486414e360a430e8395608052cd29b1a5341da57777f69751b28ff3374e7214d65fc68fb5645a25b25ce28436f471febfff96de3eea7b1b7ff34070fec30d8445a000b1f138611128a3139c6781bd18c2095265a4042189f3386109c03782f336be081420ff36c22238ff210e3ff517f99f4e9fb06c831d9d044328f56dfccd8f385660239c7eba9161d9a9fac7f971cea0b09546f4f6a90eacff56bd7413c1fca86e91b1bd8c9f1fe9fbfeade5299c3731b9f447c9dd5cb71da4d43d2f1ce99338fbe92085d37237b8ef6f7ca4cf8294e651489cffd1570ebb31022674233e1b1e3e183e0886e104ff71a896c9801fee0773013f22106c7c3fa54d146c8495e627b0d66eff28ac443afcb91c58fe2df4578b4e9d083782fa14d66927dd7fbc06279d74d249274937eed552e85b4bd6e9a7542ac93a5cf30398e021a054974c1e117037708f08ec9d3c9cbaf9ae4d50b5b4f1c12ff8f3cfede77dd6a7de5838b3d2faacad704f169c3aa2ea77b251ec3f9ee5dad9822e1c39184ceb50265d0d71fbd955c58e5cab798a55ff383efac53afd30f8183f6c3087a30db8c7439b1eef638c71bd6dc03a952806fbfdf001ffe649ac7af31c69b79f0b8661596928c0f9ffd149362bdc3cceb770f338610b38ffa1e8a7272cffc8b54e62bb6583cdd19e8c6f472d3b081b4800061f638c8ec23a6de3792a6de674c581e30007186b79ba23d7c03bd612bc23d76ac9b5cab5155a6523aca5139375eca7a3964d30ac6af9e2a77659d5b269378f8373c343d65715a22a41c67c48aeb97316870c0594a8901d2b135646a5a18ec01ce7bdc536d8daa4511d98b25bb9873b35bdea7df53a3065d7a64778e4ceeb797daf9c16dcc34e58e6f712c7d54b431a6b5361f400891b0c01c44594a9c513404004e1d3822148d38a67f5c6ff8cad741293a76a31735215cb3cf59b4c3377ac65ccf39a75a8d8b1962dd6e9c701e9f4d34867b79b708dbb70add5a891ca2e50a3464a5bdce3334fe17c3fd7706e9c003db15aeb149706ddae2bafd515cf9424d67b4a5ff566a55b59c28e5c8760022bbc1c1186155602e0050cba4051840d5438d17531180db2d40b19d6a20fd4a521cfdc0ee4680a5225acc725b64fc1baa5c5144a9a841d2b127664587d55d2034da2b8887b9de0c3c483939964ce4562fce4a246995a1463a27cf068b1299b1a85c266ba5dab7d4c54e7c30bd6f9c042a1514d048bfd0dc414fbbfb05bfe1e8e9c00eed828b066d5bdba779366353979daf3a80c2cb7f2d63ccd26cf0cbaa9ef1b083193140bc4f41f88bae5419de26af3d46ff2d2cec5b77055ec90156e8a99e2b82b2c96db5ddcdebad6c87de586b8ef7eaccf0db538e338d28fb5cab659a36ad89adef296c95bf3e43d37e38664f5b9124cfdaa2cec9f0e680993b63ae3865a6c598b5dabb3eece257ee083cbdd19f4440beee8ad92cac06e3f36f9eaa2eece6fc78ed9e228eca199777dd5a8fade9a3929b7d2a719b4b3e32d6f81de1ac15bdef2d9cb8e9cac66925999b7ec899dacc3cfd5809c5fa4d36eb5d87cc72627eb70d8555a6c97e22da0cb62b1582c160e1c5e9d88e6639998b8a7fe0ce2aacc8666b34f562ed7ea5edd6138d84be6a7799b1b6d3cde6b0afe83cbb05eddab4f2aab9193bdb62f8529e3649c8c93f1a0fc5bd1e53a6bb1d65a7fee1622d8cc91426b60b95aebc759eb9e75fa8978ca7fe080b8ed605f6e75d9a29516592c9692468df6ae1ac54c1ae5cfaf9ef16b674adb2a43d78aa760fefb7e0b038a5dcb8354515ee5e5a0290d6b584fe99403759094d9e33c4c1d1fba8857b3ac531a4099b911b6891db91a579b3cec40d69f3eb17c9fb8b8daedf7d60cecc8bd3859a378a851b35a6560e91d1bc6bd3cc545c159c1bdb82bdc8bd3c2bd38d9943ef5077155ec105bb9f5d5a2b7bce52d7f523f206f7d2d7e79caad35997ed65e2f06ba266ec28423e2b5eec99d311a00f9935ac3eee5758f2ed879a8a9d4b6405a6c8ff39ee2a0f4dea8e64eea3896775dc71161e647b5516df27419d5463cf3343f554638567d9a91634d60aa6a7e7e361cfea00981cc8450c61b849b18a275f96b424ee81b85513f00b367c465eaf4a76a7eb4343f6306d865f902fff7f547f1d62f8878f9b92946d8ef39588efec079fd63df98796c423cb8a78764787199c630dc1410a6653c0c076b18193b72b4f7bde71f7365d9571ca8512514780cdfef334cd408c5b90e3fba0a6eff1cdd878b55c7f23e1a263ae9e771e5e4b95296de915a3a52878d3f9099a7f9cfc6435358040a109aa7f1d3431bfea9a211161915e10411a48a26e4b889215aa17847fba358bee06f930233efe8517a0ebfc5cea607e9f977909e3be6df312f87a27dfb2450c810eefad7ef18643e4666c4b162c2919f70b11a9013f2381c4c9d2386b032c2160a633fb891773fec234eb6b96f31341c16ae6c122cc23ff45622b3d691dc147f0e166b94871c37678dc83cf57b186b710a07e362eddb178123d73b653b9f169dae2c17e360306e4aa3f83d057674e7124aece0d63b99a8f2ba485cddab83959d0caea4a10bacd3917eea92d084a424e1625c1353a79f08ee09f17a32dc05d3982b5d0aacde9183d54a84933253ddd16e9e1e62682d38d59974b037906e028e0b1b0422dc7eb6954735ca6a714d280eecd8a517320a8a53ad1ae1000720b4546393f5c6e471f132d8dc5c5b65973fc85e35abeb56bb56d79a3cdedd5ddbb34ac4465f4635ee8a6b611ad801752f3fd1991d5c432eda2233aa8dbed0b087b68e155d99391cf7c5d4e1b400e37e3bcaed8e5c49a7d8b16b8db8cc1ce677a1451eac237add60dc13ddeddd6a1c71b9b01197c9e3f5fb7f346aebdf2a0a4155623175a33f58e0c13d341c71611758e054063230e2e282968682e6091e54dc1fdce5e4e9c9339798298ee5d3a54c0762f63b9299e2ca2b338725a5d7c069b15f4c5377b9fddc433422ec570ec60979aa86e360a32ef3d4486e3ff7034512515ab7017726214416d7e3aa7b75e7ee38aad51a2584bb475e3ad55d8ea35a39849a772d35e5ccd33c0b353f83059a9f098b400132f335a6195f33139a9aab710e71e0d8d911c56e1ac44d69918b15c1c15a6c1f36ae874e6686ba45f2a9df047316c78d2a0dc27a0b57bc3173f81d0c225e9b5b419b4b36518b1c76d35a1cc2f68f4f47765dd7310f5dd775a4b22c2abb64b1843a5937ebca8e263383837130512439d80c7ba5b4634d651be252d355f4900b232edcc3e3c7e8e9bbb81c664eeb723f4c6e061c6be6f4833097e338224a841dbb56c79e9a51f36307e3588d12e22f640877b92b5387c332739a748a23ea53731ccdcf7823335ff346687ec68f4d96940299f1346fa4e667dec88ca7f9b1c99a9f7916663c4d1128406a7ec64f3fb140f333c222a322c400225535210e3ff90d5cc454530585eb7bcce10bdb57126476b0418e4cb9db8f41ba77f005fefaf5bd51adb4920661f982db27d3b29dabc5ee805aecc2ae25c58e68d782d9b1c93e010d13b591353437fa74d41f75d51dc61fe6a98e8779eaff010821295debc795307bf5278daad2dddd24e9b2e21c9befcd80ad128348d79fa95d7696cec2a9a6c234f68299498ef27ab27225feb3d5bad5ead3b42e6ff7fa21eb5a1dd962f1a99fc5b1581cacdb5a7a033b76ad5a8b05d4a89173356a9ce5ab51e3a8d63d79b6c1f6531b58ff912b278f94c953278f95c9b335317b3a927588600e8bd9d32d51a5535d943ef573300ed6b5b82e68e6741d0f60f722b9a703a2eba123b71f4735ce07d60e13c5bd60a23e168c634161f9c7bdc74d8e1d66c497936124c6448d3151dda8e64c4635afe1768d3b5a9fec5a1fe8515a7c3d9939ab16aded5a5dab6b3da1405dabe5b2a1c9b311f93799aced2da8b96cd9424474799cb5b632346bb3366bdfb703e68e6a33877f1cd5eef7a960e37e644996db6f29f03d72ebef20f5207d0dbf6b68d3a3fbfa3bbaafe18d9933fa23777eb5fe8e79eb281c838897d6e7fec8fd9ec7b85fe864e6416e0a07f35497ce71cdc1bc8495bad5dd1c176b949069085b0a5b00d2228d623bd2535cc9a7fe2d1c392e2c39a44158ae649140ae8b12cb446d3151dbf7577260d0d241de2acb53ad85f585ab0bab19a82c3be6ba5526bdd64c2c2e5021ecd8250c660f0fd621819a49f792d9d343acd35fbfbf63e014c7b3f1708d211cb82c7c326d5c3d9c29a6a5d80b17b8878e6a7e622f3835fa12722937b0db6add6a652632847b465c46354f712c1f7de114e70276b65e6f232e2e608db88cb8702e60161861dc7e1a841dddb17b887b7eb00e9785539d844f5cc9837b7c86d95c7a680077ec295486ee3373aefce09efe62ea24692d4a202cfdb123619c121cac7e3f57c44471dfcf1d31511d27e360958375cf4d67871975a4a71ac93cf5d7c02db9dd1dd964c9ae1681b8357338ce5a93a9cbb2dc3e1f5df6969926ead2534dce905d324906d1ef5a5dcb53b3bb6b75d7eaaed55dab13da8975ad98ac6b753c49db9198662834a8af28324a49db887b86796c0a1907ad56b49242bc487625b8c6550672f3aac83d3290f555859021e0ce2700a7a8ccab2cd6a93a74ed41b5b8e3335762a2ea14b32eb981759854569f5aed7d94ca629446bd88c128516354c864501f688b97d8ca2fafc6625e3e858c65b2283499937e6a752e036ee5adbae2c15932219adcee814f9ec3145f3258f9aac43034b8a746c6f7573026cfc765f26ca330260f47eabc4adf76a904ea98f15ecd4cea8692eee57a2dc67e89866725b0fa9063e9ed9320b3832d3dcd8fd696defe58c9d2bf60bffe34ca86cd71d696f3d46fc351e663de9301eb4f254bd6a75131e1b4f69b246d38fe2d855cd2806ca5079c021b73e7124914dded4e269cd0a236794a4f7f6e304ffd3825906f1562a668cb5544099cb7be3c4583e6a969d06b46f8e58e348888273b4ce983268f10194b4c1e211fa53279846c9489c92364f434df4f85268f10ee677c3f6d62f20821bde9fba913934748f7237c3f7d62f208f19e84efa755268f90fa36df4f73307984d0aff1fd9e17a73ba8940dd357450e324523000000000143140020280c0806440262a1589514bd3714000d7f9452745a1d89d324c7611043c620648c01020000220020826d0390a878fca82b7b6b0cd8f02ab0653d9000185a0f217b24e6a86306f688a612542e5ac2d836451b51f6a3584cc2e64fb4990b5507bde2072deeceb6099005b122b7bde3a59412300b6871694e16fd4a422f5721b8dd28e9c3567d5c2a1e7dfd713565ef600dd725a36e68cc0c578e5f701043c4d3ceef51a29945895f339502d82733a30401f3f4e4ca7e18aa89828017ccf79904640afa61f6424d1372ffb837275dae343b67a52571afe65e46f7cae47192c748a4370d195b8808f7619fcf84a265be2f9d9ec2a515e439f351b17adbc2bb9dd2367c79c134a060845c3cd32a0da405e788852c42f17db58f37bdc01604d8d808e3b6a45506bfa75c95cb6ca54bcaa7425485ce94da77117db2badf9ec2a20b47e4b640b2d73438926d28a3a312e6b1d05a7773d482c5512ce5b4e4c8c2198609d32f730d68225dfa20ee1129088aa0bd8881f70378322835fee7d900853d0e6fe384403035c5855ae62a0900687e0e542a266503cd5fc0a30cec4228043a65f3f5642a9bf9100785ab39e075e7e536b283a3748f08f6901b09f001c8e63d0d4424e909e0f93fc508a27502131bd4cbb9c2286920c336326e1d38bebd8e75e602b2db77129918f34dc9cfc45f9dcefc2ce6209534488f8c2875197681c926be762feaa94a18440de4e226c0d93e15656f1a423850363c0654dfd7cc5ed4e33ac56922c60435ed604995a073f4b9d9bae53a6e674e45a46c9d377b7c75ba5126cd803372dea4b642d9a399e834edbdd178ba75cfffa2282321df0eae93a033303f33325b614e772bc516651a7623686680b9d903c8bd6af2c8ecc52371c392bb5e8b69f82bda1a637ff79ca66070f7b7793115f6a16d39d68356f5d15daa1d0f5e5c1bd799e80277139f0cb562f12e3244bd451e7a7eef6a2d08c74409538bc17fdbf3bb2527cbe628b2348e2408f1b5934e0fac395cadbbfb10059a39a2d05e8104bbcb025a6d4039b74d65790cf1a5f46b87dbbfa0bae0c2bc4d64983a4c7888395eeb030654feeb59096144742f931c96b27c047f8f5587f20c8db745413d0b00c6d7d96e1e55854a6575dc2bab894cb39c918ae2af681a65a443e2e1db9f6e9d8da172a368df9bf17d70e1b518e5a5bc2c09d02e06ff0a5fd5820bf0ab819e1c192bc4903049d2e4a1b108499f12e417c8d74affe762dc4ad7b933219ce9cb47e44760f99a0f31056452762df96bc60f4f6890731b1e5305464a5838b14a6c06857f08ef6e6093c413fee7e9c1fb879a82704c9c18bfedfce8e12948018d274872ca24173e3e1f6876d4e23fc4a7398d88b79f1c74c806316254e5d4c16c2f7b456017215bc409930d4504135b46340ad25b7902ec49c7ebf378f7e00c313f9130c95b317af111a70d2212ac7f4dac906056bd1fb9fbccf275cf28dac4e565867dee4138a901a51e74d6515abc687d03dfc15849e6418f2703f02e5981ca7203165902204875128a0bb99d4aa726e99fea16b1fed807e091175fc514db51d48767fdab67ac46b5a5bbfdafdb054e95bfd7b6e20bb9cf7fc41d87e46c9b62075ddf8cec9e6b7f8b9dfce6c1c4703311c9af072a25057ac1090a0aa8540a0ec50d059c49826eab36da703699c99c468e576eb6aabb2b04432e1b9c8019a9fa1ee5a83fffaa48d9f766151f00d1c24f176f4efd737609a8fecd2e0113e4b35885169f60919345c296c8175cc69ae25d13a83c950fa8f9eb811d2c0c501b523446bba3fa6865b30b3bdb34ca78d09a5676a5bffc3fad7ae0a8cdcf70a86126308f45026c4e7e41117a05f90be00f65301af2fb6cb79a01df335580101b9d260c8c705d5623593203fe3e631e757c7b4b27d65b368e22965d7bbde0d7d2cf6aa232bf8166065b1c69f3718c75b8288bb1bb7c7d3792fe73e131c812af09051fd8ccfe65eaf3b51dde8d2b1fca063f929c1bc35c71069787173200954a22a862cbc59bfcf0145b9a4fdef7de92c1350d679c686e26e18a8130a7ea70533df4be7ea5c05aec1d48b0351860de6411802a63c8a7daa60e794b985159df7927911d4e0a804bdb9d0a09efd9feeda8c89f56ae69b933e2de9c584c01b17aa11deea54ec7fe392a984129b6bd53742b21a18603d3e1ab53b06f361ea8b18b5475e248b4b5b73576f6f31245c724c4dcc44e6bc97ff9bc1eaedb4a55d2f575c83ab880c2fadd2b81e177a6dd9950748d33c15b4cd7a61d93041c28a9daae76a526461cfe6b38014850f2cd4076855a2699a533f95ab4d45ad823366fc5e307b1c61b4f39e67c867a8407839740132439a207e22fcda7f1ef7fe39370e6e4811159bf5f449d4c091d5553456546bb1eb71299bf68b3d461a3b7d795ff105b57acd372f58e63b655e12bce3fc9aba76e4f640e4e54efedfe82a14172582000f84d960090653d8e870f210bbaa504e26a4da45a3325a96704a0838062d67163c43d457721f50c69049e9b9e57aa4bbe85fc05b2dc7b179a8336bdba187b690e6bd773bc699b277a889b446ca30885aecb1e7d77959241cf5ffbebc0410f18f7dcb2615f6476b35c135c8329bc14fd4346da46ec1db7863c34b36bc2b40a231f55e11938719a8b8958d8437b5382832fb82049df40c97a9711bf8af6f3bb13503ad9d212a32d015fb2770034e6512b3a7ba694bd69fcc3541a9ea97ab8f1f7af1ec37bc4277a556463a44af49e066ad990a6281d33a85cf9ca0109356b676bd2d6f27c21803478fbc225904f31588a7c6b3d832f43ef85e5c79500adaa3c990f891a5ba57cb09c22414ce3d0dd3de07b1fc59a4cf15d7aa0b317a64f6fd5251081eed9de73472c029cae4171d46c7d89427f6d68153608de66f469871e962d2dc04d0a5ea0126d96519752a30338b9ac5e2af8b1cfb4a0b1faa0c00db8533bd1511cd4600b36d2882ef7ce8f8b92df5e867c5c82184f68325d8aa205d8471ae70eb7b431c76a46ebda7f366037976bf6d35c41025ab9a1a5d89e36c08a1264b4bff6e43dcf0642665eac7d4fda535218e81fca5b98b93e42264ff9477d9af9f7083f42812eb0fa4cbb83d48053d838f79d68cead5959468365367658fc41fca39610b9e3521c10030a5be0f5e5ae113eb9f4215a7b209a7383a643dc29c256537cf15ae85b20b27f778410c5fe8140d815fb37b036b870da19d4949733fa62c7b189e90bbfe4d2433ec1185350ab07973bc73766c1d0cbc200c94222d0299837b1455649188d2b17ad5628fdebdaec7f44e9f2e19d55e8a73ae34a22ff54929966c23f47efb802c197defb6e12470d2156673c4d1b2eab3c346c91af4668c041a97aa76bbd626b339031fad532f90b5b75287193085cf35eb67acde05b701e4e86e6649dfeee71953cebb7c3b480530c15778c428f66b83266977e1e2f8834d44f039d1eccb23fc9f5e7795948ade354666568d690f5bdbea25a7972a41f6d907c47782a1b643a75bde5e938b02f42b85f8a81d0e9992f6ab87cd3258bd756a87e83842b04183b67cf18aa72f3391bf882ce6a2bfd707d46ae49c1a779cd8c97e67142f118f64d52b8a5cf78085f21debf536f1842d5757f474adedb1a0ec378e885dd731faeb4ca386d37f7658a83935fb9bb0d60bc4d93da660f4ab64d23270e918ec6ff0a8153d419ded3d975ed6e15cb158d8b87eef75965e715509b00e64f2762b8ee0309562c3426a589a1b8010279804b35b47941a364692f7629463da04b78bdf1f772751f0b83ca0e528d9be58680de708a495b3df08bc3c6cf4218ce32d3b0c940da8a13c81c9c91200b682c0b1624a3daf0a876cf4ea1f143f30ac107924cb9fb8133485e5a41501be6a464d53d293c6bb9109a2e05a9b444772f7b2e3dc03eaea2dce065cdcf410301e2fba9f92affeca3399fd30e8d97fff8ecdc862217083ed772cba7cdeef538a37158a2dc5068c8ca3f2d6540bcba35d728792fbd4d437870d9f773ab5db4ec62c945f5671e398621c65ffad2c649ccacf75b6878d4c1da30c63142da10df78f3d4aed4959dc0ffb72d875b6494a556a1a4a3af84653fb1d7e38271aaf7cc25edc912f0083cc1b96deab4ccfaa4d67a1aeb9f7d59a381b76dad88606059b372cdccf4083000e54babeb7cc3604a62532d1e007f78d7d0a9c1fc59637721f40dadc45f781c4f8cfd54be5a5b123624bd1ec92af5fd4bda28c4413a076b74a581163fb5e5efaea9a048339e78b4bba46d29e5d9e5285c7f00fbf4a7ff3bbeea16da2a56b8e3e4ec9a831e3f5845fae7865718cabaa575aaad9b956289e93e0949deb541425c2a4a5ba6eb9036c2a5cee0e58b91038152d0bfd846e3d158a81b12e087ddd1ebc2211ef0ae5709d8e3d74d03008346e7e65675411dedfce9e3edff6d791a12c5fb3b86058f0dd5417f14a0c535f898394d947a1ac739705c2cee8c8ffd370c6539751d47a735be9da772ff9daba0924a961215ea8df42e11e26c9f6e69bf63dfa5cd5b5eda25b2affaaee33dfe4120e3aa324d92b9c0967039e15f93775c1243faf62dfdb596589aec4da503f217a48702b8cb8a1de908f05d25201619693f9361feef7b11565bff83dbbd634849181e740d2366ac83944dea097915ec8b5bf6993a9ff102d8efb73bb1b7e63e803737bb5ef7f25d72cfd2a4b0c063d4e21c153b3e2fd22f7d0d9c397479ffd2436dd6b4ab6e87e66e51a7b16076dcf4b436747dcdd04279f6a9c7c30e6b350fffecead0ea55a9c6afe5cd0b32212d1c258416c9108dfc34ef7222a44189720a805850acebf245bff62c0f1a4ce166c6a80e6653796bf9eccd094d0e90520e47c8391a95278c1f49529e522031d72d1a4e51ef4efb0ae8fa36481cfe348af34861a3bd804bbf0f843ab8306fb5bfce12ee69d490692bfab13150f5d05d4f646847b562d9ca70cf8b213794cbe249f0fcc6b06530a4497a8196acfdf1aab0267268c4906746a8253cea74091f24b954b40ff1b318858a23b07e9e1cad5cd3046b25fb7c3ebda20f0069d06b431d22c2c8eb993b766768f3951dc9c64e4885f36236b801ba354eabac19568472826139bfb9d71745d7a9c915f3db6afefcb4b6c5834e336600afef810b91ab9384e1d1535d4a14c61395e5dc9f13912f61136858c50d2b50c07956a3f3a33fecce55c3639fc89ecd78c36acfec62a7cb6301895af10511c8a4ba169c92c5cbc9ca9f698d760855bd22a2b81557d566aa34b34c142bfb93040706afdf1b180312b047f85a620a31af11e3db1a3b0efd381802015fc53c23043d8839fa8bcaa7d8ccec727a405d7de5c9d6875c1b02672d39ce1155ef8948ffaf585a031e03d5604e9918d28d47fc3fd1b54c3beb44bc81e135a40f431eb1853dda46db02e8b915403856cf52d24393cd48b8503a684ad7e45b4b67882d0930bd835a2afff4b1072a9c245e9c3d6bdc71f8d05aa73a96bbd6b827e3267e834c220f1d6f9f8b7041f6ea0d11992d17fa3e4e9d0703853bd8781746e112b40ac5798a13e59fb169bef51adc60c9fa9f97daa2ffebf1c2eeabf8800412235351755c8850fc4182b8483b25a9b6575acb0c5f845bda36fff71422ff45559de789efa2af3350de3b5f4004faa40041b76454d032f5928fc899902516f9c2ab548f0c4cc50a32bd039d14f6378298af363507a54a779d22a512407232980686e3f1aa44c4dd252e9351a1336f682664990fdcf2489a2253f9245763af3c8adfa7acb4fa39e20fc17f134a2dd97b5bb7c7b32137b14dc6f1b705495a0aa33d2c85458fc633df4ed34284e09b4254962e4dd0c3cb25509ced0e848f4e718b69771ceec90652826ff72312543c7dd0e77a689d71cc331ef216aebc7d79c485474cfd577e8e51af657fd864489ecc7491212d7a797a27d63c21da34cff4bceb3ddf6255ad1cad9e554d57ba1363aeaddf74b6444c1d5d048eb13bc717cc28ef859673627a32a5c3699a92c167f73f4752cf25fba8e0b865bddb6127f1b6a3a9794ad3e507195009fe5d140a0f7e5f8b22786f63eb15c520da9d7f902e330c431ff9abae01f2f6edea6519d120898dab828c17f186c4a9a41c67f575f40bd4071834d1d3f8f76e4168bcc441cbcca1d719e8dbc5ac3c401db04854af0fc297beb4a103d001f89c181ab31970a66314fb281663c8d9d4052f47c0e52c40cd8003c42c715435fb054bc01696a4fd256cfcaa4c26b827f83c1f4245fb98a93fc962ce6d3aedbaa937913e4dd090bb3a1c56177faf3ad563b7b6f01289a45a1b4625b65997cd4139e4d7a00a8e2916c238c51e745aa25810fb05f381e1f9255c036d397df317551b519c03206eb0d39c0b1f9af095005c061c040b81efa3c31e133e0c766524ef7dfd048c7b717144d0f68a08ad3db595cc0ce55a7a4d9ac0931739022af3e95fedc21bbbae438d64e33b2ec7ce0f1aac2d0ef0ed2e3b30c1167a18dd46d35220271e92718858e76dd9e5211267f757302e41ed3f6963badfce790f4a623a728639a322114fa7206c44ba7e2f0360c06a0d60069af0a1cbe18461e1dd7ded7086a108d1e0120d12365c2ba0ed3fa84b03d87368edaa9cf78ab0d1d09c2081215d97804320c603cd414d88e94f60284373c18681be1242f2f9ec05509275d531547ec66a3cce91a51ff6f7509b4fa9c3a38cbdf6033e732d26afbd6384cc751576fe248cdb7060be8f015bbe668461a9a80f1f1f9fc34a120641fd57c4e791a7ba8b4c1cf45ab2bb29a6e8c71cbcdeaa0b37471ab744d295f98b6df455e95b5ea03b8229611e2f58c4ebccb4b3b03422a81a65cc054b63638356fb5a4ebd3a9132b2179c51bce5fe56250f6907342993a4451634f1b1f0c932d64048900be9d5ff3d9e622da90f363ba0d9ac78ae0c6cfb86c171c0bc4b18e08d766e998b8f9f32a0b319ac1b0d9099bb8056044b1d9f460f7b1d5600034784eae6ec59d72b1af07f55b72ade2d32c008502f9c1171c09d2940869819bed4d9d89b959425d18294ce36a589d1e4a3312d79a40d4cee04df1826ca365af3d37b6272237ed527823086c2c2219a65a7d9da649d048bf5998524305ef330251598aa9085e001905846f550e750ac8f3f01cb7b7536a807ed32a5d69fce7292e53c589d2aa9ceb6c3ab43176aae88707b7bbab095d3b125ccd0502e982d282cc149db147ad269aebb39cea587e9e1ee2b40e609993720d18e7be95c0edb421c247ae1257c3cd2f7586f08a504dd5fdc35e30d4b16a2758d7c9775ddc88907d51ef7db71175bcffbe39298c1ed5fe96049d602c426d6b281785a7ac9a0f4173ccbff82c43a43ece680667e34ca3ad6423a7bc0f553414fa6ba756a7d3e629975d8c62db2787497b97a753be0bf62c86741d2fe4d22613d59adcd9e78ef76aba175eee65e1447dcffb4688acc3c03effd1669c3295a9318cb0082477576b92dc0fcd895948a572dd6f15dff14a371074eeba2f1587ce61e26ae91df652647af6178758c0b335d59ac35aaf5ee4bd0b524a69048b8d71c3f24f568faa3bd82b2572ac078d32bf1d34acc058494fe58e05355ab3af1ae391f9980d812c11c63076c976ab746f1c0056266ebab88f7061218675b51dabbbf99dfa501a226ecb4a0e966071000187af12a21121ed9eddf61ebfd4230c9383be091a83e80d6df6b832cfc1e226c26a0f9204a929556b0846fe46edd0be4e43be1ff32b5a50a728e65b40e4af5111006b1e10b75cd34827efce8ec41866c1d36e74799d97e2bf14821b09135badb3637ce12057054fdee72871697b08d95cd53908df20f3c78884c08f1ccc6f9c8dce241658ace725d3c4c109b51f6b983acd43a71d076ed6da9e8cdd24054ab93a494231e20f72360cbfe5d0be03e2847022a072691962a01e3092bb0ceba9e6f92e0f7f839c59ba20c96f0fa1ce940ab3a2f09e128cb8be34c5b454374ea80e82c868cc2e0fbd3e7595abc34e14e0badb1d813b4764172bf15578b34bc47ff802e82f792c89c3c9ac43405286fc1b18bce704c8360957c63741c2ad887ea248a9c780bd91d962bfd54e0e55dc8b7e576edc30dd185eb50a159786aea939ff92ddbd7774dc0339e600393ecf934d55b7b543faf15c8d0c72c4963b5b0b1d1ffb0d8362726b279786b9b12947b38261b5a2630d7b691303bc4cc39bc329ddfcee7117e2895d7fba44c8e7885d4ca77f3d207d331494961860dab9495f3682d64715ca56fdf97e94cb0bfb4540df4b65dc8ac2119823d4bfaaf24aa68bdbfe0f34dc96b562600d17f8cac4064edf63d72cf7e0418a4e2c8f24b70d13a2c8b06ce47ec7c32f0a23e8c3d4685ab8e37bc7b364a4da81009561254ce6039621897dbe47dfa1245826409077e9d3698edabe7bb64f92504523dc5ab4e589584301e489e441d1344493a361f19f99edd2460c6613db744505041a2a6eed3153c5fee36f2fd6f69ff3f21408cffe537cda8f7a987ad6e83f7f2074c30a7223e1109dfe0d861abf7ce2da0ecd3b99c5a114c76d56af92ba063cda422a13cbce5e0bb7fef3f271bfa54c090dec3bb64fd8336fc81633500b2ba5b9003490a799af3b943d202bff6390b02dd9c78817287abc5c200bec5aa45b8c69b244210848ff8e44ce004313dca826b9982e066f8ec62743da0b4070a02183812ec1d913b09b5bcb26f39845781013008c28e6bd19890a61d9a59e00c11be3be0f904fbc40f4a47f7abd981cb7f7b06f954822820b906882066b6d422bc6fd82b2bf4fcbd583952a1b2184f277e29a0f7b3db617f6f4e21544b66f5ab5bdf52586469af5d6754debeca56e9ebc203755e0a66f16d5b72fa076d3052340c7f81dadf775d0aded17946bb1f351ab9faf4bdba7b2d2a578e469289514367342b47b4ca9cad87c1102da38ed143fb76216f687c5943fb8433342fc6d0ebe60bad14f0ac2dd429f9b8956274ce722bbace8d5def39d6bd2ea6a435e70114deaa767631ce043d07c5b3a6d7b18b216a4b27b4890b7880605ea1b0ae0274e2c49102ddddf8b0b17dfddbc7b94d01f2c750b6d182bc0cf7af885530cc8359d75c14d5f5a26f928f38c33d267e92f971993465a9e1295b4e8041f2de8502915c39c0ab0505aae7f29c1ace0c33fc7589f332b8af63739388b265effeb9fc9e923444f30fa7b61c3d6db19a52cc1c8e91adf89139df744007a87ffb7080668f5d68c644053db8da53e7290c5df6c55dab35b191d3d65095591a0c120acea90a59242155bfd25040ddf3573084a7dfd7ae0781e3184b4d86ff7e9e74405bcb49196464536d5a6105b14f66a9177523d8cca12aaf729f2a9ebae1a1a27698928a0d3228157edf5cd74256ad7080c272c44136c95c0fb47bed9bd30161aad04062e0690c89612f78d8d754a08b708f172d3da05ab4cfe8e6c2a991a636b140917dab58ce36b677576638012d22bfe196ac71fda4a00934665d18fc29a6effd13993147e5257dc242f0611d3d30517b91470a02a08293ffdb14676387aff2dfe3c224b7f2dc31717ff4ca26defc5bd428d2d8d66179b0d5304e6077c8c42900c56c1605e987f2e981613a960cc43c4652ecd74781d4e18e6aca36b8927f778ecae2fd94473551518b1f68d268e3aead2d7a9c2dda702050083db912d7428180bf5960b9d65972bcfaba093cde11354319722b8fc636bdddd79e145d08c264c44014c6339317387144187cef46d64d7dd75742cfc280e166b329dec2b5fbe1f3092ac5057bcf7654c08f6464521407ffdfe26d8f3384aaecfbd66056c5928bd9ff5139a576294905b34cb2eff4b4dfc64b5b2d4642c14e437cdfc7a32fcda52426474a9f1302a5638dedb153d5b92e2146c950410ed9775c2b7851fb5c4c374d9bc10c9c96e61c6be07cee0b0cbac194ad3f8cae03715a8c0a3ed11032c63839c02993611f05c252283e1e765f8e89b6f5a2d28ba4d26bd8f196662df4887868831be7a89d1491bccc6d9c5d0d8f94468f088a3da6c4dfb69d726bf3018ad56aa9aa02dae1839508764bcb415c061b70438436718dc5a1f732e3277e045b0cb7b6655560103f2b0056b948d64ccedbb896800c42c424dae98fa7764f412164ed351344d96acfeaac628053b31a6d17594d5689fb3c398caada6e036dc4f6d36ec8c38f6f3ba0d8369a11bb1f3924ab574b83b07d1446c6d9ef309c0b16e542066db6e38eb288465a169cf89a9735de5e26f2fe62fa78214104e2e3bd2d904aaac37e2aa1483bff0d31df7a59af6e5b89679df65d795abb0ee2ee4ad9559cbb921067f1ffced02e4ac629f056d301b2bd6ce3076ec3d9ff237077b7d595dbcbe13eb46515001358ccf1ee34368372f4301a86c2546b9adb1c2342349a725b76444502a19d3acc5873d75613ad0cde5ab4ebb04dd751e6d9e122e2852ee991d87fba03dcd0e80d6b16a31608e3d4ac9106b50617a8dd14fa65f2702443dd33f470c1987c5f87f53f771ec156455173bf7ada7620b114e79ed49e76d7faf2132199ed1275d960323470bbbfcbdc47f25510880fd7b2aebbf1fdbbe4a5660413f31c3c1b4066fec843d2e91819802822b0b5860b91782402ced7edf57ce52208efccde9c8cf058611995df19c65c39eb07e9527a731103073c959f43919ee4d8ecd4c46acee30e16b4c461e21f3a25775519e315fc487b40da4c2ae8064476aa4c003991dafa41021790882a3d0b4537c8c8c4bdacae02e9feb5183af373984b7dd69efc43731fd83853651eac27b22124b7619848980a094bd133e87f160f79becc0c635c7d6156c3b66833eeda31c65a8f5e851a5c7f829991ddf7865fbc882af0d62e026a6f0b3a16068d52fe6510529759370a4869e358f31431c5b7686d5656d9c1480d8e7e1f658334d39c0bc3c8a07da9a848a21abcdfc63360866e7600b09eb7d265237503aa751b449c0e236216568c692c51a2426a0826c5ba3a7bcae47b50a167cbf9e862ce9f97e9187d1e110ee7897975bd89d2cedee6b599e023e6a79c9632d5a8a8104aceda4da19012f59c1c46a10f2d1faaa1a2c641f551f50e619a389e264342d0d13694b37398e8a0b1fc0c0b8520c8c236422f353309f6d9eaa062f56443116a75f535a4c9fd3d392bbcb5382d611da0d439ff91121ee195b3dc15348eb424c5f66e7a7b5a9ed11341ab4268302ee99ab7bec930cff605137266c28e76c88f1f0aaaa3ac54e3d5b43bcf83f8bea5cc9573ecf1b08a671cc3c107ca3032b9828f0f1346511648f0baf1d09e3c705fce045aff0ba367d9f76a13ad651cb1a969c13e51c0b3606e4b62c9273df2c3b8427c21a58740d53224467d85c4964786b0ee221ed37f56263c5ae38c04e5a53ccab2c4acb9a7977af2e15c5e669ad857f49ef6710accfbc26b190bf0f54ceb349a040d46cb83e557cf30dceb99a2d5cc08a13139457288d0a1d911e1d80c7ec0e49f574e3e20549511d838e83b60cb8caaf7ca2e2507e4f7552de9c8eebee6bf0e3e2c5e562c11ac07f3fbb8e0b18798abb9f49dcd528c1620b2558182f049c0e823e12f772009388132c7d652d553c1cf880468f8586cbabead96a06011dab146a62b9ff5b4544ea8c2d4f20304be6a383e8255c3d55bedd8c563651d4ecaa4c5f7e1bbf4af8c067c46d4146e9f2b56dd19e8256895d443e6b231b1613f3c4158ca81e49edd327c248b18162ee177768914abc912dc10bfa06b6a5074aa1c83e31d0a4f5c950b5495dced31c2f97d5bf1c2f3f00725cdb596198d3f1375b0e3b00cd080038263db9c0b2435d356e0434d93feb56eaf5e0b1ea374f1e22817ca5a3cb2b7bf0480009b87510565794b41394101e1683d910d1b47f901f9607b1f2779ac7582a50bfaa21bf60e8838d08f6923fc2ae15ec2eb8f9d6518bd08e40996298736b238b0fd7cbbe92c8af04350f56f0e8716c34e91f0d3ee19b109e821e746447875c40a4d2fb2ec890e00c487a5a47c35b3c9481919c2993d38616590f13288e890248204024ae86564387bda820dbc7d0be2e1d5df29f05623b052a7d1e229584852b6e6b7fac420f027c5edf6774e28e157b2b743fde1a69a3795c3d023dac37d25923a6d18e061031ab30133158b0d3d2cdc7c6e7152cb52f450c1eef67608e39809ac4e39f7e991492c59226daf5d9a0636a09843c3b4f4fd9e31f65106679dab1f947196a0b75c693cc54e7fd504a27c993980e941291727e64556f6702f2c64bedb8b51e5574a6d518f455da24d6adbda0eaf65d6710eb710037c40e69931f7787b8ed257daaadaa0da279a7edcbf232969c20a85733a28a19e73ca2f89309f514c6a26464249bc173be3051094632766593c5eba89126ddcf88842f5003e9837b84116720910eb72c63fc0cc8d008d4eed34d37d8da8aa900eaa7b09bf9dfc19012880bdbe1d1951e8fb66aba81bcdc2e462c10355125af42ffafffcc5440a4f40b289894803886fdf49eea25b77f95631d67da93096ef0ae72e0987e3ed04941c4971b2bf2e470774ad8e18df4ff8262bdc662efc64a8dbc79747f8eb3ce0d4f985c957a341b776033540f90e902c1716c0ce84b3bdfe658c57695989a341c99d538953b93a03bdd2c2ae22e042cdf11800d4c4b11d1ac81b4e1090693e9fcd0dc44e83b39f90350d2549647d5714788b6f728c764773c3f0041214d01f335038f5219131b303d5642d38028e1ad9930daee4929c954da95f9a318ea87943c38b751c4c02d5e487a79bd07f19ca9586240bbc616d103bd64fbef9585da778f96a74a245d96b20780e7018bdd81fcdc8e4855d83d3f7b334c7ba88c52bd3db28b4e53e56e2615de440d7c34e8dfca153438371cb9e3512abb1def04e9644aeebc90d1b903590e8839f18e66fb2063c9ff7e7b8874421c9dcd22d12c2fd44e89ebf9bd2ff885c645f46156167ab1a6b9b1df0e3471706711e9e06d62bb091957f61d50f59fef35033f5befa09f74c87686e8b2210314cb74b06623d32fe34f935378e8bc4cb0d061263cef69e6e1b0adcb1ccf67d20ba4619a342dd1ae8e2f8426cd4ad23f53d27f4e128a7f6ea96074e82a823a54761ef9761079538cffdbb4598ef0ae380e1cad0a344ce05d44ef7063b1e33c54d912b3007c72431157fec3bf6684a9776d182c6c6279cdcc3f194704812309c60a6652e39acec1890ea7e5360b84fe13143dd1b1d8c0d46a37145b77b021ee6f4034fa10dcfcda937a418e4e40c6efc77a4fb41e02101f13aa3751f0f5a0bfaec91e193f85ee6ab5b04dd6964a8453cc9ee16c375947c0a8bc3ebc2f51c7e61d050af1973f748abcd5d9459b9f3835a0047265c245d232061b8ec7ccf35f3399c6b49b44b77f64b29f8ea26ddcdb70f615f0a457b7f8414997ba37ecd045976b811ce56fa647f1b1ace40f53187b4272c00c35b36fec0e180a4079d4a7e41ffa6ac190178936c8527e3a6142ec242ce829ed2fec8ce0e9acb958dabdd75ba9424b9b86e90e6363489d9f80776546ebbfef7d059a9c3c87acbe82c81ce9c9ed46a7e9142ce1f69edc5b0a2752602d5e9520463f8e0b434a41098c1e5ac463fafe52d3840e7ebede48a91d2a3bcdae71df9328619006d662c8b7b707f12da520cdc3b559d3c85ac400cb4b15fe9e346a9307dd160b2a47827d6f210fd12bdcca2d4aee821bc1a38b0189be7afb3ebcdd1717a895f28b9faa9260826e60d2afe876e7cdbddf067f589c27ff9ed34d17f7f23a351e9f8a6bef273657fd22988955d301a37e555a2df38c831c2bad254153437c4ce0517d447227ae44e3d1c74a87756acf6249d156ebbf3c2bd3dce821e1659b21cbe82018aa138ccbb8a86c9e333fe423408743d434eb58186669a6e1504175b51eb830eb7fef7dbc145aaff16e02065d3ffd86aa083d17f616420345d23535fb03e9fe3b120a6e85f090de1274812cd303d5cd6c6abd5bf062d64db45d922115063884a68f807391d793eb3f05261e557358d0581b0c0edd4b3fd66e727eb87c43f246e75243e184e899bc042a818a0fcffe04f9e49ec6ca015d56ed5a4ee69f1f39e4d1cb87bf93fd9984f0d251812f923aa4fd8149ca01b6ffc60019f526bf1bee1c5e85e289e006b4bb039296a17e6df892f5ed44c9a6df13bb43165670b2f9f7c382fc56561e7de759e9865d3a1f7e7b401d6abd953778b85092bd01a679405efc1efb3eccfcc44c846651dfa1aaf9855c7d2cdff1bc22565f06d420c41b2ed0c45c6ba0b7a5d668867860f2a5c2cf06600b23a6bfa720ab2217ddc68602a903511cb5906754a7d7628ba1a32bacc74da9ebda78547f12f8690f1da9c66a4663cc7c44df6a5c0a88e6ea8ab9d9cf2400713f6be0626c4d2836d059399183386c6c48142fbda4bd152f044a21f8fec017cb618c8f93c7ab937937841722cd63399cd2983dd2946fc5fe9ec197253b1f37504359429e18d228b5db0f96d52cef9d7ce2df17c690f9eadcfbc7e45d35a2dcee7e9761f8ce3a290b9474d36efa772bf530c73fc95aa8c17e3d0b352421c922ed224a2a6920e75d542c533ac2d6c1ee671a6e0dd0577130ea5e3e7dbbbc1db32f10146b8ee08d96e26fb6ef91c551da6ec965c55ffc24f66aada2a6cfc3d62988258bdf5e89cc1204149eb1f098214aa6c2e9a44361c59efed0eb7ae6845297464e48bc43483d454325b71a864fac21a21badd9eba5c02eaa479690c1056cc9c2bfabe7ee04d56339828b2c91ff8f10e891f38cc40dd5aa8d9ccb53bdd5afe5f4201742802da600f8d39c95ce0f7ce8ce4ff2f02e4145a99432797168357671e013314a890b82de60b3d112b9d1fa9c39b6da7bf2e701b15b256aca9f41493e7ef4145e7f894a460a1d612487fb6a03fd3c248d8280e335577509db089926dc349c6812953aa8ee4da17ba88782e02e4cc9a2f963a276d43c33e21ab9aa02f95032dc4fbe3f1469d1854f3ee15de5f1d2ebcb2f9c234a20cd809767b7200d02906f5cde78ff5215b18e18029a86cabf50a990dce76832be5fc539a46050b8eb428be66bf99756f215dbfca95f8a5de9717a7689819d8c0644fc47dda24c29e9fd565498d438cd9e5a7d857164563dc406739221acb77a2d9f07e7455d0a1b798db70df7f585688c742f6e97c9ec5d09d23fd9d722e726fbe7a2761ae7edfecdfd783f7399ae647ab98c1094bcff16d93809e1c91dd600d005739c8633c7bafbca469b13061156ad0771a09a3ff3ddc45205026532b10515351e0dbae52c83def031bdfce1a140b3da25e844e827fbfa48b1dfe3f4da5f2bc827e924227a9383341ddf3b7eda5b876a627465242602e760b4c20eac41727a920d5462b98e2a961cc318cad5aa51981a686b19eb7d5364cbd35e8194401a385c0266b5c0a216c32a6d5f902a73cfc3154306b71447e59cdef66115d2eb29aa5c85d6793dedeea401ed75660c501251b2364da40bd6e648b475f7a8100f745d056c1684879b965ec8e18c96bf7a0b2277dda53a9053c7401255d9a01dacf69a494a2adbcf775083680dda40d279ef3aafa34600f90758634d88366173532e1b7198e77b95ebaa2efb2f536805fca5615c69d05b0e558844b266ab8cb6d8c57f1904096655a4b33a8b6018063725bcb5e236b6b0588203b18a517c0c97a80c36550776d9faf1074cc838dce8485d1f656a062a362f23ed65eb25c0d31e1087d1781173e8d0b6c8888fc660b780ff106d6a498ea11e0c7ba2cd307dcaab2b44d0c60935bc4c50062379d815449eaf4488131c87264378ebb3e47646a89ee96b2ea2bbe592b6db74759205a76c2c68348a0707b83a6000d069876471ec003a5befb6b30c17db3349e2679c772c71cbe2f55f625bc8ef6dae852add7a792574a42018466626cebb33cfb3bb6caaef26fb48f0df9d0dcadca602b7c2994677c2d60609f2cd734d0f9bb9648324d09d8e25cd400f7de323862ddca15e6f7c45e983fbc3ab4e53aa1663de5400ca1f2cf25e8bbad01ea96f1adbdbd63444f1b121de7a8affa3a9b9b4311dc71378c798e463c61d12096b6a37fb7555be1d1946c48ce5bbe6dfc5612382b7bfad2ecf40d79966197764ebeae836800f8801609ec1ad12274a83eb67b4ffa982c81fcf23f8e4bb90d02ac2285367436912cdc219540093013dfe13b7cc7ef46587baf50e93f027b4e9d25e4374d523f14df64fdb53b3a09d66b957220924dc35c608d05f62a99cf89e700a22c24c76dcdd1ce5c3654a0aace27bcd4603e8846ef65a5338a68e10bda87de0c43fa9d30c436fd29c0bfc0e3fe6227b3b45034fdab38abee8775a5a1eedcb4fcedbff80b00d5f0a08280b498c9795bea3c068bf685ab857839f202da50cc97d3f641a51b06906b7da088682169d5f1812ad4b9ffcf2d2739d4d3a3b55a9d010859da4565ce9a5bd2386862263b6621d08905b460911965d09ddaf909385c87aa9b9bd34ad692352c6ca89edc4a13f43cfca9c520cb3a27b440aef04ea5241bf843c0c1ae952baf6037ac9a506d52788bff134b2ea2d6789929286a861d2c77c61344a3b84dbabc71d5dec8878710de8b1ac2af687110a013d03e26511cd4391390f6ab7f63e5f878884642c7bcfb2cc4f26d471044b238dd7f2cac980ac2b4c8b4176f6cc5c7ff910dc677ffd7459213e57a3c72f8ef2e9301eb8e37aed3af115cd16279bdfc9ae09ff8cf00091b456513cadc76e496adaa8577b167b5ab0d4f82d1b16a61c0108e90572afb1a95034391d96f96d3385e4626163ab63399de73703f26d9acae8ed54bd676a1c0279c4c57032134895db348405b9066a77d283ff0c2989e2db11b93102d38df39037de937c321cab407a72392137b3e04f9eef67f75fad531537bdfbdf5287dcb439ef5d917b16e5c94eb1b2e4196308d7aead8ad47dab231ae22240e11467cb47c6ea8df4be4f5fbfc70909178af74ea13ac6afccbd9e9481a1f8469a922b3c293beb39eb35ca7789f3c555b9cad872478a44945632fdabe18bb4149552143dba7a99fe4848b86ad6e1b0e891aeef6a9789f5fdee0f969027b80d0961555ee1955e2759e27e60dec71d4c48e9ed093a54a403caa529eabea543d59fef32b912687e7ecc88a78f22cec82cd85aaa99089b97adf350ad9baa5cf68577c4e5c68a36ebc40a0e5aef4b84dbf74138e3b376313046a06108f00622c1ba23f60099e0dac26150358ecfe19f7488a83e87ade027170bffcdaa24e72106c237c460e5f5d4127851a78696e36a660b79e879238a0ae416d6072297813b6e9f000370bf7a0cd35851028e8dbcc56f7da9ae80ee962387fc9bd18ecb7e3c883a55ecd7301c84bcd78799e3f4bcb0375f11be5b95c0b156159a1114302c46b1bffb00e45c0c6539c0e99cdf3cc3a5923b9a0df5db6e33fb912c6b20a0430d2ae7a53e8de4d037f8934c7385121eca6a93cf7bd23cc23b1f3465088029641faa6149560047ae64ba13971a796ea8ffd4002d1430ad4c1eec2f3cc9163f90877983fc0903bc221fe83d8cc38689f4eae760d3478576b7bcfb547f1ced853761e39f11bd11e9add0381a3bd6a3ea33053aa546d5f97ac0e7b2c32f478757555e6e624c0f70fce68e2dad77535bcae23fc3f762a90cf5700e56d1d635a89ad05de026f8c207b03c981fc63fda06e152085350e022cb8c42e868ca493204e020b8c724aef8ceb38bbcc96660cde181cf61861c5eaa2c143b0bcf7245f5568caad4d36a33674f19f06134d178f60cd3f09e5ac4bfb9056f88da9d5abc106552899c2763488fc1adb63ec8eb90359951d6c7912ed269b8113a1642e44904c01914eab8dc14cc04570822a84f29121418a26f5ab1306c9690706a8a75314f66ce9493022e770c11d21bf41b0f8e8b3902a2f71d8fc6438f9517af41316db982015c7d1e219a507d1e5011c17929b490239883d5c6e514a3ef1e58ed23a7fa2bdc3914568669307cfb47c3012e8019e16a6b4820bf8bd8b56a8eb8b500914540eb6054f68e2b52d1eaf1e71b7158d3c62810ad72b1183d202f8ec82e587ed981f9c2386d33a3a665bbac49f8962da749df751965add5bb28e25d5915fad5ff22402cd07d73bc5ee108bdbe05236721baa8337b7617cd4858f616490fa0db16b8c6ca1c9b9ebecb186b07b067478d521779e2d2f26491eaf9a33d9eb9e4f888ae3189c2cd3c11744b36ff91e613fe1ceff5dd10b421b8ccdf4bb079811ad26715bbaa0b7693194c6ca611c4c464ba3f58220ac9eaa8f8f2802a20cbcb415884b212d685dbdfba7d1745bc8263681752bf1319e4866b1dc6d9f59a3191eb89114dcbb81c7c7e789d6945dacb136e58d6cbc32eec9ce59dad6e78f2d3d4a160d165eae2bb3257c9d7646c0cc1cd30c50f2e1c4d41e20129e5dd93cfc3c536006e86ede04eaa6db2e24cb5bfc24634c8cd9b9dbd3a06f9d8ff9f08b2f428bd89b7b0d8080582de713103d110246c86e84ffa922b27ed9ead21b95087d45caea68f257fa982f10fe63c693772277ec09478803b9efe77a64865b448cd26e9427a5d4457b4537792ff2c6e07254fa4ae0b661db7f073b0097f4af43183c5964a1dfd7a76d26418484acab77da1377f56a53dd8c96a7ee82166070030721dc9eb7000ff47cdfda023c88051e196082c6314706a344314e59fa9ed5cf3661ba9f689c2ceb3738962b4d529e8d5e20ba148f97126055bdf6ce3f717db1d3d789f33588cabb349057833af6f91ccd85ae25a8f6c63430524a4c385a78124ec721d2474fe0097f1eb42821f3c9037b11115f3ca07c9f1f860e13e4ba5f0aac9a21cc0c6661580728eb44b2bc0357906ed5e3a1ad564831cf436be1283c8007182bf8b418abe743767a34fb782194be5de3ea4de736ad990f4fa5991e50efe181c480d08d7de955807d9025889888fd869e57f150bd9b5142ce518cdc410bb6c62acec3c35baf7472de195ca475be0873784326dc63fbbe7b8398230f9e107d189449b4aadefd3577010125ecc21b6ad78702ef84c2662d452c476fadccd6fb44dd399fa81ac844578376063aa366ed9497f000954c0deebe0641197709192a042ff698b243153af854b17a22fd500a82bb201488917e41b71bca16c9682af7607acce1a75a72a68c97caf40becd42fff2611a2778a95a3200c5fa39478fcf364d17334dca63d5ae760d81dcfa0f5c999f0e2484be1558b2703a32c629a386009e251a95af4cdc3a6eb02d35c2dd03668a85e8a5797025220c9bcf3c031efc2d7e4ee9ca03da7c1a5a6b3079243686c612245595f5ead531c3c3516aa8d6061c709c30aadbde023432de4ec2b76b313ba934b394781142e2bb915aeda9fc1c0dc1c8f3223446fb89817e60c0c267776e7301f4a0a0fa51e03ee391d50c54e2e43cd0efa82e17a960b19944d61b376c706fbe4b50b92358eee99cb6a82b0641567930810aa4842ef7a7e438f5e3ba294778f67198f42419fbe8c26151372f25ab00f85b670f080f5919d3b503f21484ca3363dc4bbcbee8b5ec7b3a9850c805f280f011e8f29dc1e68f4b002c24b3b3eb0bb38a3c8bff890465d240a4c001a015cd0412ac28fcb642f4784763056e4c8f5ba5bc8bda34e2c7fd9fa58cff62202d37e0ffcd71c5123227f13e27b6a03d7a74bb0ea96024fe042cdb8aeea33d7f540170bf44c7f50be10b01718da5a8a2b1389bfdea0d26bc51c08d8179488af16e1b36147340181581b63e9a50a267cca4a8b8fb8d0358fea1ba5bfc721deb51223c48c0312bb8b3f836eb7411d660296104d20b827a501a741c48a67ad8d50862f0e62958fa3757d2e79131b98f92e4ce123868a442e8d8c76f6ee0e6f127fa584d2e99630248b102257429e38272e463601a30602f62576405b97e9d8d9d99201435bba6a237b6baca52f1d26637100a5eb847db4f55b0c0767ebcdd727c0b1c9a2f5808a51dc92ce6b053a829f27482ce6c9f2ce969c8cb748a632628565d75ec6c15e816c5e8e27065ef3687342dd52bc8e9bd283ecbc738f7ffe96731b5b57d0c59fd364ef6dfcef89e85013821556d47cd0600f4ade602378d0dfdaa687a0ab3690f1884ef788c6d6f00cec61e5666674010dabb756f007fcf1f7abbc072766a17aad87e9b327da9db33deb40472c4ee41b8e89e557ba7e068b0e378e78a0154051adce22946220b927b9442be6e7ae93a398dce63c71c4f9543913bc53d2ed38d76d61da9c05fd7ad9c1bd828092afd68a0ddc1ce60994af18e543d3a1dfa8534a0ff54cb00fa9bccab58702e580173be834564e99f45d64b6690d15d76b6c184e3772415f21a5de32d970ae88de984d2107931688985348ca2000d51187ec6dac60407142f8db6a1b521a67b157602a16243acc20f1203099c88b0e2232580df70f7a1629fc385b353d76268c600ed326981e5b05c29e21084a9109f72c3c4120aa6821f7e395adf27c2fd829b62c50348fed84ddb62c757c275fd9a8f488957a5bdd47975f32f6626a5033b26fb74e8090faf216080ac18ed5e40b2c6c8a60d05c0d4b94693bc9c2c424014b274585dbde043bf2d2191ee640b7e206648213245dfdfa12b80f716cf8a78d148ccd4fe486677c7c58cd9b7ecd12b4987b614596d6e5a901685b8a5462db9983bf1108d536cffcb7212475e981459d38b61cd1734fed42b45e5e924392e3c8b6071b172271851e393421ab2916db94de067455c0382be39f02764d9e333ff8ef5074e0729dd2735f2ebb34e68aaf8b5d1d20319d08a4cef63f2ac51681d4f4a64902742a20c5c87b367ebe2ee6426a4b4ccc88d0d2ac8e701540b89c8ed97d5fefb4d97e0c5e93f69773a7c0152701146fcf7e64680496aa0210c24d58c2d1895f244a78b58239576e18f8036356af0cd2c36d153845d2f59a5f34e588b44502126fc437870f7436f72b15d2a199d6be336fef548bc528ca7d9a659094f3e4f3f8a2201b966b2af12e28cb4519fb733cb566b5b563d1a543b8c322ca0752567bc925272b9c829aa8f9bd206a5d15a6c3e18c807a38a2740812e06563fd7d2ebb5fefbf5a8f358fa2456301f93daeb52bdd3d59342cce5da9c1294173d23a1bf8cda80d96cd7f73d40e06e23b6f6dc97db28237635203747a31817910e940ea93e3d522d88052b14bb60e91ecd1921ad362e04165fbe4f716f49e46a7727456b45a8772f9724b728ceafc7d90c8daa49fe3ec9fc938eefcec9b8df7627eaebbbb95feb248f94a18cb6c8f1cef3744488b3fc1e15182f21ee0825bcf5f2ea3d15b180d75375811cde4f894f84b877f48ec83d87d8c884c8352f788e5ccf0003d96b3f07be5327687a9189efc5105c42f8912510c4c7d73feecc57ba7640bd9336b6294eff149c829fb44635c1e74e31d691032708c5c7d91b8be287022002ee86485dcc148c5eeb69f5fee6225f1a4c706df8719f02961031db4eea799ed96787a849a8fdc54320ae86c733d01f41179ccc15388cb3f08c8a407a0bae3a9919206b4b14a64644dfd2dd3dbec8a7fa0c3a0a62b02f6bdc9e5e5e9af68ee6451688846a15961d83de24d55c5a489f3e06c849fe88bf3991ea592bf491aa39584f4317ccffc5edfa415a2b3fd74d9e9a31ab220bf5528ca402519086a72098ec37c701f15b59e869d4f75fda7d89da761e853795d07aa513631bfdf7c44b021a4f1e78f0211fea64470d64e5e1e778249789bac9fb3fa951e19f3cc70a16571cd8092adbff17e562b5447e1767cfd22bede0ff8d3cd60c477559a3a7e0015408540591b268b6f36b9e3f3c76a51cb5be143f4b577829e229b05dac5ad71a2d2051ea8301fdbb4be2a9ee30bcfb7d0004949851e3a8afecb5d5cc01c70b8700c80ff183bb425fa523cae5c3a1aec8ceda0cdac4973f37d158da8435547f6d6d7bac2be30d6b479d1d4dbf807227865a3e45f02f9caeb051c42604addb6b40f877cc795f6b5b4ea2c3f49e9246d2b08d1c76c4dad7f4197a8a9a94d68aa01fb21aba901e52fdeea723fc8ee43b51b2c2358c5a7d9250044b578503f7bc1dcb0086946eb68a673159a39d6ea5654d74ed032932cd26156225cebba0966cc385bb594ac4271ba9003df73811ee2fde1af1725b2b542cffdcc31e3b4abff7fccd10698661c8c204147de04eacd3c3bcc8f3b8de5f612b98c92f0225a97e82d68bc964a54fc134285266c6daba38c2785ba92a0535a1231a6087bc69018872181f6ac92def6cc127e829aaefd99ea7d6ff175cf396184ce7f1d25c2793cbec4dbb55854924101b2ba2553ede9e6fd83ca134f96eaddda123971f559431bce4d2c794107a0ed675cde3768609e8eb72722bb6f81d4ad247d9fcf8a0474eb194951838a97522e25d92def61ef945d3247f3850699a216b5433c5d8920ff28cd6ebd40490629323d0b34bcf75a7acda8de9af8bd85559424fe8d476afe7757853708fb0786ff1c22c262773a599cf9e6ce352e58af51e32f35d1e111c05297e400467028b90736d3dc6f747bdf6359f02db71ae562ca4e7392c336b1e15382805989591d42cd7bbbcc35e286ee684fffa37b8a693747c39334ea3c0df5618f500012e90c5b82ddb732b03188d6a66befbaf788228bcc9bc6ea0380dd984c67314f7bff63420458cd7b3da463195ce6c2dec2e251fb9b796d0e89674ea20e0e63049c607961a65d0edb440aa68004fb804d0b70597b0e1c961793ed0c2cec865635aae534e2c197325cec05b8e71866ec4feb3b0ef4d7c36414997b80dc0db4e45f5ca29c940384a697bf534b235e7f8a4ecf14728d578c185eb294e7687ed667c16b0760439be350d9ee4f5ff9d0856a7e9bc478c1d3129426233c584566c14e38f0d8815018bbaf6d053dd45cbb779d4a73e570189c2dcdab92e69f76bcbea86dc6e92907c6f88001394f09045361bd5e509f5cefe021da32f0137ad42b6aa199bbf9742f47e261a5fa7020e29e470b12058f4331c7bf687e283827fb2a6e4ac115fbd5a95676692db9e4ae8e250c2ab9db1920f413e05c096b299bec7e81fafbff0fa6ab8b56209531545b66746d2792affb87cd42dc80c8a72ba90713af9d80c24b92d47552c71c6fa8262a2f44c0c02e0aad080231a514c6ace5f25e317dfb83aaeb7b654d799993a86d59be5701c945b5ddc78fbe45f6d1d0b57a9bcc4496fedfd38a63e8574f47afa6852f3ef66f7dcefb83c931aca5eac927ba65152089344f2408a1d1be006312031e16af83c90fcd35ab67a6fb8fc0ed1845138865d9941c531f9d7c4d56ec91a5d5760569ac168ccc8fecb292cf46c4a734fe828a1289cc40c5d2600ddac79bdaee83103836f149a5a1e44ec3a061dac42492fee1b661c1289f2afd4eddebb95ce57a4245f1175140489ca8fdab6380facf700d57654859b34f75cff573f37e03fad33685b5686bea4387e883b9df46950878dfc6e53e4254266a05606b792f1f7ea04aca021f2e9cf227da7555f698488a2157c23105ba9b2a25a6819db67a29b51f4c653e49d48f32d71ae29da6f6e4923e12688492c5480531035edd9629aa8051a5ba4c84264c6264add081b8a242a3e09e5183bf65094aa3228dfdffb1166611b574253ace9eae3a73cc66e79a2526d4c6eef833a67da48d7afeccc27e592976048cfe17f5eb3c01cdd89648b6be45094c3c262829128e7833cb2a03a544dee8f18f2538fde0ce20200304e9d0be6c4dff13a94e009f6dadf2fcc0aeb93a68b50e7468dfe5133cdfa9c5f9c279a1e4c2b580228e93858439e5157190d9726577757e9c8c1f7bd0952b91a49dec34deae6e783a407a32ac707406a2fd19b6ecddc3ca53948939e82bbaae1a3e12936a564724afd132a5064df4371d6e72badb85cd75c509740e2f224720f0491b70191c6f06f4830ced4475579a2f0a59fdcd9b6a5608cf1c5828ae89ef447b0791afd1b16368865f3c13b036a87d817691394044a572f7e20a807866be9070e7df82353906d621dcfb177601e639f98c8eee22fd3460e666c2079a68c46a3acc7fec0a6196d7ed972b98c01a8b7c3b9069ec5c0284a0ed17b8633a2268835e9344ef675149693596e0cd3c5fb041d92b7f595cacb4574b2195b33df05a6c897cb577d2ec08f91e527990489cae0e8438897dd98f4c0517556cdd8c16b09e8649e1d323b6d7b1b3761c65919ecded12d1fff38d4093b42ba7f78f2fc9a3e56620c7fb447636930d3cb6ca314c5ad386204d4d13b1d94e6689d610fbc37b6c1822943aa9e93f31e9821bb101487f2e4b29c45b893626b6fd8b5b00aee6819202eece26cfde95623740c815551ca8473032c4fc02995324831adaed0038ebec1ca6f0233ffac6e4e5650b19e984a497e8d7fa73225f1bf5510734f7856197494c57254c4a73ae77adb962878a68b4f8ce94db361c914ee1a72ee2a5beb2cb1d55b2f676db2902682d850198bab57c812763e3dbe9e8bf5f7ded2792531acbc845a113ff45046a985752a832d659a452763ef914c821fd70562443c5f78a0531ef7c415750bd7b7f356a23d7f16fa5e4f4aa614bd4436ca227c506e7965f3c1fb1137d3be60fa9aa8e3efcaecdbb0fcaaa8f8e0da50dd3709abb1c0f739bc4ce03859d248c4a98fe36ebd7a6b93ec28ddad644963ea25ce4e7742fec619c02a63c3f63176d312a3f72cb7e75d49e51fbe9dcc137e51e0c3d822c258f9912d9eac2b35eb58c992ca2a31f54ecbcac308dc16a4d2199299b207fbe729f00ac7a0cd936c03d945ef477ee67f26a69ac4d1227a6235cb50f74247a982d5921d7e3478a2e4f6413bf0ea2a0833caecba4ca14122cc214e8216bceb122ef388375f0176e7dc4316c69fb1ff65f2250884aafdfe8f85132a5dda05c5493b4ac785601faa977cc60c8e5e327207a42827ad033c39862ec3489644533715a2684d97e65636219c30b9c4ba264bb2346d611f903061ec1e2a2ef9067ab46f005883df3cece51e972da772fb56795b6cbec263f8b24987ecd3997a258fe9939e03185aa07e1a15a3e739d591b023818c87485aaf146c6bb885ff138c318df0ec3a720ec5651680723893f65c8b82d5c82e2df2dc1763b1e1376c31cbbadd7d488f0877119ea8312e316af55826476de1c8e80fc08a0d780e27e3b4b0a254408539551290e0edd066cecc880db1706871c7624392f26d4b675f483a5023432117185cc1080e379814a46196260b9d95b51ec96844ced708546a814b1322f7fc2d759409feb404674a891ae81ed5c94acba868d4e6af0a4e2e032c5b149d5b87e558e530889907e401477ffe6ba97110a9df5ecebd70413a86da7fa2dad3c87c270886f6112df50eb01faaa91074fb20a819f76f8705294130b5925cda5e31bc13b0ce12d16c0ebc1d2f92654bfc043e0748a5e141058decc53a2cd0bde618d98b861a84ff0b439e89e4c8e049ff658b2d143689021abd5f9942c404f4d8544c528bdd759c5d6e5b0ac3b9b00298a70e053a031f672086c07368f9244d343e11e640461c712c5e98819110c7ab0915381839c10e690a589a1b5974d94e873a32c43fcac82440d2bffe64b284db365655891cc21fc3ed71883495bed0e484211131acb66a6951024cba75a6baa8e706ed59bdabe9fd439166ad80608a164f57e6e76a2204561276105901e7f7822665922deb420577388ba1b0907378dfb601e58abaa3f3f5351e32a21821c8ef6d89215eabf025885b53799b0c9797b64ea1bf632986dd247c0157ca0149ad4a8c1cf54f1aef738f3ccd8666fcb77e2715af1650d2a8dbfa821f0bda1c699ccab11df848bc2725ffdad4afe904b563e24340b92d4b14c429cec963b11dcdc8ea5e12d8b0da4a514d1870255c1644a4e7c632e6cbe80ce067abc15e0d0f1c827f60bd95acbab6304a5e8f5218546292d490827da8d4417ca734e75d519268e4df092226893b8fdb3e948cd27cd449202fc750e89f5f52ec0f68ba23371866e12f163f4962591065e12fd148f792f333d5272b7a987625a2b32f722a813880a1c7d7bb374578b531130db5b49891276907774cac400cb2b3989984f71ca0d523762656b6ab832a2e827a930ba761506a1d9900701114c52154e1c1dbc6aed5848f721b8658e106c76b8fa83e5928411370480f60be97b4b780355c08c1808f0bd4db5479fd5db91229b2c197471c68e71357f3e30bb33cfb2c1a0de2bb0ae8991e376db13a88df042406e315d4a22020b27c4b0a17b8589a091060f9b066f1e74b6bbbcce0b3bfd50399e8a38d95694c9c93a56d753e861a7a78c61dcfcbc0c9ef731bb8a0bba35bc5fd662828e5b5a979b6e57f5ce5dcdb550099fcd0f03535d1860f08f722413068bbf22dc07c2bc7e0a877a97c551bd26962a858a12bb7ce31a89bbb109624e32e65894736da21ffcc2791be1cd0ee7909a9440c2e2a94f983c3bc9194155a32bcb476d40816b3d0d801013602abe83521940ddb16a59d5237c43ceb81cb22e0e87db10f71b78e048375d091cf623a7a34f0d2d07d0f8e792953ad78b31560aa3c9ce53d15adb07b232d92dc7920f8179280640596a9e0d33970b039d2df6e8a6f10a5f971523cc9da3c8c909e1da330717f63baf38fa2127e8fd457bbb17bcbfe50a1f91489f866dc1b9d5ffb4307a6168201206c822989142a8cb4d2230ac75ec76f94937106926bfed6fa327ffc70689e07b97b3cb38a02d721c113030c392e2661a4e9dfa83496e9f3eccf741dc5dfa6e34bedf8e5170bbb802e4211c0f32c9ff270554512c906154019d5e56d286ac70cbdcd0e8990bf5ab6630bd1aeb666efaac1d29d1e555213d682bcbe7526e485c593249153b2b16843413a0e386b7bc4961b87b04019c6cf76443535b202a93b0e1d403118c7432f7efe260f451f4f6bafb6e6d6cf7598dc7678f453f68573de9405e996ee053a19336249712557a0bad6abd46682a666bea0bbb897f13f87827e07e91c0fb8914f2d82a1dc82a6fc948d62372b13fb0e5d9192b213095f3b1e09d97a647d903a7ef7548fc899898c69b6e29d4e8251b4b95a0454681d15c5d09a572099e48bdb8ebed7bc0f7b7df5ac185d1b61e04a1468eac78b9ee8f7c1a0b74ebf0d3a879251d9acec16ab4e247c69478953668acaf2ddbb9d8bb761417254d2f79a4670c4b0d1c9d1309978bcabbc438011322ea9b4e857e8661e0a13314abad57bff52f9bb58a1584a80f46e45cafb4aa300d3001f96e3e92b716e82e7c71590cbed53b2fd0600d19984d76e9e5101d2acf540336efe4047d2befe8c3e5d08caab6aef025604d72107a53329430214d303b2501703a18dce17a910bacf750e6a6a6a24f34cd62b128edb148ff212463d2f8fe0e77556f048008bddd0b2e5281b492603ce5e78c4f092d807d4b01c092acdef9718ac9d39175f4ca80e822090902ef95ad8c981c24f6cfd12154dcb88b41a0489c263a13eaf9d0a906fa1d1da5166584c9bd5008b036d9715e31473afa86a7b8edcc1ce7d6d19cfbab258596d988170dbec28f5c599d3bff54c473c36f950b2b2477d6b9e6d5d7432816526e9d53ae7f7c9522c2fbf018aac446602e1ff2f8940e32f361cba746ea84f599d96d8d3ae35e1aa244c1e596454c78c326bcf7a2d7d83872a3f7709ecb8daee1499dd64d2e2c1c56b379a28fb844ee53e6250211d20a3a853a1ab41a6e09da5761e1d92269d6ba8411c7634dccad9f040c4184079ac22a6e44a78e59661569a36535289a70f56e1caf1260ad81418815dd06dcf43e5071a93cbed4599b6f9b0ab1cb20004e3baae89091cd41e46141e3f7b8eec7796d3fb752505c2bdf9bfde0537e36eb24b13224bd3b0ffa0944146bec20f5588ff8894c702bff5f1ec34be1c4fc04750e1c17ff9d81268084db67682f720e3e04bc8cb869c2201923520f71dd44b8acbce21ef4b899054a7df40b6823623c0b2a0bd5ea84c6d0ccd6fde2dcdb32e1550c97c83ccecd7b622df3fbbad02b57e36398663b62f90a71abf06cbe3f336993b61c2015fa77cc806b0577dc27ba8f7f5dfc7208b8dec49f7fcfcc3d6206007ba6c6b53190b5f5d6b3dc93011d07f39f97248d2d49f3c20f480a8e70ebaead861aadfd451c5624ec66ba0c47c58d3204dd6d0c8315169121517e85b09505da58202d37c0b747312598424f060ed24bd82d3f8555355b7789aa0387b33a498b058c362146cff3544866c4150f21daeba8b6444c6497b30576f5e2c4741ec4fb1177e496dec37c87ac5c089f649f810322e7c766aceac5d5171b07e6d83369f84f23ab60c5d9af90fc1a8c44928678f3781a6f3c1819f410c14a1ec15d8f796bd8437f6f5f1c305773a5fa50fd060ca28647dd4ce08fbc1c187449513e07e5110be92fe2ca48d5b29ec3d165c1d2b55c0a1d90fe83091a6f136dfa32f56d6b16839458ec1a1dc4e11bb81d86c72464ed5f7bd55eb17610cd82216018c5c0bdae7944cf3b9b7f39a4ef12a95374e549d1e3997976443e80cdcd125416d72002fdca307871246569febf7912c7832666b62c08c67249b1c00280f55fec403fa01b3261ce9b1cb1f1c5bbb36885209704823af89e1a2993ad36439edfeebe2ae2e113ecac4094f1ac55f75e5018e1035343262e9c1ae99764e8c2b7922befd283904bf542a334294dfeae0316decdd445381a99f759127c69cc101c15bec5bf636a2be2db723fc86ca6960834f85e41ac962855b81a03c9424d45a66e184f6c0e04bc81b3440a8d2e438cd34de595ec657a46cb01fe07be7621df82bb2bc002f94bdc2a73a5056ac1d9c82eb7cf3e7aebed0906e2edb881fa833ea504033cc8918db0800e99fd672bbfa2a647c0ae90206b172cda3cfda27aa9a49e15166d3febc7903e9614a753ce184cbca426673af847fc87b4e591223ee5bcc74d0cc3eb92a44290e8498291e8aff2c181d25f926b9bdd575200ef0478ec94176801948ecd0aee48e4438e2182717537c0d935974fcbcbc7090a29d30a44e777f035fead86720964f6012e74f54bb7cd3832dbab7ae34376926e4c7e943303b778236c0666efc4b36983134b45af3a01419af44c0aa33b690e5727a36f1a65400464466e6234178455de875b8ba44993005b5ab4bec8c0b83c630dff3871467bfd0355c6b68fea8b38c5a6a89684e345c902bdab00f8544d9ebba7e8e9c8eaa0dc78f06a0dfe9fed50188d1f368adbc2cec3601270a79430e1d50a20fee725a707bab01160272e70e47b4eb3fb24f0e6a8d74a722a3c778b0a53331dd97d7a3b718bb476bc2fcc130a1399a30d3e815d794f8da62c59d49f07eebe3c1b9ae0acf50738cee1bd3744fb5352e23e11317b3beca2f49e4f11d65f2a1a0fcc909217a8436956f0b7fdb48a021dda6919109d3083bfc4ed83a53412040808d962423ff830d50d0302023d5134b05e0763f69f3b8f1ef34013deb00ef44c7e6901ddaf377609dedce29c8ba130eae21875ac1cd035cfc6105a6e292dca098a2810274c2a4e62b01ded3783020c3e2b210ebeec1271f0a6f0167aae11b899622dde23de668d784e7bd73e461fe0dc27d66fe74454cfe0487078ec621d8a3c669b204a68717858afaf084070fb3f8a91910eb119a1e34fd94067626a0e04b9d029b709b19ba554a685a3401afed8599af124c7fcd07a4de50ad6491395c7ebb3d88fe25eaf1743de166e989fc48cb40116ea16a2224a0f3bc781d92d1e50b1257c17c51f8e0e94c48e55882b725448b5c065a0ac3a406ad65aaf21841c9849319e6947be96ba533bb48e0cf3243a1e3dfea8f59ca829205e068e8eebb0d3ab291ea8e3cf4866c6e0fc7d3a1490f538e22b94a71f3e84fc8d680d5c3f131546e9b04e03ebd4b12775b987d936fae51103e25dc643ccf1e2d51eb21c04136e2ea50a041ea0e4aed7bb037e438bcdee0d01b488c1a771470ea1d585206f9fc897a50321600650397dc863e04760bc2b81873a0a483f9db4f8af538539c744805428326eef1345543d7aa1f87e8c9eec0e2e88861a79824e5fddbbb735adffa323b84a77d4d89fc7e3fc6ddc44395507742984127a205c612bf64230f715a640daeb38c5e2c10b83e0b67ba48063d91aeea29e3a0b3e54cd3480bce32488c9c9e96f7388ef64fca07b10fbcc64bac263cb5b243b7ad1df2344d644e3b8834a90ed7b3afc0470a9e89ceeebf41fd943e07e6f84f5a728cef09fd81cc0dce8b87ad8ff4d563187675a890e4470a23f6f575a5646ff93fb4e126507900dea2b5551b0896c9537356a7fecb18b87180c6f7aebc40140f6e81e3fafbb29c0581abb38132aff792f791c7f0ae6a5835cdae36afa8bb6927f523dedf25c3be409c9c29ab64b6cd4677f60e12260f327c657488caca5c05adf5c0f34bc546b17917e81b4df5dd4efc600e764bdf481ab180ccb9be8672b83e58409e1edf1f97620e4129970928f621974f6fb4a22e9314fd566a627d1751f825e096bc4d0fc7d346d4338f285271850efd4501f94600ae28dff496649a1e2c881b6822d6df7bb92eeb94eff88d69f5a29da6907b1708dbeef2a756b5b24ded74e57051f24b616f5cca667e5e13edd0f81703bd9b6957282db27d474a39db3885f151e742f1800172ff8bff78c9d94aed3b4664e71d18e720e7cb16d638b8cd4ee2d7d8d21b01eee3ea5da3b098af304e8b4155932e9fb05f34f0273b3de7b050065f1eb2bed938b67d651416cf145c6e027641815f5f83e22f92aabae23bfd8b7f9eb5ef4eb75d518f4e5660425cf2b20a3d8922dcdb351b04145bf5074e2d9652545d22d1eb9f5f310ac40b6f6f8cf9e8b44034f0eea3378c6ae97db72d6769690f848914878c01041fd9de4ee4f9e44b40e643dc0667ae557c1f8ee5226f4645b50b946e1d3a764f5f3649ebf6144bfa185756f0d95814518d2b45f598b5d3b0716733181cb11121816b08c5ca457dbcabc22c73136fb5af4a59a8b1016c9ed7500d296b3ebc194f3eb09a2aaaa6acfc1de88de0d6b21e130072c10714b4dba78b0d47a157f73ba36d57c1eb124a4528c9b736da9ee6d2bf26f6e18763f55c46d51ce7879cbeaaae1ee1555bb911aee03530e4840f2428b9902df6460a48451bb27c044aee30453693473ce887165b3332aa6b081f5f2a6ea583590e54a0709d6bf51e03dfdbe112046877ba61b6ee2f3e64e6a4833da6de84924d78d6fae1dde28e094ea9146c5661a2e12872481225c9be7f0821f3bbbffcbb4e5da7e691c41aeee38b34cbb5a53c6869abe246b9a8c7eead149dfaf89eac588a265b7d942cfcdc596d132870c87831c61aca5a54424add061689c4101b31667956a890967590ebc845c7e4dd485b0d747792ecc655c43e7ef4c6d7c1df9557947614b9409fa4fdbc6f6e90f8c532ca44248ce096602d9b9178ca1fbe107cd99c64afb2b722bc92737779f4b611a974c34ef4b09859ed173ad6b9ef54528b3fbff208fa50785491b2f4c8f00d468b08e12051b22ccebedcfe8a589f491dd567580e2ed55c16c43b485e04297d1ca2a134a0b4b2d6f5903e53efcad9176487c174d000515800cc52e1ba21daf582413d46621682a1124d97ec422e57f9ab5b56b6a2989a748d28ec1e20cc3c1d2e0b90acf41622c8c056b00071d3d41f11bc46b1e195a2b6cdb3d03d65def58ece01adfba01900807326d10aa97cbe0d389e8ed7cb0e096d8780f38f8731adde9a3aa4094aed13786f5d3e680b0b1f6378af7b5343343dd40c433d7a17be6550544d489019bdf2b3deaeebe5deb03fbab73679f249f59dd86a5b549ed9d571002bd73ed5d595b5e2736adb8a9f0201d2f7811b5d2a50ec716009d23a2b6b2bfdfd469c440c18a03e4c4973dcf5cd9363e335d910cf6e6f9aec9c6619559d57efafb8a83b10c303b8f19007775d3d677574d1695bf6ba5a6075bd401abd596dca75a2698c6d51d31bd16178f992c67a3ce6ef77b22e3ffff37f8c136282dca20f831d0aef6ee7d11f428c52ef150e4133fa85087bc007a7dfbcf7f074465ab3ae09b8674ae2febc394a383c71abe861e9508c7a63a213a3e1380a836cab8d0a55ab39286c420a3179e3306bb51c36549434e64dc3d982220cd90d4fa54245cacfed3c4c2c2c68f540570a7f9ddd8574f8b1ab6cb008a9311aac7ecc180935d05ec75909499128fe275b6930686307ece252b968f525f8551cb4d137977934f883a48904771d8f31f440ab553c93012eac86534bb0014f34b2d2c025b51282d2bcae4ec314084b510fbec3b221cc1e3c2e86f202ac3af2fb7569fc4096b70151d1d14600f8337ba3332581f033e801b47a7a4f531202e2f6b08d2875b85f2209f6bcebcf7017993c48aa49585907e24509aef45f4307f97585093601e4e0f8a066ea025561383b592c7e10d628b002c2b12f7f8f659df63ce1d2386ea1fcef6cffbde3d229af70e72efa39ec36f670107c95c7cb2582301ebdc28d5c1d58b40fbf848f469a10531375ef8c10d563ce68812b02910d8e30869712c7aac47e578c5c3337c73e85be024ee29e10cd557ad399156b1d297d9c80076c2fbcb01f1019d975bbec8f2068c6038888547d67e2174636883252349c0a3bee33e14cae0fad9a90874ea2a75a457daf63415200611664b2cc87b7c48e95a6502e1c25a2b8d89403f3630f6eeed8063e5ab1e894f793e1eb8fd1b8d2650275d1c711595c894f76adc1d7ac6a45b6117be04465943112a0085e48abe919bf1df062b3bdd4968c3ebc0da4815cda8f731175b725ff56ce500b266643dcdc2b6a5bde2fce5882b1000e2d738b0e093ad7377d4bf6712a658abc831b32c99de653843abfafd313650656a66b76daff865d4f2f381a1178067e2040ac1ec39d119b7ae95310860f581d2d5db3fd03bd99569b298fe893213cc19cff571afa89fb9eb8d7c5378850de2499c0dd74cf591bfc79a822fbff243d9e713ec865abf1f9548f80f9119707d52746af87ed51d9862d1516fc3116127166693bd421f931baf6ead4399766963a775a83bcfce31de5bf769eb5d6f0860620d4d30b662362f4c1c2ab54510ed5b5136026413f0a5a737cddbfc1bf41c40505a79db25de36fb12b2fd584bca3e5b03eb7319415b1c3399a5afbe23472e287cfd3756feda624e0e601274648076ca1c392dd381a957ffc39c9a2363b91f0a228ac9393db86e86bc3878c37c855dfe6462c607346f96959b40ba2820a8c98fe1af51bda538b75ab3811e23797bb79d4ce0c630347376aa189c7023024861bb02fa20167995a47e7185d998f63fb0d453d15c75a11cde803287ab242cf557256f9907e752dec64029cee1bfefd4b0231b65bf9c1dea53ca53feee8582f6f1eef0eafa672af8f828f4c6e113f7b11397372a9ccea9f2562ac110f2ee31a09874fb49313b7c5aa45131ccdac02752a8f6fb2fe0f42c97aba40afd68b45f7c6ef0a29901278d8e781b24e9b4696bffe7f29ca205188118d42e5896a53a16bb0f5f868d370fe4981cdcf17af489bd98efbe4d8f2d072d4be0537af061604d03d49892a17f17f0436c86767138f5ac8c7f65b2a982ad5ceba0b88326cc9e22f27dc4794f75c9fc1dc40c81a6fc841b5a5759955952e7c6979c3aa291cb4fe258cb68cee98e754e868456aacd4790ac0db399367b7cdcf50d05b1b58d99393b458fee04ab02b6e2bae6196ef41ef7ca50fab9257d5d2226e8f862acc2ee41c437d28a10d792d7dd51d323dbfba1a9746f52af09b9679e7794093943bc10b5867fa178009c63d9c76228f9d39d1f33c13a3221c867553f3de94ea1e257039f7d9f79294180b01a0ca119b53488dd97e8db07c28b51245e36f8529af15985dda5cd4a1b6a45af243b2262f4ecdb4fac8084c75afdc9f12e5dacf3de7181f553eef3cd6e962406cd09e8586f784c38c87222b0deb0074c54b86c712f504b7ff1c264d93dbb00463b0e600b7e488d66e34356d284c9a0a5de2028650ac21e97b9b89cfaca51d4f74d1b5ed8ae2dd2d3ab99748a16612a6d5c5e39e741a9cc922f5519616f6541e9c968fe60a7dedeb03f820e0ff05a72d74e0b96042d2c8fcacdd70648189ce320924ece1f8a9a8217c34e7dd6ee695a091e043bc13dba19e2f9dac85b11af1f069cea1daedc8efa19c27c95ef20ca132f50498b52ef4c851502bdb8b453e90802bbfc77b215b02c21ba88d8fa7700c50241fcaa7ea48e50ca31b7b1ed56325d1db59a5fb44918d803d3c5f9fe607ba51bb6a3c4420977c0884bbf830aec47f205fa0cb3732795148fbe2e2712227c97f50693932c5c27cc839a5aa027d711b3060281847ce578cfa87ecd64af82f32be58bfc50ef0c904d74b08d239dab314327a3978e041bf1515e82bcb83c0192174ba57e46f4ad19e99f12b418f8f547cf54e24a09aa0b1f9466b2a908c374e7ae5dcbb4e63f174e6a9bb9fe5e7032a108a0d2a3d3b85126cd58eb00a7b6be18bb8b2453391e06e39f7b0c632ca946f8a4842f4fc18ff3dc2f2c7309d588e56c743524e57246d87d42ac2f7dbcc05f0ea326597c7eab1e48c55e384e4b469393a2d90ec1c9d23a9868811251ec449dc55ed1422dfe8798903ec55a42898cb2c2a8b2548e82c2b9faa8999976680d5a802203d83375bfb482d319818a4c6ebdaa74dbeee2df1eec60fa9a6469ad8355fea0d1e7b2e7d06aef9cdb8365aa076e4500cc82d64efdbe40fd53143605eec4448e0f571058c86ad20100f9b3ca3ea3eac441f6277b7aa44737603283ccf433e37c5d599f6773c3079970e630e2dd22ab019e476cffc54a95bda793d273424534a9440f003532ffd80b13f2cbd737b326dda4a9b54d08d95bee1d6b0f5e0f780e615b1b863d817cd266cca152d2a96aa164249e929692383d31372757e50e9d2856cc35eaba9bbe56b14511927e1b914aae972e60ca151fe9962cad9c2894bd438443c032770af6e050d6eb5173c5dd0d5bfcba3b563c7aaf5e07cb63d83dbfba6b94c26fd908cb93933b4d841c044e10658b1804596104453788328611901085d31008babf5a84e908390cc34fc231cc9d4369402d68820533b0704a810b9a34b1200b1f30218919b82006a00d0d3e87d270a8c116431da8011754d8745451451526784107a6c0248733e0cf943895c3d228f5b0a50b29801254b4822650010a4b58c20c424005143570e205cd3994160127c80009464f10d2e24aad93e0842e9c3c6551a50456d4fa17fbb6bf29a80c429a27d883ca209c97b693e08f1e29aa90bfb76e212b4b2e96689059b96f847beab26f55794f79b5755a8c215c7daa5ec11f35a78e03478b49b2ea54f583e614b62d8e47f228411d80a44052128527b690e28322d4525775c7d457af208fd45bee435346306fb94454d968e83518acd3168f4059f51d2d1fbd6cedabdc71b53ef66c757bfb29775c6edf72e7754b0f5e9d898430d52365a568edeaf42fd7e54e8963ffba2d71ec5db74a1c6badb5bd7a833f6c5ee19c16e98f6a063e954a5910c77ea86eafc22a6c0155cea157e5d478e4bb558a296b8d745a461f438bf4334bf86a917ec49129fd70d803d1ef4e2f63318e16a9d79fdd8d67700e8c1e470eb5a11b08bb5048ca3c93bf3ab5c4276dbeb067df94dd2b7aa5146563a37a9f961782ad2b1bcf8930a76731f46c9d56c5d05c2c21e9085948820e9ed4e66194865305174ba07882c20a65d4e6ec1ff4a0361c876366898dbafb34b6360c61bf2889332f2fcd525229294ad2e65128164bda2c6d4ed3276d4698a6dcffe69c734e1e9bb9f4c2a4cd1c5c5ac93b7a72df46769169c8520736b3bf1d72a79fd3f2f14696af8794f5af6868b98c09696939506db55aad3ed2c8b57a8d5ca75e84ab6b8990ec11954d21716eb68adcb724983274ebabaf7092d6816a9648eec8f02db7257eb216b6b490ab9829ad65f5d68dd2c2db03a6960e73454c45930706c906499c2390884062b5be9770bac2173b793afc99e3e5c3c2f4c9338370032f727f2ed940e2f4398ebb85dc841203a30218a4b92433b96e2907304893a762182222a2c903535404c354b35a5171b34173452767832c930edb9943b44c26ed051f12a7ff8254fab12371a03383c4e943b9cd963c0851a9540a95e718fdaab790fb5ceaeecc3293d26e06157aa0041545b0e1a0d6e701098c600327829024a444addfb7febc44e0bc5083d2e18248838d29c21b51763324b3beba349d1855d775ddb9fb340f42d427949086bd57ab573dcffb3ceff3bc96951652ebe816d6ebc89dfeee7c51e9b418e108f3c4a85313da43f1eea30d8241a233d36098e68f1c4138c228794f3d85ad8761926098aef8e00514014aa98a1620f6300f544ee11084a30dbad9a7a6ace01f4eb55789a0a8c0d447445353535313c6797ad3fbbcc9d4b37a054beb8b56532b8c9e757779086bd9749aa8c53954369bc4e3b09981bde3b88eeb3a8eeb38aed5a2a19ab76710d27f34543d463886f6033344ea5b4fd1581c350954504585128e62b629299a3cd60332477f28db25451dd4e2903c31114d84ab5729c2ce33c806d528c2791c3deb0c53032967305fdec07c10f373a86772e79b386c27f5496873db72250a59594a580339f3c2974b87478b5d304c5726c7711c27aae6b431c60dd504234cb9a09a2157a108479dd10615116518264a8351a24a4c3048120841158b1725b9e315218f158b7c453632737d221c6194ecc7b964933dac4292381d0482708429b281a9d2a20d9d2d962da26c17f5cd096aca0a8e79a0320a734772a78bacc0acfa964e93ce6cd269d2d111a94e8e8e7dd7d9ee66a353391b1d1d1d1d1d1d175a72877e34cca5251a74c2dc5994ed3a510c80d2cca124e660c9769ded3a4b25db9b25529a3c304c3a3438b5260cd3e49964d0608d309b88085b4ead0dc3160ce6fa43516cc192ca6eb7db9cb7287355745bdde493959ed52a3deb190d2f067557e151278f2d108f39796cd94671bca5638843b4798b5cad5cadad560b061d30e8504d3042950d923bab5c3f10b6f48c9321f75bd69f991225cf4ce9285d94411e4e84a30dca73de25b164b953cf05c027d75402a8e1c753f10081c05e2d1aadeaea4ab1c1cb9cfd689bb81244e11f3673aa55dfa638729f73a9e8c6bd8b5a3ce2b89ef373be8fac0d6d9094f5bba2be75f2a7c3329c2b0e4f261c2d5a691146a96d61ad61965a34214afeb2e4c20a0ff268955ce4ac4b502471fa56b99a2ba743c39475188669e6e8cf261a266d8a412985619a3c210c5318b6885a54b46c72a7c108bf3cb66c6084311e5e9e4765b065933bab892d07c2712eb59cb4405093a85c8d41b5aa94564a6dd09cf574d24aa73d6adaca0a60b55addaecd5ec82d1bad42434fc2b1656b6a6da07275cea65c1bc9d5bab246b90829c7f552f70ed5eb7251b2dc6aa1d1eaac6b875aee50585b611b3404abadeeba4c6c6d75bb0149c3aa09b983c43ccf943b1cade546ac4fd759600f17f2c446aeb3eeeab2ce24a7b6faeb75ad07a40cd66683a86ce85a222abb81008adcaf4d842d5fbd655b0acad38adcc12171ba90339c25ee76ae4c84948656a86d1316092532477f355a0fe4b61fc87dbb012983d8446c128eb36987ce12a1114ab3422deb3711daa096d1a6951621b443422d062505f56c0a0df54c56235c9e67814080f2c444542dd25f99c2948ff45b4e5ad66fd986c2d106ad2e115466836cd06bad2d5b5b69f98aae7ec2509c477012a7656b7940e2b4a69039e46c39d15589d09669c7e94c9e0e8b9ca8234613a5b5528ee366586bb55d67bbce16d9a1a029863879e61232476ba085dc6f2122034cc25bcb36976eb75bceedd637fbb1a37476d5b2ad6e2ddb949e8d7329f7ac3b8ad233ae673487ca6690b559227bab2f1eaa6a8be44e92dcc1194649e996a768a26930482debc32039c930452ea092821598404aeed19620f73f1e2a3a7910d65b4869c7711dc7b55a2d1eaac983b01611f55015a19e9a28050325e4389825982a6084b1f999633cbadcb995c71d372be1289f6094e48ef7e9e3fb7e7cdf00becf86ef03f27d377c1f01be0f87ef83498299d2621f46a967a8f761aac81d982b5ac8198c0f6060946098a6088d54a6296098729f8824b3e9161aa979ec1b13a57551cbfa3058644fba325adda76f94b6c2304853e44e1289b3023933428a7d0e061391043d0c949e590c43d4b30e27e981ca44b428148eed7d7e35d81f8f47d783f3517fd0cc0de0b341e58103f8bc331f354d88895893c25d488a3d937b3f30474d9362df8729ea3a2170e60925e4fef950f9f051a56f72a79b51907a0a2afa8adc6f2992876ba52410043f15087e5781201e813e90876cf167ca21ecec22141287c01e496ec448e771823d3280271812677522ac0d43d8932d1cfb3679649e7defefbbea3b0bfcf98e907a7f3a812179a4cc91c2466693dcb172c73b0a85c7b0a50952b66c12a75b4e5a442d282d796c8540e6b04136c806d9265656c806515adf56371bd42d5b2ba945a56795b66c4ab7a5a34c572b9ce4881a4c93aaa70ce6a429140a758a6938f11c9a93abde94355993b591060e6d15bf8ae7508ba107240e513b31c5db13b96b5148aba5ab22330b99528fe33cc97d3eedd3def496660f72bfd669148ef3ca6d125199076c5584e3c44265761fe7136809c2eea6d24a1e41c484127ea1a4f37de0cfbc5648164cf60ac9c2caa744850a95fc8142b250aa18357b207766cf2a0a754f65d356ab573dceab3d44e007da3b27869e7557125b67a9c58b7426752d95c1d0e911714410f388499b9e53ce164d79f24c25c81c5308120a390b16891384a21095c789050ba54d9b0d4b4b9e362a55aacc398564c19445952caab428b3902caa5461a2d30abd42b260aa5484748962215930a166e02441c8dd86a94599ab5099debc902ca8f46c52b152a569d3d6b256ea9664c7948515a52ca86441258ba469cb426962215950913b730e654a049bcd668332b1a0b04cdb1c32cea13c87e6afaab66c9e62a71a117cd266f45e9ecbe5f25c2ed7abd65a5fb76b495cafdcc7b9bca0aacbe582f90baaba5c2e17afebbcae0b29ed87a62c73badcaec1dc594319bdee7afdc575174ee2ba0b5ea9264542bafe4396d9f4b95ac9a576a7b490c2bae7b85aa967d6f3ace7b98c5c7fb9dcd80b2779dd05d303d55a5661d7a5dea5509de7e170e6a0216de6602e519948653998b417aaca75bbd66395bb8e48e2f40bf389ccd19f4da4e041a7aa5d57bbaee7d4a16bb27b7bb57ab5765dd7553abbead5ae1bd2f11167978f61def1fa088b79bd5e1f696479dce1593f5025c11ea9a698f79703d55c37561fde9959b95b402229eb83ab5527e455af1097cbe5250b2255cb7ff8c096b1835a0a233373e14ae5bab2f6fa9aaaacfd3e4e1573b9bc5abd5a29a54a61f78faac2aeabc89daa85a4757761b404511bcd1fa8ac6b7406a148444522715acf7a5eadb513296d918a1dacd6ea59afce5e3e18555d1d955dbc560bfc59fd43ddcedf8beae5be56f769973b6b3017e6ae2be4f217b083b9495cf641a618be257cad8294a1ef7259ad7f9f8c6a82e0e7edac0bcc4b07f3020373180ccc5f6e4c08ec2f87bdc0ccea3f14aaaba8d7a35cff813fb53beaaa9a41f0678e221197fa17a3eaa8777b676edd7166619c57c89dea25a0a7f250959bb3d6baf2260fad972eaf7731920657cfd06bae8f3459ba3ed278904e6866994def195d0ab19c39eacb953557e7ae76ddacb9ee90adb95c770651d990c46932c5e640ee8fafcc34ef5c22f6e272a0dae7725f37f612c45f3726e4e5afbfe01c2a6b172ca96ce21cbae6ba0b07218290ce8a2518c5249a3c1d947b62e91498603609fad6449ed775dd41f0a75bfd5057e0ed67387f90324b876b28d4dda44c8bc38961547892329905f014246532e7489e8ed64a2d3a72775733475340cafa6454e10722224a54bdda433d1b5799a79b514a8389394db9678ef98fa6464551f97565ed06e9a19f6bcd3d806ed9dacf0bdebbab87f9823fdc5daeeb5429b4e7012a2571adde577ae6bd065d4c88ebe5ae03d55e37e68293b8fc75a0da0abc2f772edd97c3e0b98463dd6796bbdc981098bb1c064f261cebfe82a71395c9621dcea16b2e77c1b389ca642db3d02cc2a69f9408c25166f9a10859f58f86cae51999b779eb5f57d6e8c4446a2bf552cbba6930c5ae5684632bf9e0049906e128445131ea51d875f65ee759ef0757964ea43458cbe6cfc995c7a4c309e4e95154bca3310460471d826403d37823c728b5a21c32a7e0be6591fb7585e507deae798165ca1a9db5a3375a458777cc1cddd51d58764c9e4965937663eec87dae8394481c39db03b2974c59575507c91d2ad48492308cc11e43c7e42c007248389d9ea6d3132b8600ec6831784c61149516abf44c7228a51671b4d897d9a2a8a0a8f4ccc3a8282d2af52c858d6894862222421d115927a22c7d7084254f6c000a4c388941ad8f1aa2341c49c4800a50ac18e94004b5be75b24f9e5038338ddfb8046f9899c69bc8ff0c637884b20b2b64c938b2ecc20a54b2159e32be8c15a2d830730d9a472180f2cb696c646ee3e26bcf6e2e0241dfba0daeb74eafeba336d473b22f173bbd2d2cc4ccb1cbdc3a07440c17f92189e4d679cc3c7699391c66f04824738fe1d6633c344ecfc8f437637451852fba980296faef9be08f99732e1c5aa742ccec7ae5ca62bdfe635deeae3b73fec1ba34f6238665d3cfdc31f61be7402032779d037dcc2c73fafafff338900056450930b30ceb2edc9222646122af5f8238c89c622064eec240dc9cdea76397c88b8589bcb2cd2b1e89bc2868038d1f37b8b668831f738a471b58b628beb6287237bcdcf0729abfdcf1884cc33dd3d07c82378cacd71e72eb16bc81669a09de307267d1fc82472332cd636ec11b3ad39cdec09d855b788cf9119a692e411e343ed61a7804ca34b8c66328f8a3738dd35b43e6158f5fccad9f61cceb6d4911c61c99991ea9995ee50985311b667ef9cc2fe7f0cd278efda071ee34648090394ee140e358ca1cf3d038fe276908651755b092f165eec2235076bd75c7576e9d75472299c55dd962e5689f99c1a378d6631c1e69deba0f6ddd1b675d7bf18e445ef9e53477dd21667e79833f3abffc7839c6a3cc5f5a9385836c71e421b74cdde5d32ff88e327ff9f772c7ce339f047bcce071e6998baf9775d76dfde5e56bf087ebadb3eed3dc59989bb932bfdccedc1d69ee5dbc40c8fc3ef617f087abbe5abcc136f9e6b1c7301032a77173471a4ce4956530fe8dfb50d50db1986f3c75479d7c23dff86a06e59f63191279e52166a6719f067fb8641a47dd946d4978ef38f33d775344f7f5c6bf1b37e50489708cd1a53c4e2b313a8526298d295b4ed9a8784ac291da3adf57be1728d7cf6ebcf38d1b974da3b696f56fdca0d306003cb18284cce1980d315b652966b1e4216a06c28641e3ca7ce3d6ec2d099d2809477b435275454751a6a83c9967ce7d2c95046de04ecfba407035b35aaf13e4d162e191fb1033b35e73d685d5ef93493296149b31164ce16249b1582cf6852aeef5aacbbdaa4ec11fce87b68093053387eaaaab52799e3773bbc7543095cb55b5085a9bb211718516286551842e94f08305e0a08a183c41f941d313bca0f08e610e227353d56521645d4e257a1e8b41cd4693a8127716a698b3f636ab10c67ed0980ddc6b8cbec628e65133cd36a416a748214c113969b19691b24df007cdc21999853aea5c10b9bec382bdd11bbdd12b84b245d6a90f655d7bea04457b6e927904273c0189f623a410cc5841538d86a5586be98dd2a4454ad95250687037e5a4452faed0228b104451c60a327002253499a04916a22bc440d23a29e502c973612077b0dc71495d497591d20247e66ce4cc8811d95b70e13acebdca19ceb95b39bb71ee2a39b371eea19cbd70ee2f39cb31cb39f7193983e1dc2f670098e138774a6ff4964a3a231c53492a4a4464072db2487d9bd4b7377bd4b3d763f8b8a3bee5a3ca8c10f69a1fc99dbfabd0201a837e72a2329efb915a414f909b46a11405e1686f35d748919985dcfad75eea40425746f0440972f0021520219b30032b5b3c61cb4151184aabef2e17b645f62625ec4bce7597a5b776425d59c1e076ea2714d31076883053dc65d8181e61a853a872425d4131c1cc601485254461415d4131a198929292929266e8ba30355e83c60f0d9f9e5c8dcf1a35eed3388e6440419779b81dd9027f66df0cbb8c165b86fb74d8f216f823c3531df813c363921c7b3fd97cd4816194d5c48831e71e368d76ee30b933033bf72a775ae7cec99d9b73ef260feb1c1e89ecc8f534eeba63cdaea72ea33a511ba5a56c3558e6e5e53678a4916f483073f4c56b8bec914dd1a091c7940caec16348833bcf603b45e2dcac109efb289bba8c8b7aaa42081b51aedb727a47bb82239d391aeff7130d2c43da51ee1df7efa562a132ea8ac469a79630357a566b65c2d294a556540b27037e0a47d411aa0cd94e768a3d6aa776429232a924c92a51ba2959629d05fea09c5e3766c38d73e73010dc6f5ce6186ecc061b362e33caa9e56a51aef5da72b95c2d897a42958172ea4631552963d725262c4dd2fb3fca81acbf62b855b270911eacdb40338b56b0e2c85c1629934e36e8290c9232998128199232994539abc791ebed76b24e948662b24ef606f38cafcca83ca2922073130a0b8a09c5c47a0b8779a43698fa7bf1a51287bbcbad36ee3346c7e199256b3f75192827541967f44ca867238a08f5940a4231a586a0a0986844444a9c943c2d095a32d4ea30ea274bc2596e71461300b8d72173e12db8806d60c8c9f1828d1bd74a911ee75629d2e3b8528a99e5c295b9852b330579d8c8787c06f60a55acccc24592e4faa644ddc4c27154e0905ae0f404269c904216a0122831050b2170c22d0b4aecadc5b647dd14c25b2aa9be351e915d77dddafa6658679ed97569ae4d5994d225484b9296282d59ea30a6b7c4ba2a25bc2521b04ecf6539033382903fd66c6d61c6195b985106ebde58adda6a3dd5baccf5b4750b0201caadfb740bc7e0242765eb190bdba29413da3b95d4342c5b763383a89f4097b6b002155ae7ac8a037bdcde6ae714d2283c1a0ae1489f2077b89025d372bdb38ceb564c89b8d605292ba39c6c4d44738b2d6909211ced8d8b6206454a68a9a4e6411eeb7c128ea92498eb6338535383a9a49264a6c1a39dc1a38de131c4b6a49e51e959e55a5aceb550a49eb584b745b115c1feba5fe7148e16a972282ca82b36149314c211c58485ab078100f1e86ca9489ce670ac7e662e7338f663ac99bb2a739888aaca162e78120220e4e008482451a20227243d81c45311a4112d95744b25b9c0b87c823f30df71650f5cf001a55a509633c8920b1b95fcc91ef7377fe714d6f827bb3242b945118e32534d5d411dc99d8a5357240ea65a489c3eeda29a504c3d9b791f75a56735efa3b0f48ce67dd417a8265418ad566b167b7dcded19ebf534b7b65831d3cc8d5dd41554ebe2d0ba2c3ccecca243284685d162b330aa09d39204d1640280fb9704b3280bf5451442d7c77662722aa3674f29a614d3f5384a398ec5a23423da7d282dc6baccf82d4a69abf5164e59613db59485a59c9325175920ca32e72e10b5c5975720648b2f3f42e5375531d6639fb923509e79833f33add3f7689d1e2873774c9239d6a5615da04c8367939923f6458edd87ce5cf1b11b73a45fe8b9cbdc1128cb5c823f312e57cbe5aae3119955736576d59ce36a28e5326b488b1c77d69599885cb91aa05c59b888976beed3b5e63e4d5d67d55c823faed75c0a25b5d4625fbc292b3258b6e8aa719f4e55e91910d4499516957a068411ed29a5165beed0b075a2b21b1f69d8f808b3b1a911dad0a041c3023af9af208f9bdf9b3beae49bdb1ba5b948d9cdfbd689d268bf790b8ff6e6376e6ee392d26ecebac996dd9cbb2f897373738aadd3cd2b76ca7d1f7a735d7ee38e34f2686fb71a3795347277f9efe823c34ec3e512e4d1b9834238a6926077b158ae8e8c907597bdc1dc972dae8045366d218426b293ae4442c98507ace49d2cb9f0802d73d1812fb8e8c015d6b5a8e2c9498b2a90b4a8e2284f284d93b576b2929798042d25f1648f7ac6ba4e663de6ae7f1fc803e62fef9c42ee3e94bba3cd23119d6c019dec7aebf49f4a35c11e307f197532f7981e2d3ceae4d6650ebb52e2c43ec6f0a27d74d1f948b3f3d146cdc790c6f3d172af786cdde53cb7c51d1b99bbcee54ebb1c4ee63e1343e61ea3c11c1e5b231de915b9656ecc1db9bbceba40c0fce516fc71c132e430119d0c8343abc8064e64eef6e8e5302c14f8e322a2935fb2045fb03d6ab12dabe6ee09291ba5a19ca8ac9f72e25e57e61d3af636a6926c510d3e4f51c943c0b2a48a325230042894dc8041962ba290820a9aac00e54469d4669d8c68d4465d98b630c535a49d8553495c8b639d7685a45016ebd4f6819065596f0181006516fdc1c2409cf8984908975a98f5d65d44973a615d7a534aa95136e5ce09e1686f497936a66c19e594671e5323aa8cdc4fd9b819b09411e5949d7ac601093a9232896493a44c661b9020252993200e34736f5007da756838a683ac05a1b27e0fa1062350620c32aca8bde05493355a2a89d274b00c1884da9a106a614494a516464334b2d4a2680699da3c20773823da1bb4c1cb94562911cd087562338522f72908648eee2c31b5b5d88d113e4b2eb2f09447d9447bc8da98108698da28b52d21fcbefa486d39858550823f6a0dc216fc438bf3e08da1c579d53d3206ca769368c6c0511ac6ec1642b905168a88e4165880e2044aa647948a11a664e899041fdc58210c7267ced0e18842d19a290a8542a1f068b1ea1d0ec34c61b04c3fc510c6ddc2dc2e1f41978f2df985ee38ff2e7f4a4be170e1f23e94d6c2e57d04a14328ede6f23dfc10041194a6bafc0c9a4394667359e3f29388d2f0e5e76d1e4da49994e3f27369324d2c3397b14b99cb984bd8e57be8e5127519d2e092239437684225cb1b0c65c9634beecba62c6fb08417b9e52f5c82b3c890f0ae0f417df51fa3169804872489993927e732b01c31afff70393ec1202f231af531580cf715576f9099960afcfdc5b701c361c046dc1bffa9363ec120378c6e6a18a135bfaafa96cde935326fa488911dc2b37ac86cf4ddc89024e25f1e7e4812d9618e9acfc9b9f716ae117a2166365264486c079ad7bc061b219ee6b286f321b11d6aaee33ab0113518fe1304e7130c0283d10b305c97e7dc9877170e54fb28cd6bfe4d7007f134a74364afb90487bc0c81790d8ed1a70e548bd1b30e54f36e101cff725c9abf6ecdacbd6256395fe5b8b11d5eb0bf70ece56a158d161aff725c1b38b6438edbb80d6cc49f03c77668e978eb756fe0d80eafe7780e6cc4fd0bc77650fdf597eae6ce9a047f8cc0dbb8397e7365edc67ddddca7bf1c17e79dcbf7c29db5972c95f314ce8dede0f217fe023642bc0b8eedc03242c759aa178c3e30c80b475dc74d220586e73cc911afe7380c39f88817962dc3d851f27c8ef0e191d1eb2f5c9c1b52d93c8eebc2d5915d1f54362fde9b3bc4c6fdc1e6ce1a77da26d1bdf3771ecd799a3b7367d39d59a86c5ee64e272a9b8fb9f389cae661b7616e0f51d9fccb6d176c6f8c73e189a5b61ebe8567508c3eb64378d6c3b3b04865f3e10cd282e712ea2b3c99828038081511544671900fc77640fdf32d0079dd42824fda4818adc33da5394ca57195dc484a5848597fb45972b691969ac9a99fe88d224d4a9928968a54b1d4244a93b226bba9b334d252bd55a26aab4335e8960a08aa7891852d787045ad6fd0410a9060d2c1192ca042addf48fd4e429238434042f298086b136d0e08df0c4f7d53c57d5fea16e62e3297f90e8b396cf5f05b5dfb960bce5bfb983142fb0abfbaaedb3bf6902df896fbb4b561181b00d078840b8500ca2c8c84465ee11e72cbc39175aee582afb7e0b1c8f3103299f5964f1087f02bbc3a78c767f02008e2980b0f0028b3f0ea2d5f5dfbd71d59977675f0746557e36a858f70d4c6dcbee58e2b7c8493391873f92306d71665f0eb2d1e3f5c5b84b5bc7e8237b4e0716698bb5c8237c0dc05e68e4764979683ffc0ebf28f53d9ebb23ac8b9dcb67c7510c6e5f59ccb6dcb57211eeb8fd0ecf2b1b698c2235076c1a9d7141e67b6a98316bc7d3d789405adedc2587de6d55bb0ea0dfe48bde5f32da9960b44eadecafbbce7dd3105ce7bde53e11d7bc82108e2914606dff2178d9a1a1a9a9999584c462626060683817979717179bd5cae568bc50ac39696d50a0455aaef4ba550a8173cda0cfe75976b738b97c1cb7ce4720b8c0c1ebb0c1ef6982b250e0c8f5d6eb94bcb250c1e6b6eb9cb5f6ecde05fdcec75b0f5f02d7f5d29c5163c7ef2e1e5de5afd3b6bb5fa046d985fddc33e0d4e700040ab151e675e85a72d6fb90f5dddce2d77fcde815fddce52f5f159056220529f1f3f7c84cb29ec5d8679fcd8439645bc2c64210b4df948fde19263e62b88d23967534a29a54d7b40ff165b9c728af3f38a36628b3de210e99c611fd6b7397d863c4c5e5a2524d47fb529f87a91d256af2d3772398e16ec62c11be35637c6e15802c0afbe83cb5bbec3ea21360274b9ac7dc491ebbfd59552667fb8acaeacd557eb72656dd543fd0ff541ea875410c7522e9f4c333fd007e186e40e4f76793de52a101fe7820ca1b49ad77112cda10f5623f3d5d75bae1fc55c5fc55c2df6d95183b4583f44488b4c45a20d77090659d9205476136d68eeea35e1656ecbcc33b1bbfc7599cf5c59dbb1176292535b7d28acd1faf253f20c01cb131b0931096bf4f3237db5ac3ea434a1a19c1a3d93b0b67abd3707bfc005eadf54f5d2789a1b7bcd753905ed67ac4b0edced815e1d98ecccf7991bc442113f6cc374c92485906b105411d47bc8cc3d89701a89a57090984b10212198bf2ef472997f54b57aae2de47a593fb5086dec331fe2729adb203577b946f4c1c7ae11b1bbbc869a4b333399088158eca33e76a5a0ee728da0d766e9f2d785b8fc45ef728b85bc2e8393f4ea2e17fcebba5ce6be72a05f5d08fdea282ca40f9ee66f66eeeaf05aad3cd55d5eebacfc8ab9aac3aeeb30acbbdcf0afdbd2b23a58875059fdaa257c0a4265556cb1bed649df7701d97e31a2f4f556d2ecad614b06a26f6d18d67b94a323919c9c1cb61944725c4ce08313e450e923adc8e6b0510d2de6743938b098638219cec3e6a5ad5ce883562ea83a85f52a1e7a4631106cc471785e944ebd03267b57792a11e627b9d393e97de48e9729b541a61ea553086308423f84de87dec7ab45eba38627844099b3a74a3895d9fa29515986f2ca679b29b5f8128706c99deddb7b39996b91be38a6a22ff60303656993ab6cacb2565ee7152621f4d1337920f212d6a2947ff691cf91d791bfbcc844494003a3b63d4d5b4dcda09aeafeb52a022dce1f5a363f41201168713e48a627326deec6b01329edcb69b941805775ef9893eb3fef12f90109a1d7230297b2fad49532084ae3f15c2408ca438bf395f372fad80239996219c6bc381fcec36ca8e6787084cb94d6fbe3414141b377a0827d0606fb6760b0993f6c5145c74b6e06d5e009272b64e4d8295b486125698ca42397294fe98412f6ac4a9e1ac6964ea8692e4859f3f05044f3e4aea16555c6161e26cf9436485a0e29ebcb2a994a22e41e35f4287e9c6a0b7994e4a961e6689b6b983c3e63e77a9fc953c3e481953c0af678652b71e618b9b94fb0c73c2277a6b4eff71001eb7928cf4b79dee7792acf033d6fe5792d9ef71944c1ee53c9bb192777e63d3c4ea1dc606e30a7af4ae234aa2945f4491b6ee61f9da73752dd287c6d31ec9b80f9fe0e611fc446ccf73c957dc3da85f5ca9a94b5ced1b93871ec7b4cc8fc95087528e5c92db20689dea3b38970b49943cab3462f52da3d8581c55c59a321739d54f454466760e32bd3b748c52954bbeb5a4657bbae4e1dbee1ef5d9de6ba4ee3b62e5413b3dfa1affa0ef3e07f65adc1fa8faae412be35bfb7e5134cfd1f0e52d3f2ee34ae1133074f738d902264e6e08d885df56f82a81445d1a756796e11cafa09fe1829528fc23d64f62e2f83d4d4b45c825d912042b0c33c663fa4affa9079f02d37564f53dff2f02d1712bee5f52dabd3e02433f3e0574f12b33887beea42a6eae1816adfcc1ec8c582842b5953c92cd3b1fe729f7691b9acc7dc97c3aecb61eeebaaeb3a785b3908855ffd5b852dd78bdee5854559414e65540aadaeba50cb41792aa332fa15a54c8494c200a1daa5834fda8c9ccc5355eb955446ab12a10ffaee22ed3a9edc1dd61462e93ad94926617f0cc3de407fca1d8ffbb81a5fc229eb3e76d9cbb5675689fcd179670abcac592ce47b128f82b12029d525689fa4febbc5425257e12460eac6bcc36adf8d79b86bd0bba112f9a32aefa694c85f55799ccabb367f9db7f26e2b913f7bbd7f134c80ea1f8ea112003e86c242bea7701229a8276949ad9ee26c9819751d642d0c12dedece764db9bb0df25d0759fb70fd97c246d0a7bc1c5e487d8885d04b2ae37287c2def5ba6f75bbb7dceee09554a9e54a5548651d97c44da9ff2e843e75ef9f673f6e8aac7148dd3bd52d771f77e36ed2a6c340701e50ed2f049f544048df920ba226f268739d7d467af464413845cea42f2411954fcf395fcf49f9399ba850d0a20aa18c93a516454d3ab2dce28c23647beb19ec7d7bf47d1fbd369f91e5952cb970e2084aaaa94918d6ec9c761a4e42fbce816a639875ce731e9c2416839ace816a232cf78d9d9ba4e73b68e01128d398e00f77c35322747911a810a2d2548333bca8618b1a5ca185119278e203f572339fb94f7baa098a7804cae22bf85359ac732c168b357365e62a9de1b85c6bcdb5ced43bc3e25817fc11dfd1c89c6bb1f0f8ec7a5bd769feb96e4ac9f53195e47aeb52e231ccad11965bad73e04f2b5567ae8fadd3e014959452cd2ddc617c4464d7290d75e2a271e11128674a5b2cd6b94bcfbad43673701df8437137512788a04ea8134a5b54e6d6e5ceba319999149594528be29394787a3aa94d16c58b7a728228cbb2e4c20363e44f7c0c8f3b72cc4663b1c75cc6fe12cbb1b3625e64625e645a2c168ed940cf7a0f7a56abc5a269d1d8dc756f6e582ccaa2a134766b6c286373b323c35e03fb0f0bdb32765c8fcd1128bbeacc515f670ed6632d3c1ec92d7a16115576a59c84ad4fd69c42187beb632a299522ea59ac2fab9d3c33f6d6bfd84d11a59cc462f7e91ccbe391fc7a47d558dffa0479d41411558ee114511821edeed416b64e349ce70dee30c377dea0109df3e0242e3aa8e9bc4121b4efe0243d5dd89c6ab41f568f9873d896a1e32d11d2cbd02ecf75eecec77369dfb93a6ff08697bb1eb3a102d1fa0c5db17a3aa57563d54691a410a6945254e83b8fa924eaa4c51d68b8ce25b8c30ca7e1247389075770aad12e41213bd7c1495a78426de71214c2f324530b255060aaf1dca725eba9a430266530b551272cae5622483995f4199a8a2909c231651bad53eed3f09d21339c47e7ee5c8209a09d0727a13b60e256a3ed5c9d53f087f56ff69474d5fa3873bf75592ceec6c77ac79a55b42b6b3a343ec3a53bb8c2832a41351aae640a0aaa7d13fc31a29d46c30da9ac3fc33d95a5925255326cb44ed6a96715a7a8d8327ac6a56cb694cd072e60c19522145c210756c0420928ca30d241154330420c87c7578ebdc34238525bbd79778530f6d86bc44e736332a7f9cc2e372673978bffb8eb1a0a3f1bee86da6c6efe4dd0e57af95dd7868d5495a59e397149f06706fcf924eb3d5a2ceeb6eecb25c8bdb85e5da8325a6c314525e6c228a7165be61af921e2d85dfe43731f1abb374e7365b6b9a3455d4131b548a7dca763ee48e485727aa2345415a69ec9fc1db5d8236a8a12e188aa82b282aa825ae21ec617614cc9598e724c965d64e18e6a11bd75a678668a8972fd77d6e958333efd18738a5b9771cdfce5e3cd6962e2bd94171f8f568c6199d3bbc4bbfe72658baef1e5f4a924eaba8da41a49aed859b799c147b83c43735b528437f848e7998f9fc83acd1bec01447dcd331808171e678e7de60827d30a418831cdc7187ca4b3eb3478ec30b81cc340b83ef3c38581a8e7f20ceb051fe92c730ae2c0e5eaa2d4899983dea75bb73bbdaad77dda4915ea2465abb4b6aaabd63ace5c5dae225475672e41dbe469bd5227ada36a8c21c5e3112ed38f1d468bc32d7aebbb89da2abd7504aab53ae9f0386766ae3ab5cdcca83ef30afecc6019d22a59f54f82387cd975c7995ddc972ba64e74ca466934aaf3d6ce0eae8147ab832595d5b80d95d99c06ffac7bf37b73161e6d328ae9f5f42d17be0f4d25d978833d6297f9bd33a3985ab8e298146f03d7c036f8ab2d639d7591f518d65f2e733d90c7cd59bf97b931548b9731a87b9f56fde27757bc3717bf9b7a26b3ead489c923b3ea923ac9bfc7249cf9c73f77e7b465eefe33b49fa1f8abae7a0cb51173991aaff1f2fb1b9bdfb0de3a8b759b31bee858d7757a471e32655116a52c1c9b790b8f311c6267b5cee131f390b9c7defda039378343eb2c1c62779d452b8ecde01f31173ef7cabaf434f8e5c5b2aeeb93f562635f5e6c6c6cf24b0d9b6c737c9b1a3117efcd8d1873994bf08698cb3c464646c6938991b9784f4694b90d196ce362db38bd36623e51145fff7d4c58c73562aec2b11f3ff7c7c45c85c3277843ccaf523d2676d71d6d78c8adca7d869ba1b98c0c9e99c1232cd79997179acbd0ccbc72a5a1b179eb33333777e1b76e779b3b12b1c9acdf3c7687f8d136fc07eb2a3cc69cf5160e9d1be4c1c2e3e4aeebf5ba3ad73bde3c86d6591cb669612062fe8f3798884d8ec1aae3fbd02f863bcaa58cdfdd91878cdf027f446bf3fff5b3ee6893594f11f5ac561a4939eafa98247378641dffc34984b5454f33335ad77d1aa5d433171e93645a730ba3a8fcd6538c52625d31af7e747ecd58f6cc2be3cb5665fcdab414beec19fdc15886d3969328ada2a8d4918714d32bb594590120a8a94dc2515a610222ad3025f9c11f237eb047fd44dabf1b4e40f924ba3b9a825ad633d46243c13bb05059233993d7769cd132f40e272a6b2478c7d3ec3d1fe011c2e68408caade80869c718fd1d58bee8d98e3076641112645a8194652e51824444c10d054528384201929426294b3a3a3a3a3a3a3a3a33cc30c3cea71548e2c530dc033426da151a13101f3a96966080012789b644b3421b9a39e9d0a143870e1d3a740000000088e13a96742ce95892e13849384a384b3856709870aee060c1f902a709270b0e0e721fc706325ce34b684d453ead2d421ba3082d4b5e729fbeb63c99b834a7a68dd12211c639a238519a8673d4331e49d4e0b304078e25356c38379c221c4d389670707070707070705e78e1851c176fe24dbce5bc86ad0611a5d5a851a3468d1a356ad4a022f76b4cd1420df007c93f1790fc43726ffecd1bf286122477a435e1b001fed0c268b16959a692fbf44743d5813c9bd0c2a065699910cda965688944084896881f6dbe117f531334b4c4d6a851032be123994c2693c964b2165a68c185d704d504d504b980c46d6c401dea7cf24fa6949c80d3e43e2db18400ab3890a714570a9287e036f9a2c67c8284d1d06bb7a2a32848539294a8c8fd9e2990dc1d585abcd2a21c9ac20aed803d355982e689e68c9e599a9b8d8d8d8d8d8d8d8d0d1b366e1cf6642dec090b0ea1db007fec6d803d6afe709eb2bd431cc9a71293e8eeb8d2b21d5fb4b8a309cc7309dec1b48369a41d61a710949edd7a8e7aa2f420f54cd1404f4fcf0972bfc78a1d67344d48ca10489e86b831e4b89c604eb024d8edffffffde8bef727239b99c8e7ca4353511c593a18b041317875824e61945cbc00a222ba05871b3e2e8088bdc01529ac9c9c933333333333333343434359f45479f0b4c647b6d6e9c0b262044408272e4c831313131313131323232b1f7606a7211fc994b34195330b480c42a795b3b95f8f454f2dd504da22b93c893896b34a38802d3c26882694d53843db69ecd257a369bf40c09115632033ec19e48300b1218b4b2b4c66839b59e8ee40ed0ebf57abd5eaf978b8bcbcbc12ce01398c55e06fecc25489060a3219ffe44a13c85aee511da26274b2c13d8048601dec021168bc562b158ac56abe5ba6dca0fc3278423ed03009470dcc124694d3ddb71a569130a1692a25ae2a290c873c9ed81d262cf8dcaec133d362a7be23842b887c8225915582c248f1048728708239ea4080d6480d2261413ec91ca7389b7a5473d3b62662791a7b45aad56abd56ad5d2d212ca40d28ec023c489e89911597a9684e6442b83f62481fec49763490b44443956c80590071da24374e88524c67eca19278a111089a049044b44881021428408112215a840056e9a5c54b6c02708b280000629f96b214a2735a9d25190a8ac7fd4e2909213e46f66022a6212134a493625fbc30f4fa290024a08443082dc07bad1c90404126732796232816232219a4c3c2071261327244e33f9c064e26432219a4c8826935b951ba5097c9c4a4c24dd6892c536d4012708400002108000042000810844200212f86442c9874f26374a9b44b69e1de1a467f648ee1011a5674620f52cc9949e1591d4330d50e95906942693a5c9e40456d0ac8872e3682a196232641ff00033c030c308661cc18c30cc4082196298910433b21431842288504411cc682a22084508a1082f8af8a288a6229accc832c50692033e4ea43c95d8042131c91da0a4052c60010b58c00216b000063080010df85432d4c38bc852049622be28a2a908a722ca28e2a98833920425114a3294c466461472bf0832748ea0d8104d8189a80826cb030f6458418609c838011951c89042060ac8b8820ca422252852451123328e8a4c51840445a8285254e4a8c811194845356e0af8386f432c61244f451015117480031ce0000738c0010e3064c810047c08a604bc0852915b91a2224745928a5029a254a44a91a522568a3015c142060c72bf880b6630b2b7c7498b3d472df644693189ca7a94a80c4a135148d1636bb1a5c03d4854b64411220943bf27a96748a8346d467104a5cd27412e8378413368c3326d4b3b9eb0f46ca220a96d5ec8d944010c6ef9c9b6949bf60429a3250105309033da11a48882a49ed19a5a7c02cd09b426d0c6a03181b6049a1268596849d8f144693b98284d0c1a126861d08e4033020d0c5a93dc994bbe63c753a695419b42eed39c3e6b7730ed709b77f868c520430c28881185dc372246196248418c29e4fe0ca242ee033d511f9a20717c7082c4f1e1096238e55e727d50820f4bf081093e8c61349413f4e41830828d86a612f643dc21c23c042c5b6c349f601f9cba6e35959ce80ed9da24fa3755473cb93e381189f134950849994e2e123c6d0620629330f40ff2a8c94004d114c4520e39e490430e39e490830e3ae8508013213a7221f7e189d27cc8e2c3183e384d2068024213189a40133d2be2fd09d87aa681f727e0a46719787f02449436a1787f02374a9b4f240189dc9f8012c92344cf92cfc09fd9040992b9e412fc41729fbe123c83241859669325486e076e924fdb2de24557488a0632f0b56a527127d12709ee7cf2398511179a2010e2f37685a4143922281ff10e5c231df84ce288fb70e4830d0810204080000102e4861b6eb80c01ee802cd6ce50943f1d54f010a480271e8690a0e1a4a3ba03821c20e4802107d8286d1201f9f0e1c3870f1f3e7cfcf8f1e303b8029e3e00a86693968114bb065c48595f4182dc2aa0413505d1dda189cafa76c90e58a8ac9f3cc13d4e5a48ca5c7226af49994b184d22bc438f8dd284a44c26f0913e6e8283984aae002c0af842014d0ac8a200270594d13323bc8842ee2b800c05102920e8e7e7e7e7e7e7e787070f1e9f4a6e8fcf25b74f20c5be42b60aa40cfd1db0e0f030c4e1a3bde2a60229eb5fcc33825bd18d8a2c7207a856abd56ab55a4d000210c0547251b986eb70f405408afcd5886430906247815b0652d66f81009e5c5153cd24ee270027f9e3a1b237039f4cdcc981cf28ee14c127d19d4f7c2ab91af85ce2ce26fe0128a0f86c728da47ec4e6b9814f2897c96b52ec27933b8443a081cf115ca119c5e7937f93e8f640217b46c0816b3404ab0931794d0a133f02cb48869ec4a54c259f1bb84d3e95e023b03c97984ba692f78c40ee08f17e8f087a42d00345ee1879bf470ab93399bcdf1385dc19e2fd9e2772672af17e0f91dc9942eff740217790bcdff384dce90181dc9943eff738913b3389f77b3e2077e612eff73821776693f77b3cd0632b9a441f7baac8fd9e5bd330897e6bb3896ba4d68490154a9820f7873841ee4f265172df8894dc3f820628a0c115b92f045227716b2518bab52a98b8352389d347726970945bc9ad4d21716a2468726b54dc5a914a887bc4351acaa94d298e816b34a4539b52dcc86482a5f81442f2a9041224d8684a816b4792062b90346841eed7b09032f4bb55ea5d9e44b77624050d9072a770ed16c595c24392e70cf2a8c93c9e244ebfc751cf8e78bf47941e4894369fbcdf2389d226d1fb3d94e40e11eff7a8d23323deefb1d4b324eff7b0d2b3222ec447c9450d72bfc70c248f10eff3c8c28349eecc202448902041820409922186f868f38e21be0303ef79ed76815b7b818fe1054e83db1243495cdc279062ffb3413589aeac09117d2aa1e435a43c93f8479542db4ce2f638f594d1b2b1c78681c33c75fb48e2f4a792db57c89e44b75d41b46131301a725a56a0ea7b21e5420b28ebc93af186ab346ca03d65cecac887aecc53c9957932b932cf105c599b4934c953e81acd279f485ca349f4b904cad112251f694c28784a81a3c041742ec1415349076520a63c7fc4952d5a274f88888c485284063200c5134b97080c0c81e448917717c2081e8378f72257e620aecc17b816004196dcaf00912888721f883b7eac58d89a6013b8a3e8c36462cb7d09dcf1118000134ab9ff803bc21c7047b10733b0e47e03ee78062c40c950eef370479802ee289271fb90034c810966803b8a528c2cd7218731886038dcb15b90fb04b823ec06206064c97d1bc0201a8f858f3b561bf0f8f90196dcafe1f2e028f70570c78a456d8ee096fbf91aa13e778405e08ed50a1a641741b9df73c763c173c76a033b838e1548b94fdbc194fbb33b8a32e0248d3000e8d0b194fb30e4d4b0c15ec0116fb98f6384b520ab09ca7db1e786d694fb37ee08b301833de9e4b1e7ba5c4eb9c71edb68eb9399597494fbb13bbe62516d9047c905d3d832c87d185750eebf802ff069ac56e4d1daa62779a447f4a8c796fbaa3bc2f228bd78a2c16d0ea140e905343fd0330633e09e70441c141c110785c4e9cbcbcfa7458ec3767ade9116f35c80a449171a9b1c4488dc110b9083949426b3c9e34c86c9e13893bdd0aa6c8b1ef8638466be5a5badf53cef67e2a091b4cfc81d6ea6c730cb6ea6e5ce120f9ab9b678f2d07950f20c60e6983c0498397a66dfcdd9b536c8f1983c03b8c116b969f55412675e0e60f280920093a7678ef99e2d79de6066c9e45d52ef56ee74f66ec11f0f485a256607644391fb5d734c70032d4d99eb543170617058284d14bd8f5c166e8c6c3d2fe57d9d67adb529d50ce9bbae9bdc39ee8b16b9a72c1746e6ee7160c81cdcbf8e4b3535f52c8ca609f166113c95bda8a7ae903b84bb01640eeeddf77136fbfcd873ff217980f8c80117997b0d72c79e0b5f216755b6a7056285fb0c926708db23797ce60e397e798c21733c99bb0e776f730e36138430352477b0c81def1cd7629338dcbb291c534d9449e27041ae9021930701320777ce8bcc7d9434c81c27f6ac3bf745cbb8731de6b0b4c89dc3d2d2d4f5ca0aa6ec7d86ddfd31811176b9f322941c1524169206977befa2744872e7bb2271baa4ec5dca1917852e4af65cf0e70329f3ee7d43c81ee794bd30b2778b472e0bd9fbfc985af4de0921ec3eca2cdd92fc028a4de866ef1d949ec95bd382b467a9ccbb87f2d1a2f75427d4b490764dc81da3a1732b088d5c6eb9a57024a293b9b77c94597e5adee00ddc2718a14b965fe1d1475e5d75479dacfa376f337fdff72e08e128b3d0fc3acd2f8ee807b9f3bad77ae8ea96248e4dfe70ea49e26009475965823faa96bbae91d9baac6b648677ac58e4d525a293270f3d03efbd879eb5dc7b047a36df75e00ff809651473cb5b7509fefb52aa4f25672aa5e3d96a8faa525a69abf7092594b7b332bb2bf9eb76207378ff3aee6362ead995a611699977efde2c027857ff2e91209e90bd5740e6f0deadf00f2d7a07af10baa52c462f4b94f6823d9b83f46cdebb01248f77040c913b2a4f02217b172277be7b36e760333055abe6a7faf25743cf52f7fe43f20c616f903c9cd304c241c19338c8de7d4c90072aa7fea56e4e0c39dc157104214c3dc99d25b983ba473d9985297b475d9a45e2783f5c222d7a47dd20248e5744e6f0ee3d0a50c81e472471bc7bdfd134977bd7e999bd7797db5969996771b7d4a2f7f9b15b0a9956ed7241ea26557684d525aa83ccc1907a621493c4e99ed59ed5874153369dbf6559e36b91b95195e2ce62a6ca7dabca55d2e80394689c95a294751714141474abd92cff4d5a9bfb3c6c4e25285a9cb1d4822721226ae68bd0f790797e5e1e3de7a511779f162716674bc2f99bcd0d9b396d6ce6b499b36d6ed8d0db48b1a178a4d406cfb40883d10331f16d60184c75e3dec6471b366ca0d9c6c6c6868d8d046df068f31564a0c0a0ffac97e9187393e9c7eb4919bd9cb5674f3bc159bb0e06b3b6bb475aa4e72e112dd20ea2454ac4112c84a328f69b9e11d22c83a0346b432122a5ff3a8ee33814376d94d6436a42c9b4071e86b4482916e87df49f7bb0921604953bf5948a31641a84b56108cb1e364a4ddb0f75de521da841962936283fbc8411ce3c2f3fc11b669e5eaa7ea8d48b14c7a920764ad588080200007314002028100c0884a201894cd12349fb14800c86aa5a70549aa8510ea71452c620628088000000080824690800a0172abc01d4f13fcd2e40017622956a06762131a3ace2fe18e493294cd23a047d84a6a9c788fd62a3165423bdedef2182ec1b29ee5aacdc15d93ddb12f9b860b489c1884c32e1ede0a140dc42e9a326d317653c555c0a1c16c1a99a5e611d433515e9efe5a184e92c8653e72ba2be26aa0ada6f998430933f39483e53d569823c4ce01a80a0c63bc9cef0ab43dc3e29e359111a3727a2bb47d5e55940af2aaa0535dc35c51daa2d19bd2df5759c2f661ca84b99cf4a77c25f1d72fb6999e76ad8cbb7b14cc0fab6e6e6de06c5bbedce05a870d25593fa14765bc2c2a1e614b4996dabfb9e1a9324af8cb739aaf09329787d99640010cf2c89cdf5ffa2128548023c43c39d40a658cb01dcfa8facf0ce6c9c3c6bbe35f0afff705c71944b72a69f19c1aeb98d4c5c327a1fdae26ae96805c9918321216d14f06d065f064414c0c5d12807e4f0b9732b4350e7cc4d57ac3c62c5a67ecac68510fe3324e6b8c1dfef134203094ad0f56811cbafd9d41e97443fba82bfacf537f05761db250459f0f2850869fa8632d09283e8dd066089c1a6c78516688bcacda1fa76beeb1e4610d9b1880a9d1365ad90ccd82fcf402177f3195c53d24392a8aab07dec33467da78da6eade77483194b523c7d445210b8b3b3725e2350ece590ae6ddb4e627a90676fa2dd4e2a7ac0a0a9be0e20200d3a10d58e13f00e68af70ccb525bac604a004d79f4060ddcb15faef2864648d056db064249d11a61a24b70dd482f90706b9411c28d87534cfecc7da3a7636d032a06bfd9fba2c1a58d6814761ed9678e51620c9fe9a8cc66f7ba14da35c591c3f26c05568841d1bde3c71fecff0bc8f675c6d85f3bcd4d0191c6f4b417e54827d10387a8e07f3baedbe0beab832ee6aa1e240257a9c3cddd9b7098e3d56a2f7fa7556ed200c1b31b06404308192e43cee30aa71a971cce4d57b77becc60ae4f829b259841ce4b9b3547e0878ea7478a59d11af1467249f89d2eec0ad40c72298b5b6a464171b04bf98cf382a41dc6832ff4e09fe4cdb267c6ea66b0dcbbad575290f7b9e010732b48403b93554e885297470131e3e425f0874354e781c777242a89ad3badb618f0bc1dd16f5fbb87c5b4851022276c7d179b451eb1aa1fe08e0e62e93331a93b37dacc3b2a1b8fc6f1c70000e509302774323e1a60abe2a1c2e225d18826c244dc344fe5f0deedd58a8e8f0bc9593a558aea28233c2afa68c5b2682bf2c7188fee75d256bda7005b1ab8b373fb040335aa83b24e120eb671d5382cab9e250700992ebeb4413cb176bcc2ec88d7e0108d64a72cf0deb04db00e63d7f00e3c27074982c04c47e1b6ef482a308ccafe0931936984371181e331ec2c69a68cb900e99d261d60719209d9a53a4f8e728e7c42f5a0b9997e278944f1de1b639e96fca785eac267762fdbdd3066e8e03fb029ad0302790c15068c1dbaa608dcd0373b5babb430202c3500e9fa040f92815453cc43a3226b40e53b0af0d17e186080ff2ba281086e7ca5f0d913a27575ed14a15434e5fefb8d24ecfbabd38715ff44322bd1fb89a7c3f3f02be6522fcddeb0483afc27da7308d8d7312c0ac114dac295f3704a5df5d051f238f692bf2a1d6df6c245bf2c82da520b363b0911796313523511a0e74820d9987194febe56ad54ce611decbaaba2f01c3a181c7146d19e9e7b85edf5849e42a7104ca3164df57e3eaf8159cdf795ebda33afa090e05e49b66e4bfa96273e7422d83608e2bbd45367e1325f52b7503b2aef9c08d2ad5819202dd20a3d47a3d6538a813274f71b2c3d0b45ac1c5bd62619941d2773fd94ccf6935dd575e5ba714dd564e7e5f2fea1098f131f0e97f023ca5e7a3615b6920e38233b21718b8c357dd4479f7f99fe1539e690aacff1b4a2949f4c26664d04a320a864ff35241bad13cff9d0abe272e8a15830b38449eb890b1316d7401a1ef61d5324d42cb2e27585cf73d95c09b82a6e23e0e48e911beab307d749e88bd84b5a79f9a8cd3f9ae176ec4241d77d59ea1e6eae97b284ab2c2b48980c28052b70d4a30bb1b00dd2a3a2830c29dc4c65dc4e6dd450ef5d5c3194860d7530d58e38b1e89516006cca24c20719edce4efebdfdf482d530b37296b9629cfe99d936717c9d917c004cd4c92e8a63f89c3e4c05d1d729d9313ba4a945a9ef64917a84b12eea46b189ef960913abecb6d643addb6092051b29512dcc9bdcd5457105a351463bd234e424c0d8263c4f473b101b5d20749942e8569fc008580526f2b54b068f78f5f9c27b21f22587d974b46690f59152f9873d8841703de22271b3770aea08407c0a46cc937586e8679eb0a51f47fec7916fa2e3c4a24301d725147ac72ee8c5059ea93577fd208ea3acd08b976ee5c9a72351f4ddc5b485868a8797f8bf486647d4f7839d44e3f241d492a8cf8510a018a9d56696ad0ea324c61bf0b3a407cdd1a9688542d72537ce8a700085f6c20324eb6373a8e4589679cbdeabf6a84fe059b1c2627dc8a10f6dc39e19529bf7d5a06eeb2a956dd05232451839f8d346c94a15d187491d4e707c51557b324bf9acbba175b7b4a56c503bc34e4a31d215e837dab95da2a7ae65adc1c1b62d24a53eade0dff595c02e4cb98f751a1cf96d48c57f19c78349d407e8939ae0b0d69c49864630222b36477eddd69c75333c5be9b9595c9af90dde6b88e1dc56d8c84c7d656e53015dd3bdb39c5dd36a79f896dad9f3b2ca042cdcdd8a66ce8dc7216cbaab659ebeb075f14686bcc468d310910f0bbb93e285587898e397cb4afc8f2675d687531c459b61650a42d89d72efc74b3778a6104351e7b93e4de049631aa0b868657ac6b3711b29a6790c0c58636910d6cc5e840d87adda11e561ab8ed54b3070de3689bb24542158a1d108a4509a549d7a67637b9acddc769469d105e4d0d17d2828e63eb9b7e34f5f10ff11523d7303bedad1ae998369660f490a6447b783c11520ca4e375d140469e510fcee0d5cefbbc2d91d76c0f7447bb0fa8536fd88880503129f69100a63ac00c74c9c279d85c9f72c0c79be4da98139e8c67520db8b28c64efd6960d45e9ffd4872b2a560795c230ed161cac6dbfddec327080a31ab196d9494fc5846d0c0724fcfa6122f500ae4c85618bb87ce3ed5ab2396a33b1302a1db91f4f48c12955102fc0d5851be6e2b986929074df8635f613eb783444b946c38b2001c381bf12127d6eae60beacfe6e9cb1113c4120d301fe541b8451b04601cf959d5fdfe7848b643e9985cf540784e8269f1b197cbe8b38145603cc6d07579e6e568097db20d509e62834b4ceb6db6130f90884aa291afe00efb68019037769cb4c42c72f70165c0158a69e735282a06172ee8d726109ecb7ae00feeef472e11644d39ae46944677402d9240f7c3a9fbe24db1ddaae570b30453f03ba3ba7a3c1cd820667ad903b3f25489ae6b978a8b61d880090876758ea78f40af476370d69063a30f434c299ec52ebc5b94c5103eb17db2db0c28f6396377d6f86a7e13359d1c2c7e499d218ffd5b0791bcde0de1f78d928667b27d426af8db939bb6acae8efdbdfa4662438772e78401e64cc695a5a8bf83dd6595ab71acf78f3d813b8229d4717cefb9e840199fce25d20d5c1f1b69490fa2a6e2f06df73a0319ab1acd4d98a5a0e7bc89c63f5c0172818178def69655e2ccc0055b62620ca85a29f65674848050c225579484a6692b38049433dba550709e2b22b73c03481f401828f61402a5e7b81a2c7fac0cabbd90424e966e819967c4fe4a578d76f02e15d19ea93c4243a7f3c04520f905d0a87dea3649f4d0dd2e142091e369ee14c26a0e07806954e9ad9577f28df70efff9eabf2b3c740d84fcefcd590693e67194f534128d49d2314c62ab8a91eaeaea496bee82f689013807e020c42b189a3a168bfaf0312f8b988185f2de44dcdbeb48413ed60cd086ace5170d9b86f3f63c24987cfaabb848730d81ab060791050b99021be5786107961ea8eadded695c1ace08d7583ea115a76cb1cce364182cb2b5b6404947ece9fe2918940ee3bbd6f1e6f6c5d008da3fee3e4547bf3824dffade5b02c1c3ded508f736468e15d761c9eb547337344f3f05e5fa59aecef68b66f2054ed6a8491b5923780b48c8b584ef106d4fca3c3f79a27a53a1958f176150a3cd39da3419e0889875d068746d90b99c781348b9d89590d2ec3b6434d3256e2aa253c6a5332de52adef232ea4772d03e9b61edfbefdb2ea2e120e5b9a996184dac804ecdeaf1b14fab0c051d256063a814b0ef6bce415c1745950a1e6c0eb2f0dd89fe3d802d202006f2cd4fdccde4078b81de5ddca403bc46101bcd151d05606da956b804748f86ce3568645607361a8592e8cb63268d4158a01809f182e5c0a9b2d8297f887e7caa05786fd64afca95c14510125cf065348dc818cc36eced93b4055e31a420439da1d9183257061716fc32249e2b838eb06041a8768e9ce3d1eeb8ab835f898bdd222f420c5d3031e5c1c4e0be2b43345ca4185c822d1ec1e97f9c4ef1dc518c5dc2c95fad91cf728ccf65120bab666a2f8e719f461ee91ab66a006d0edbc4aa350519b928fb4f2685fc04b80b7c54a09cc2bb8116d66b020e7948c90921de3791ec0d1445e39499e9d24cda43a44aa7f5f004671217870328710c943377a2a0cdc58edc99708716e43b1969155915562216869b4fc6d873af77adea67bd3e6edacfe895617b594098289a51a47cab5f834d3e8277febb15d8fdc69e5ec863c8d56c975abc2cbb0c6dec7be57f4258bb832d46a8dba0e156f783cf8d90bdb61b3aed868ff65d57d211d97026660bb3a952851c30748ae8d50c74828288f72d40987501418d60a9f255ad543bc016f5e8bae928505bed6cc270d2c5f7f41c807e459ff34feaa564bf37f763d7c3d16de8d57f3411949db5938264b6d9817ca14f8062379d91308e805c819e169fe221a9e8b0f217b968086eb1e7fa2d3db4b387c8731cedcc4da6a87ca3e2fc47435d36f5e947676a27fa81814429b6cbca607e27cfb25c4e223de91265f1444f61bb32341376cb6d8a5cc4f21c98550deaa90c00b9ca03f6a8c3216151db727138e483c4f7cb03ba46cec58014f72ce65d5cf3f466e36b188e2dd8186ee277e725e92299031282797034c5156a24ddf024409e552b7ac4be3bdc89ac74a2712a6e975201a24d566b496541c22c131e59849c0a9ce1b2efab7804ea7b043c4c44c03487c99967d20f115e7a6a7ad24e370550ec080b6e8caecd0b9141abaa6d43bf1ed5615ed7dbd09598a2046afaec8f32bacb07c13c712f098188f494409a7f0631e668e5c13d3f00c468e984c6d8933e4b4b66439305c10dee2a72163f30fa5465f8e45fc3bd046872bda18cd6675880a0049e0f037d33f506f45fdf1dd087a16fdc27e801bd5d83a4b031b6e5d1b07056345bf0911595f5bfe1d40232e8886468437ba2dae53163d71cfb9016c516f430fbd699d6424e9bf5e3a5c316ebaa649d3a9b36facbc9c1488bcec16b5bc6b4d1f63c1e575e57a9114a74c545a7c1557497d6e42b5ff38e32b589a00a3072244fa936f427c8bf094bcfc0045de42e641bed037050ca47ca06a5be66e8850d1dbee9c74b8126003b51b80c3511376d347ad81c676393f931243428a20fbee56a35183e09dce065a30033e4793d5cc62cdc815dcbb1d1c19c5326d4a558f7a90f616c84d807ff219873fcacece490d75fa7b5a6930f58a3d55ac182eb1fb219a40d4cb970f1481a07dbccbbfa66a8b04db937d925741f6e3e558fab641d1913e8771658b94ff64182cbc915e883cfef6c1aa792e2267520a0f73750d13af19dcee2df0dc2435d7d23f5002618af9115ce873f936c4d5b60d9605a2b4664ca42cbfa567e5c8d9c9123217a1c709d2fa4b9e5a6e85e1cb37831000837a6eb5a1db30c3412c9a78f4ffac5a216b5717d237cd0dcade4d0d88ac709dde8b9a61433748e1c65b587a3192a89e9c280e9230ab9a0fed944cbd7ea2f638de910a71f83bcda30dbfd8fcb24d0fd15ce8f8cb46d4e36e2ef00040495655c5fbb7b19c7ae1900e689b44e4c7afe1b3852460f0212f368632298977acb52f07509d8ceca6a03bcb18759dc61c4811b0b631bf305654de88c1d56263f5510362885e395949e5743187dc2673033cbca76d74698134f949781024c7c7305f3b8851c29f5fc7073e088a5eb2a07fe9cccd2ab3216bdee609f6a0a91d0a5b7686ae9a33a7edb6f831f10b0d3da199a053243ccd850b6cb5d127dc412bf35f92062041a6209826db917d0ae4dc66b0afe9662f13672fe000fe8fe60e43b633b9f60253fe744b18ddd89678627e93e05fdb0b9f0a0d9902a56f7e4a64662f6a32b5301cb5ab7c664476cf1ed23e4760d267f23f5c4812ae3e7e2dc61bbaa8e89db817159386182e9f70a2c6efe0de3f43137100118576f0069f3228e0974166769380d33d04881006549ec80ffd57b630aaf865e132b785f913f982197f6dc7504c654385f99e1e31c28575d410b5f3c45869eb06d1bee51548e11fed3bba15285cb458535be8094d18a4bcacb410c61698c09335a8e9ad8469b372be957ef2bb2a6163b3a023d92f1d526fc981b0efabc043ac4c339a4f2da0d59e484de9cb3d3bfe2c871e32393e989759afc109137fccc4abe9238235b5eadbf42d64e8ed8490f2cb20bb51244dda3c000f1eee95883c531ffebddd1ff40cac70cce18ac158c220700b1c164b21380c36e222983487acda18529720e79b4d5950bc3d644978409b6099b2f204e9d39bf7e401af50ba3477d4d84fbb949d6f134ac16508f71dc0a59db8338ac791ab88c1ce8a4ef1b3723129664e48fc2e7478d99ff4e0f96228e27a7d112d6e921250adaae2a2618d953a4b5dcafa32a5204b8d9deb64b12e88ff3e0683c62b0a5a08dc6f42f37ee10a885e5fd9a14cee51734e0eb69151ebeb84d818d9e4b92913c98c0e9f89eb5a933db0c5b53e9e49aead822022724e270db5e3e2b186e5c5de4323748655fcaa1c5981ff2d9ad987d4a49b245204971683ed3ab044d73e5658b9373eb5c9c08a7ba74329ec1741a6ba503bacfd6a5cf5aa6bd1569f34a7c89b5c7f2a3ed42fb725a016d34aa05817eb8d23947b508b548bd2d12ff5acb31bc1c5f992e5e6ef2dc546ecd8ac465225dd698fb3881704b10899f6c8d44f9dfe2a010c943867f295294d8c0727c4f689c73674a363abd6e48ba55aa2864d7737096027c21a9c8a6aa1ab161645841289dfd05bf751dccbbb58c27d224c92deb7a5c2f6b97ad9e80fb1a3e04c09471ced019e73f4b4d7190f6d8d3d5b74663f9363d12988594e23a394245a70b6357e3e5389908527d98ce5a3d86809b9022b894686e33e7cd71acea200916459879bc36cf58ae9446ebad11926a1e3a7a0282790cdc091e18a99a3295a0d1668e902d5d68806430eca0ad785ec0259da87f26ce13a702b9c01bc1de7dfc596682fcfed3428de6802eaccf49b75434a3dd0361b9f2d4607fe3f6c969c5fe9fc4160f93f6e2f57a838c7d8b64dfa341350d4f6ce6f52cbea6f8e8eb058b2b4b9a02620dd98226f5fa20fff8f9422193d6d01121687bd114a76aba3a9a8fa630adb1ee80805944742f1564ced865bc52c30234bcbb551db25399101a65499ca8c795ababf17e71c97a925a4d4a5a981ea8704a9780175cb0c1c2da186d08cb11790f5d6ddddcbd1ad95b10b48fa4e2329b180aafb6480f2b74f847f63779b6b2cee22c2ea239d57de0a828a36820ebe19c81077342bb126b218873ffdb96ed8e750fe14053104828003f77e3a6c3dfe011e0ff60ef2ea1acbd7c8eb6e15d257d9e1ab36e71a47c6065f2d20f4cbf74517034b0fdff376d96110fa6b78810e891ce575204e09722801a991682374e3142a2cd67ec2c71a1b38f119cdd67a4cbbd7df5107513530540c983eb63aad9a116987493fe12fca1434b357b32c8dd1e4a9b850f50526eb59b046e99b01b379ec09d92fb66bfd03846b21dac616b3761d6b44c1f0b294772280b6486b1529a269d8e009648d6aae2dbed1727209e9df12147c59a9e90a009ca68977ef3acd227512926e09e62227a1009a6621c8ca8056e41a02d369e5a55f4f64fe063926981f643c9fb91d28fbc15687dffeabc24faf03c3f9bf944f328691e1b9b10cb8cd0ea81e4df4ada67fc85281216c6d100e97c50df501778d1bb5c8450e81b26992ff360be4d21a7c140a06f161d8ba62c4d984c6d836f58ab413f5e59f7a4024e3b8fba6b03b41b95e53c261b8bec47e4002d410a59d28391a03550251bc4484f780a0d7656273d08da63d879a7955f3e10fdeca53f7838dc7acca9cfbcfa4cd7c59cfaccaacb7c7dcca9cdb45aa6d7c64ced4caa657a7dccd4ce546da6d7624add4cd5667e3de6d4ce5c6de6eb4aac5ca97bcc19abbc0701f50770c9eb16d54c09dc124fc30d4377fcf0994e89c00c2e9f0781c50c40e0a84840b5d1a5bf89ba79a70bd43098a8b4e3edf5d79bfdb10ac1641f74e1129ddb060926ea7037e7c0e72ea648fb8dde44cd3b48dc163524eae1c15f3b7487d1c1f1e4ecf27b0121d47faf58346d5d100449c5a2916b4f3a0f3f3b54a6409e01dac771d6317c2c67fc7b2e0d503cf3b022dc301f007edd884ed1381ca27692c221ad91d82123b4639a80c0c58a3f660c1e5f702e80ffbffc5828c270869da36f07499bb44b2fe1e18f10dcbdd3d72542eef34c8b8e8350ca69d1457a0cc814f1ff78d68954d5db542dc05d2d1fd6c9fd3700d52e600e16ad14dba30fe6ea97057c1c9094b976bab5d0b0f2af495a9e6c10ee2496d5ed63e63b728b41a1bae66078b350189ec8fdc5a7882a362f9e7c558e953f0f945f45834d78f1455c0f43424c44415899cfd60f3c8be5d92e54a3d05f381e25c861df5f9144c51d32216a99bb745febf68dea64cae60a41af005839d6a24c1978fc9a4c57c4d99d20f3726a429c6b325665401b21edb2edce3c4c55948633ee2f0ed5e119d8637f1374a1873db9ab10ec11a85b375064bf343b51beed2d4d66eb89851e2175951b451442c22508d5568c5292205fbc88e5e2df48e524b92f5fe58e65b4bf78446e46ddf2771c5e5d703f40d2cb63cb2c44850d4d80ecf25e502d49afc88c6a39cb8ac4ee7e6450bd4a49a62153f9836008bc1d1d928f059f0301099532c6932d44f34a095d5324770b2f01663491e28ce4b2f060b22ce67e95ed5328fa466288986cfd66af63d794a309c2c7aa63cf6b3e52cefd365d1fabeb04959ea25c13b6dff81f6d3eb5265200c78329e29ec827b4d1cd619f971853842db49ce03d090d4b2d9498fc9d610aa3694c884ffa614c3fa521be7e02e76ff8a271727bc4ad859237adb178b8678175e3ec0530ed133e1a17656aace16926475497a01139eba9e0c5ff22adc17a5cc83ea16863543090ebd13496aff468d5c5b1724115232c9cb0b206b702960b6a55a675207e5a1e8da15c20761d8cfb1a811282a767d59106b791d41a8d9c153f1ad036ea3eff15e83b45df3264c51897094684586a7d1d54cb3952650b4c929c23143a6e07fcc69f9d151796410d234694c115b991c87a55cf133073126f643272edf40c2af8b92491233247a6aed10529a85a83ff24a8ba853506b379d522b961889b3d4b535343b55873d63531a57d3521035151f80cd067ccac060539313635aa6bbc90d2a923cf172f303917f17d4bc06582aa986d4a29de90bfb17f9c665bce27c5125188768f53b3e5129bbe28f538322feb4a115b32b806f9648432a9679c13b4205e9de7d65185d6dcecff43cac7e1a8b009077dff6ff9ff291f20f2cadea32698350ae08163721ebb15586cf6f1f2fbf8a2726a9026cd916fa54ff4a990d8d1dfe965376749b3cf22ec1a796b22a00cc7bc39ae69e4e66274cfdfbb0111b02e0d676decbcc229ed0bc0e10fcf48f26e9b515a0442d010b207e823703b08e935055c2614cc2bc231f909865e8f34ac7899ddf2d1408528949f23f537deb1e0a29dbad8a1b65b3b0ebf1291555601b1b91905d2e80ab0e8700ca729c05886f409fba420d2d152b2e6207fb88ecc179fdc676e27c273f29db44988b50938d00683bfb2e886a18b12cc52c50b8daece4dbbcb1935e5377823fd3cc6c84a95a2a592d9bdd0285f8eef558e541cd4287b86c630e00c4bf3759cad3b6bcff112500aa5126180fa81227983a2e9fb42f6b1b2ebdb24c10d87e191b2f71b0d2ff65ae5ca4a59068f1c744c2283ab760c3275bc3bf06a14111143630a8eb1322c361f390d0da5f918c5572a6736e194de65a28f9c56c8990f1ca144fc02b012e913c01a4384499d220cd5ffb4897f470eb83f4b7563c648648a43cb9d3635eef446a08685831d79473aa87dc0c001ba0a0d9a6d60d733665cf8d536f8352833b835bb9d3f4fea62a3fe07b10dbe1125ebd61e239e3d2512ce9275f402dd320275945404f9064793426768f951b15594535444e70370c09f1ae2ee4b5673b568adf511ba604112afe175f323ffc823408d2881948314ea2186aa2652cba0f32c6abaf122a7e536485a5a4c01eea4040615023372163bc3fbc3fc97d48d4c5a8419c3a154b8d0a88bca55cd27292771e77b093b87295d0898f22ec549bf15cec8bd94fc1a9576e9753dad5e74800a2be3374cfc2cad20ba4e38e24acfafebc3a03158f83c6759d4618682e5ab9d132ab646d6420913f81f7ab1e603c80ad85f6c895470f3c5bdbb86d984f43099cb320d196eaa94568351a9c29102ac3b0545452bad63f264f107c7b40e318c576218daf26a385d00903eb345ddb33d9579590c6970120eb3f399c374e4c1532cab1cef33172d86d9cf52c1f1a748134fe52481e22d783e5a241610c20d349e9928a8f96742e627c2d3dc4232e7b7af44686f18e344a0388021167244303bb4bdf39a2551495c89507ff2d96248776431207db618293e8a3fed18b562636a399eb6e9a532fc40546077199a34a31c58c73499978830211d08ac806177198d23caa2817ec55252b17a06bb2744244ad067568f629a0920352315b5e1dc03c3734c756f89022ff44e8be08888454b7d57af2f4c6aebf8c0541415a5d44a38450701a7b2cf24d248a115476ce20a959a7ca14643397f8872a42827e6c9ecbee194e81f10c1d2a47a533f1b1854497d3ffd21eb68fb3ca57fc153186b2a4d11024a130f03ce853cd9179f30d77ec4fef007abcda8c7d0dbe1a8af5f595d17d4ef0106fe387cb04647ab57dc70a051118a14a82d2aa2b08f51f5483b32ea471d176e8035fc9a4f634c123f641ae4ef855a24994764a80ba4c18525b82940b6a3f2ee18e768c9cf0d4808d91ec82a59ac7bca0e6a47173de6090e5773ffaf95eae10781ec9e75da6186cca88bc9202b25a3ff0b498b2abe216f728f257ac98a3bc0350f9621d9ac3c952df9e4dc6cc24d8c09efb680df13613bbeb5e983ced30ebc2d64bcde023152e3456740a5d7f20f05bb960f393e453f502e20434ab10aa863023308151afe3ce8409ca050c81fadd09fed6ef7fad05bddd5b79cffdfcd603e695f427cc0a409ffffd2c27d461b5d1913ba5e4fae705072c9c997fc27f8243fbf92592ab1c8da694d9175d3995a24e96b7fe19f484870901938b296c15dc02ecf62ce71e48ba70874fccb9612c55694eb4127af3a04d4ef9fef9620c0e99122a2311a5ec00a29621a69a658b32f05fa4e972ae09b3de07b37ec29022746a70a2d921fb597537c74e41282265eee3ddb0edb62d644d352905823cea61f8f4d7498eeac9abe3e8c3832e9020d3fd0eb1075fc30200fe0e0e4ccdcf9a90d8452ae238ec529741df8f9f54ada6ab144a63c280acf2f26bfc019a53e9bd59f6059d378b09b14820895d2f504f09dff4cc50bba6d2692d902b03c426b4800a842629284ca9e5a55950fa690854e3e4445cc83f3f44e11c7baeff3f2bd627f6890f181b2afe229f5aa020b60c15785d57025c1934accac1592f81d4c7b5e34cae8541eb79690e3033e6231b97e076b4cc00422aea885e078f249f02e0306a61d2271e5c99a0f0d881408daa99ebe850e1fc97093a5d946adf5c8df8dd1b8cb6bc1716a6abe8cfd6ca6ddf89e92f6ea2572659a76149761a0c11d84c031a589dc338797bc5b31ee9a6bf88fe21dc59e09bf7eb450595fbf2d3ed61a72d90206395512c2ff29531e77e1bc98bbcfc08016c802cc301f5ffa2e85c08b1dcd96babfd368a5adedfcc400e78a28885e8b1423182121646040317c9e8cc414142df0a52738e8925a1044a775181fa9319d69ccc114b86bbcf2db920f491b9fa1c3af3a46e7091c4278bdfe41df74c6993c53e9da75e6e1904dd80888ea2979a2a7b84dcc2d35a47d1b0ad5b0357046b844910cfb0092b716fc2668ed4ab755cc2d2166bfbebd2d5abb1d7fe15ba109bc1278e852861821ea4a9d03cbb76da9461c248fb895c27323eed8a5af21e5b382c7b694adf2ab08b72d790d7ad77e8eccb0be08fa44add49bb38c7d5e16cfcd87df0484828d4a9704b0b86d632c7d401b0eeb569d1dd9e655d8e264cc31f30b5a3c4a228171c37c4ce127f9a24125f40595d6be88cddab0beff2db040af2f4077949c15a22092324278b64eba6a3cc47b7521eda09181e67270ca84f6fe0097f6311f973994933672334311f3a4f0549b42c94229b54e8cb3dad1685b2bd65cbc7ed7e427dc0c180fd74b8c841cbcfb3e3c1027275b1af548ab1bdf144d6e7896b8476b422af398990ecd740531deab3a9876be7082d6bb28f6861b516872a11af2d279155a32ac33f40f2a0bda0f4c32483dd7b81d0c54f577cd33abb57e11147a127f053f5682d2d64345d59f1bd1bdea41b46d16ac81b16740ba7de8d44db4b0494fb127d6a805eb494b106e7cc53580c50eba1a316229054b164051e1c54b072dd79822ffde378ee2c131ce8a18dd7eb69dadc6fbcb97e7ac5fa1a8a28bd85ee6e4a14072722f5eb43682ee921206442fd8e64bbe82631f8dd0b885afb00760cb715c58263361fa6ff9e6bb83c0cdfba0dde5a8a4d6d264860dc6bb02a20e4ecb60d112e621e97a34326ecfa93800ab3d76888e37f88e1dcab501caa579099eb559b8e4dfe9688bafa925b279059d2e203ee800d99edc0e5615ee823c3e79fd9a02c4ca81d2c917c6917e9dafc3a9851df7a55517f4e56d6a0bf44b6cd2df7c47f0e2fea2ffebf364140c9e11602de0ee977aac6854d54ecc3befcb153fbd516f1b523054af589982e50fe756f0f4303d7e0cd625516a443f764886c7800c465b251f7f80c25806bfb4d34ecffffd796a6185bc97439142ae82560647adccba20eb0781f4d835d0b37e99920b12f2b92d71d3b932987da4bdcded28cb26cc699f980c26fb3725b0c0574b5360df8ffdb728a89922a33327a88d7103b2ebd2a89761748c92e102de5658bad92bc5843613e9621227ced53cdac9cb4e92b50b28ff775e47ae1aeed736d05fa2a51e68ddacc5fe80675458d51d94e6964f3a4e10f662844fa86ced2139eed0555f123451b526f54612ab638f6a639979b53b4c2f01f23a412b87624c060f2dd6de92970d0f534c9432bbb2b03d4e4eaf7b78e8170940b498f236592088c6e7075f749f8693cb97cd0eec6742c8867015b00016ba3138fe379dcb9701c4401aef263659329bb93a35fddeb0a392e94ba35cdbe6e94e8a776ad4f50eb57cd87991580b3882fbfa6d21048b59fa68f607ed30647aac5788fc9127479746af424f551e4204c00fe6f433189117cfc070a8374c4e6f285b2de2cf24835f8f04f736adde2dc221344b8b093030b875b78e331e8d570d08e86cbd81463869499975887feeb539a01a85781c72bc9884581773197f815e2a2b37f2105cdf9efc514468d122f138cfac711590a62c331a276bcb71faa78d02622a7eb07d0a7ab92215a868b4d53298fcc50ed4cc6ae63b52481dfbdbe842c8698ef7c0610fd271c1edda77bf08e64356aa5b4ec57f88160a63bedc28801e3e5478ae44a3f25f80bb024ecfca2ed818369959c260768961a771567b71e0e2a867a3fbc0e50e242233a84bc7affffd0b3cc6f097056adc4b3d2af80e63b817301b366e558079932014bb0005a2474f74d638e527899927af8acd61eaab3c1f9450dc6e256c486d3e579822ae141c50149ed900b2b5139a12810d143f5cbaeef7700a93e12dba8683e041fb46b842d233f147520c905466d4035dd30cd277211703ed868f88d4ceda3486561416177bf6a26aa1152e62702ea6fb2e0e79705e4577aa7b3b84dd578ed44abee4885db3f5e9844051c42e38173ebc38187416ebb85edf32dae51aab866b41395b0cf650a2af7388eeed6005286e09e9fbac06e904ee9d7d114c423bdb54d936f542cdbc0f5ac7143cc03dbaa17161757a5370e24d90f19668da4347314ef96c363ecd62b1d560d67788f71a88e3fb78a059200ee5c0547b5eff8d54f107c478b6f102d1fcc3b479e1b8305d6e195c8ef0387f6ac64e81187827db41695747f26b0117a8f7d4b8023b1d5a374e0f48d0c91382b286da5f90af229b4da73e97842211c50d799ce7173b7495c489d1697a661113050544cea5a025652a0c585a267ce05bca6e385f1c7fac001811dda724f5eec3bf78f915f40f2c328bc7013ecd8ba144f4752008b623b86bca5cc9f64afba926b62845aaffc4a0215fa36563fcbc84531854463ba709c5ac529f61cc1a517cf6a92153f8b1f3bb279c272e320b9f7801afd1a2aee4e1a6f54e1ce54fe91252bcaa902089af0aaa281bb2051e55f4f9b1d87d081fe1e3de720bb62e39a51ccccd044db061e41e07ee483a38f51a3c548a7fa8b7df3f325988b44ff10a03ce8a8033f3c2de476c30017659a96d175fd02a7d845c406e361150ec317db30c98fa014d972cf9a40d61010af30be0c44276d0e04ed95c118a6dc5dc3bb9cbfb2bac063153f1d087fd43940f06d44a1ba106e258cf397172b5807e60d8b47a2bcd4e5ff3afc46733096bce4357a0e09cf553bc63eef65d1e0b626409d05cc0b0b4a1c858045320e5d830650a4a89926fa1041a55a52c6c1b66b3d67fa88c1214667afeb01075b82d74effd4fdde4b35c7af6bb41c83db4a7a082e52cade69838b7bc81ca5320e675810710df43e33f0f8b31d377575fb8839299250f61cdcd624fcaad8d5ca4151f5625127af2102f0875a206c4e0e411199ae1f3bc486107eda2fd37aa3fa39c963d6b6283d7c4fa8a6f48f791ec8458ce4a7a0c38f08f3c5b84f7023075ea9ca281df1126f811446b287303248a7ad8e24ce790623f7eaa7350d89a1dc95f6d7722959e2edca3b8d78c3a89f6ae184ec9e0939c01876342f6da5ebcaa0c07fa5fe704fb9247672e903caa34e4ec0eb2d6a9ae759da0c37626e2da48ec512a4ecf03b1876004179130fcfec3e05081d8b1f95f5c2538fb24bae175de500fb1178f90a1e58b8bce89de3a594a36a33f31a85fa1aaf0ff269a276e14d4153a4d2ca37e4517af5c77b2a3acc61db5a2c81a2b4af2f8b7025a13252d7592968bcd9b1c45970ecaff4953819ac65a05d5172732c105fa7b8099cb17c4c5294e331450fc4d98f02eab0337661f61201b6e0b265893bc698abee47a3e023a9866836fcef7be6c63a613f9c7e6c5d039140fecb43479c8a8118a6d237e9553a1cfa451a6b2b2bcb31c58f21bd23642f5f00951d157dec0ae06e63b9fbc6a831fdfe5ac18e41821cc4a4e69060ccb258a5e2ca601dc00b7aca1750fa989547a00401db9f1eba2db3ebae94c400429a0787ff83775ea8f5571a59ac001fadf2d5d93e046f1b0b4237f84c19410d84505107cdc630d288a9ab78ce9fd41866b0ab613fd02aaaac682ad145ca670fd3aa81916a1376132187727846180395ba36eae3a1f1857adaa4fbe5f7821f4616f66c21937c3fe460d29e9028f30eee197bc36c71a02cb473258f6bd8519f0a7663c8bb13b96cc8d6eb0b2611e53a4e2f0b1bc077e10b4c6b50b4b2e60e019df07c48de9dfdf048f974bd594da7d4d98df93c110d40c2f883d312496793b2aa8bfbec8142f9f9f95b1eba196b42b11df8088c830fe1affacd072061b9b17c7b0cad907390dcbe128254b351910aa464a47e832cacefc42a96a349928306edebe407542af7ceccfdbd7b5efd82538d55d1632cf2fc97c9ca2a78acc1bc018d14abe0d495f661c2d244e6e1a00d9fb6284342c0a92f7fe08377160d9a1c34fc2d96b1afd4c9371672412f7bf3636e7a2f89d12e30267169d4d761e980160ba4a617b405f69ae3db087914e9aea2ae272c31db90fddda8b43b2544a022d21c0caa4d2a1a85af6f186788bbac6138353d67472c1504037895062708df2941a359068351c6f520e952c1254379199c513c8503d58cda700cb308ec7a13d3796f045096bee2384b102c15e0bee2a540cbc8f3491a1705465c2293166d543d397a7490ace4e5cc89db74bc7d118515fb076a58eaa14f9355fd2ae6316f6a880e201c6d9bd12c72ff9b8a6055b23bfc6f387eb94b3abf0396050015503f345533c4e7c0294f61a8e205b505b0efbd0129ab687729c8e0bce27fbd14a81655286435194ec834f8d966a391789bdf793e99d9efd58b5ec1922a177bb274856743a38be759e63ffc2949ef80ef21548794669f5a4f32a9ae37079b5ecf204c7790d04169044568b94538f03ce187ada18c8f0a99b4dfd3ea6ff630c0a181cd099ae7d5543ad5cd08b3697fcff6275b1dd3c55f1105b0c204fc3d1417313e6f1079329c181435779fb7f4c7f73f75dca9607da546771cb45adbf703d868c6a8de616c7797921cf615355d38ddf8c1da0671b4d95bf60ed0cf04c1d9d39e1c73ed44cc03e21f444e5cb6a4fa50fdc2c01feff344bc084e2eb1356a98ab2229012eb036465db43a0ecdb50e645653c74e4e478bf2c93707e663814703fbbe691172ea6453d8e754684fb11a507645c0bcc587e656c1bd1421f8aaea5458fbe1407cc22fd141be4a01fa45d970c8d4f07c0e34c075de22a63dfcdc52575592f0b049df1358e765f9d61f88e64959d844a55a66d66d8fca872ca9d7abd64902a1a44a3d483b976dd77b358cd38c16f474765cccee4f67fa00440faab374d1ba1f594419f3d3838d9060ec73d71005d191419a4ba53cf9a4526b0a5061021da2101d92c62221375ae5a18fa1ae6a773018ae4ecbee351c3075cab429c2e00b80064ab6f2676ddd4d5264394c8aceea0f8b218d7a47a8c8ea4944165db6c352cd65dedfde67fb912797fb8d2b95dad0a12cd7db9f4c1e70178e20ea64b264a5740a7ecbd0ae2fd261e5ff8fa9b9dc326432ed3e105baa968c82e2acca1c32dd9cbcfec42e26f9483cef87768cacccf3391b2793e68f62351162beebad1712f401eda2ea1e907c95f524db5c0e1cc1e7d4281e06002b3934fa9a6e5cf02e8b4cd041a4d43a1b540b90c110d6b94261da36395a7994770300cb19bea17b269605f634f8ec80600b5f8668d907bbc8ce13e45a90f054bf682df4bc4bb9404914fc44629a65d1c5bedd5c26eeff983c215566e0e14d9a0ae719305fd23d39d8121652411d5d5b238cc0a888a42002f13fad2784640b2403d8935f7b3fc76886a33e5f62e0af51b7bf6f96e3e7aad1c406c46d463716c749a83d1d886de96b54d6a9b95f7bfbf2d6bce886454a04b26ef840e1e55085b076b1e1a739eb2d390d44887f7cf728dd2c48d71af8300eb235249ff32be24b8893ff43bc6554d6189d19764a0c35c2a1ed8aa9c75b9a105ec37208e0f2963001564590f1a89519a866c27b7ddaf3c263b9910b9b3afe73730c1be4938b9d680b4d93085edcee645461dccaa39a6488ef1fc134233171d5e51edcea667b2d8a340d84e08712711d4abaf0e2b0db5440c48fc1fe46add2fb5f35f056181b7e7e5bc214e5aaa378bbcaae17441e977eaf7cb217d868ad9b367a007931e489c480a4f34a3e1b7c2f4c722698a078c7605b1c5116b02e55056239f211833e76fd5647f12f2d281bb20883689d3310ac1cfc497b9b796037925b596dd798207b6053366ce2fbf54cb86660920f925d9faa5ec1869ff382bfddefc58e88bcbb8f883c03d43cf99d8fce2d17e8a9a39e1d98a29948b39ca6e82a297fdebf162557027a29043ced837efdfb09ba0ddf98a23a99ac5c444d8ec51a3f1de9e79cbc5f79bc9322313148ff450550dd60e1df9e90dd757acd1ca2ad1eee82f9a33b6228b0502d6502ae681ce576accd4d7811abeff5907543c4aaf64e684ac435e6a49842d75b9f2ca2e61c423bff10e84edbfbc45dab80385c74263aad09c84b5a68de26a9db7d1b9643009b53495748488b78ea1a5c629b56f4446fb8b56d19f746a14165ee82b4a9bc0db43e1b9b9e1d2e4dc11d490e85120b4142911e3e5e8f1a097026d8dfda0ab997b865ad86c14121b602e4f756230d0a4a0c4684faede77e40d13781c08ce7fa1f12557f45c422e93a549e95a63b09245d586ebd18f182d59a830461465727b5b399e97b1caeb8278eb388a6202f5a3e2f54a294c19ae61a4c9c99b81240a491904c7d96199b6bd69d3d96b9dd3e1c4d914316b6395d3224d66c474c279bf352d21ea694ccc40872d2a2340702db0a2dc4d2e88f563a17fd519edef01a257d90ecc2f4204098e37e022b311e440c374c851fab37424962a2a64b2dab009d5cfc2d6e5d3b39a6166aa6b8cf29cdfa5373453e6bca584453856302facf016e7ff3dc145c228c325451e3b145ec29cdf3e904e9d92782c9885ea25ec22522f112dcbccfa077c6fc829f93066582b93d3018f1515cac04c92b72011797ed4d1ce5c579c67eb48636eebd6d05610f2e5fd6bbb7800ec778ce69a43c01f63f703dc992ffca69652d1098cc2895caf32209fbaca20fdbe6b1c7a983fc711ace6397516461cefa9f196328414a27257fc26f0e9bbdf03453da006f3bb6944ff622d9f52d11bb85b262e5110652500da2d60a045f0e5518f6a573e3ab09db66399dcedd1990a2b411d2d9426f52c4e5e2020599a580796aee691c00edb96f7d16805e7f28e8a83680616234d06583c0854d55c6996ad48c80935802f4e29200bef3cbea57f775cb17d8854d936d4b9681b4323d501e55953b62c1426c44004ced9ec3e3a512575b23070d8aa0a29aa0e44151c46e94e5c9046ec325d20228693949b30e4f18311ba8a186ba085692f09d546ff225044d1710e0202a84eb3309e13688113575fef0d09e52619e02fffd4d4abf9d2c10c9dae08eaffc71d7c1e6e71dec3a2ca546b05144aa2c852769089522192bb4795a03e338285b742a14d07957d227f7029fb2d77f9f2aec98d1eaf8177b251889581203eaf24292af9909d289dfc7290dccb5093da209c34d5407ffa4b882fde154c35e72bdb2099e62f69ee2b6001ce576193f518654ec3729ae5c06d64e96fe2ac1760ff1fd7fe6e9eb106352dcc595628484d1c1930aeeba71394eed2eea7148f05afca7a801493271750ce971ce4503695e9dcb52b10a949059c266977b71f2bdd8add7f1a216f9a7d97780e2abc34f21c222e1b85c73b9cbbca18ef6f36e99394100f50a68bdeb7878e5a712ef46b7aab966af1e66c851fa77171c80380d5474c6e307587539ff594d6fbf16786831888b21502318efeb1807a0977f361f6f6854a523c7290367b7178783e1509b02d8bfb935b1149dfc89f9e1f50d02ec4330899264d764af7ba2cd08551f7cfbbcd33a5e4d9569f47ba65d5cd791e498ef84ee551d6157e4d8db9a129ffbe2b72b3427f5f0e87a9c385922ae149987b4153ca5d05c91e607fea7b0b0c8d2f5c83ee11fea6027996fa3b02fd2e05ddbd428815b6763d9cc14543fe4570e4b6a09fa3950a3fe4a94fe9ea7468384bb3552097a3b388f7afd117c4492511c0f083ad571596c5455c12d1d688b49af8c779828c96f15c3524a9317f5fdd3b9ca888dadd7e430ec0cb12f38f43bcf6b90da8266a41451d4c1d7eba255b0809656e21badfd11f890b556843dd828b6addf7bc1e6806c200fc41f07b2a494414531ce94420027c0449e1078bcbfe853e95986ec17e4266f066bb6a13d5733f68a4a1dc74e41d3503896081e4718532b6d027e9ca6255f1b9deeeaba93ed17ed59c1f46f9175db79246380149113bdc3ec3821cd1defaa916f2bd9dc967e878e732deb1bd62a752e01fed0102910ca62073284a088dae6e2eaa10fc9290fa8fa07d24e24cccf01884b53fc3bf5df72f85e4d5ae0fddc2ba3712a28e3e2f9eede0d69db00cdf745e7465c934f3b5cdf67222285d138021fc56e993072064294aad0f67f246467fa4b65e058144f515ca2c1cc65993d2d9533205b1f86911c4534a5762ea82b4a9efed98b6d9da162493560f1d13c2fd957740c4e337d485159217009d3ae2d5891f453519dc018b059905b1bb8e7fc66824d8713358498238a8bc6c9384f60b63c20865d9c2c8f7aa51cef667ed871840e61551c043c046e6018900e89cc82128a4817eb81c51e0dffd99f58a6f1d4321847b10e03360afd0ede1f992b028a5806dcd017ce7613e34485d6db84155219abe1e33e6f64b95ab8b99b3e00285481002046de4d6393fc90803e98d8fc6165576f4a4255de06ba20cb5cacd3eb643ee92b0adfb74f670034c4090d4f1cb4fc4d4d471449da4606b0a3e0189e6396c0d19c5243b03c4077562d27ed7d1c735f45dbf68cc758e42beae75b8f0580ee864b1cc496db930dc27d39af47bf06b6325829e7e58d5f51af620a775004f84372afee20f3afef8b6734665dd7386457e6ce4590b3c1e2308fd3743a88f1d7538827f9fc60e1d442c28ab60ccfa8c9cd3b784d7ef8d414ece50c49d1940c0f8372df7f3dabcba6a0f382d80f149945022b20d36247f563bb4416c0876a1f0f46c246bcef86b1fa62714ff216e4d81b5a906da1721027090bd8d7c52db7facab43a06fd5095a93ccafb9aca7faee48430fcfd59159c7dc0e10d3cab797b8a003c3a4025137958aeaa417c713aa1724aa4f3f7e0a7c8d8a0bac86c2285797b894aa48e940acd2a207a2ce5352a669a839db1783546f4f6dac1a271691594cd2ea0a606dd55037775bae3d82b59e6d10f0608823d8233feb8e1ad28872ac1d97d89b0a4e20c96cfc5fcf74d191315947faee0907ac439d970bbb25aa6411d3aca4ba3b0ac89313914d6aa317d4561fcdc49714339b41481194de487006b9ac02b2c16f522700325564c8a8f6c75373e9c46786b6564a698c64572dff7ff2f2f12cdbc3d4c1f223407221bf0193b86b17f5b6a13a61f927c465485464bdc2b4879c4a76ec94cf32b91ee441b9737ae04799cae69c071e34337a4b396ac18545389d1bb6069abfc5980f4b0c8317270dc1a68438723d9107fc5d6297b22d87ce6369f48462931dcab5d54da92054fd8f2689920c8df18088e78b8ce51d60de25ce7a9acc1439275599474cbc9001352316a334d7569b970678136cd3602d5318bad84ac78df31ef667185e485f65d74eb6af35947af175c71000166facb1746de0843efa670cdef78e6fcaeac07ff427471f26ee78cd92f07b277abb4f993070ca14b0aea0751dd6951c9f6d0068cdca347162383f51a2506b750c06602f2d5457cde338a8e03971e16877cff4affc370f22f228f64a410faf13d804b206060410221f1633dce439008b1667123959341a8af102e30b3f8e7aa2fd12d7d2961cd61a38958d4d96f5a306f427b5e7f64405bc5e12598367e23ec81f8ede0c72e1057e884c56a7c5a33ae2599f2d799807d6a9b66891012abd508a4fcadda557b8c49bef835fac04ab8e0a62197eb79f03d636607cc73964147ef316ec6bd64f8cf84a71f06262e30fd72a875cb800d68755d1f8a0b90686894150e3993b869a031909af44cbcd71cc43f3996fe980da6d5a13f5fb0f6b7f6a4b02e36368f9b0ad91ff54de641cdab2be4755a20f9bd943acf45be118fab0814c693464170f06e9c3fae2e6d4aa19cece0ea4b0873db5e3097c7aaa7b9687ee28c4c0d162112cbddf6101c73509e069d22960a9a510d9f201e7da004c7b6a2f45e5aa3002d72d8e1e6a83562ce7d5dd418f202826036058107abf8b60429b923224f5365655b86cae460dfc36619e51bce0e80f4892dd70a1d84919867ebaf749c27c6bab64010e537cd62d5ed17ee3e2d16bd40b8dc3868c124af2d8ad2ef1845a72d5d91255b356dcbea3908f4c4b6091e1176b43baab49d23fea8ea2e6386d18d773d55c9641d521749223b0e834f213510231cb040cbb74b3e62ba7930929cce0dadb3fecd87dd2c812eb22ffde1f47d657263021d0442e5fca2e620fa81bd06312eae90f903d2334aff436c846908022d583ebd39de49a50110a8a0722aae31a8dd8238cd3ae497d8649fa4239dee13746a6d2ee9d1dec10d5f72e8b82ad3a82b70fd615a42ba4b6fcf19e8c8c604cfda1f925c6ea009265f64d940f6f818efc8f39ba44f59a76718aa5e205e1dcbd256cdf0d2deeb522a2e29b4249fd90c775ac3911ab88b2cc44f80f668c836d674eff8ee3903ce96b99a2c32c448b2d86421fa8490b0b1430bb824ca01ca640b362f199658232b5173cfd051250b89659bb153126f4e1743d31b9113219970b1f8e2dfacda2f0207336c589f1789565e8386928f73f6b1720a9a59853405a4cea5a2d3b6bb78510cefdeecb05b4804bea1eca6b846b9f0913298546c4fecb20668d768214d1fabc9714301d72dbbd44c218a33adfe81cd9ba9ce5db24b3a95005dc24f2f70df47b9be7223058d3769b11256304c6a186051f064c608f131131320ac1dfa7506c83ed2e9e38cc5e7081646944145b56516b37e2b53acf86881615afd21c9cb3e6a040304fa2302f154f79c0a666c42a8dbb4715e1c3567a60d6a0f614e7880623203c710ad8b490b869834d331cc86ea308e90b8f26c30801d3c5e819c083a9b32d79b08ea7ce945c2048eb4ce44e3c148a87b05e971b691f2790d4bfe78413a162937aa0d941a72dc990e4f877ebf6fab5a98f083ffd551834ba8e1dc31f7cfd020356d11e14b960007e05a6ee00720ddcadc881c356a69dc92a0b9f08956e6dbe8e6e765c68b80f70bbd90200f7611a3ad3cff8a5d38cbbdede2716d784d873923ae65aa881803b62c88dbf4683a7a7a193b3877abfab5e1cfc9ee12f60d9528d39915f1b696014202e5578d0327043823276e011d5e1439fe5c8502134ae2eef8336f4bb1d3e34d5d34d1aa64975502fafce4002cb928bef1eaadaafe9cce1ab948557aa7e1cc20f41fb8cbbf39e0ce920aa0726a7d4362ca62509a075e8033b59c40a08a36dad9e7acf8ccac30b00f9a5e0b46ec81729340646577bf8a6853a1c733ab5de4c66d4c40d4a5216dca72cf2b32b45d54a98ab738e86b01022c6892684ea81865ba4fe93912a33c447188c232f6f40e4dd80f0d2a83c111bd813f410e1ab7308a0967ce1d2cf46cf67c2db437d22ece3b0a31e1c678dd48469d7b74b258a44d285d21eb46a7aa774e428724e21dd7842ce027a9b4595dd8f0d14a73243d32468e0dc4c1509cbe6515ad30f19bfb2ea419e7f4ae3f12d15e7f7709b521b8e7bcfc0a13ec4c54c4052e3e9a7055bb9d117409551e800207a408a8a20e146488935a4b883cf5a486826f4abc97f0335c307a77d001177f70cb4591b5683de3c1330b69db45d3d0ccc7b91fcf725a916b8e1e0dfec0a99582ba2d039925c4e288b141fe847a50e8cb1a780f2299ec2cd8b1ed12767b5f9f337d85869f23cef6950a98b5af85708d1cca1fe5e35b74979aaa7ef00d5f46148631e52c7068a5ae8f206b5527aaeb91c5246c657bb95afc741e185acba79620101f8db1df9b7e37f03d7e1216bd721d5d63878b2d6620e4cf8f126526f7408fc869e74239c5ce79fd9e680733e85391bb7830800d44ba9c8c6ab7837a11e7f4d7e1ff51cadfd04386f56c8655d7f1a2e0b0468ccbeb5887f7ba3c8ca7ac3d8bc0fbe652a7a3e42a03be353eb2e9e7ac6059a47270eaa3e49af4b9a42ffa615078ed8a650ce561863c37e68a44139f486ecb1dbe0ab41f957554f6e05d2536b6392108906f415a961b0770c6c6f2029a621a8d984d822b45296a6f58ba2a83f77bb287852b2973d7eca888e8efdaf5946b747090d2ae6db6f597e82adb6c4b17411d9b48a43ff52e50c2bc04a4bfd883a0b868c938cfd644cdf16c4a20775261831e84664ce8dde4a9f2ffc0d70d306c82d4e43fe278e32977c61fbfba1f285788997c968e900fc1159d877591a07b2c216445736b4ffaee8c3622d2d36a6bdc675b7a41168a2aeed9ce7af6f24c48947f2592988b5509ab85343f002a10fb3d379e941bfaf9507c62b6a34e152253d38f94934e1486dd9a16e3ec15b4c9fa21a821ac044ff95ccf15f9178011669d5568ef1fc8745a21748c2ef6fe96122e374a4043af06191d41f9ea2763d4c3a2c92580e7ae4ea72886409b15e2f8e4f36619164821028b4b2cc30eaf2e130bf8129b6ac2f3e2cd24ad44a32b891519ab08ae4d5dcc9a537438931c299153a57dc4023114c637ea7cb73c07d73f2169316ca43de793336bbcbc88122f2d31048f091dafaff17be0d15abf108871a2128908589c3e9ce2a03aa1d272869012f057f5842591d537ddb4bd4e64a3ca3a83cdadcb2b9ad19419a46e328415be0cc7884cf5d32f4cdb3538238c18e5339e140843e3d293d18f26ea8e2dfa9d87c182f441a0e6320fc739174e686765381d7e870addbc3350d5b31bd80df889850ceabd1f0c75dcdf91ec0e302cb9dc0a9f82c5d6b3698ae9d5a9a2eb41f9d7926ef610bf8ab1e8bd182ae8dbbdfd327f03ceed2c0dba8e2332a6d2cda7bfedb5604169e74f6a863741c55d93f04b4213294db0d0d9acc515d2e1c941cee3c4e5760234a18f4b78568140bcec3673c436dee2e6dd9b32f4618e434d66c4af930f8427fb8f3c04d977dfd39390b0b6bafab149cb7da79f4bed4515f2cdbd6cec321ca3da91a3ccb81c6b87e044f82c2e9eaa7d53c11355a851ca082fff00e7139aee82b86937c62f3472eded8f58235a368301d7c18e66a04a690782928e913ac663af97753267cae21fd183279c6ed7cd93e115d976c445c13888a8cc2a71c2ad58949fd69c2f31c6d9b3d3d987b15cc20bc10d17c8a735e400608eda9eb2600d96dc06f741a86dd8edd8a0b5619e61845309e2249089105a68787279936260e6fd0d9e6ff06c76cfcfcf99f02e9614e9c7885f776e48de83345f68fca9234adcf506082ca3a155ba9355fdba3de697b0309bcd50b09fbf58698ef6cfa61a8704669e26819dbe1f760d50c828a0afec7529a2c2c40972cb436d64bf0b5b4ff5a43a9c1c001d739e0b065a79eb3b049d795c11db078381a044b3294229c04880b0db2dcffa01b2c46033c780564f319f7b417bcdc6bba2008a5337d64b747e9d078f0fee1c74cf3c6629b5fe2be1400f141b97610406f4c330e70152e5c7c312f193f3747a2caace282461a20040f72bb34730e06bd54a2d6c3df8149e77d4b3e2dd03b2120d5a1306e80d46d2faf1cd1ac8dffa33f57b9d78dd6a5e4bee917804acbe84962f0801c95598491596a5829913b2a58dd3b94122f99dacb0e69da549793c38ac8f7d3b45c63cbe113db9f94cecf54022cbfa047e36ab09ae759799bdf7472b1f86c7c4d5bed54ff7dd90994d52cedd6014ac48be1847ac94780f3b2b968023c96f5de29d0d75426c898f364bb491462d72fde3ce3daa22fb2b18d00624af40f48def4b9d870ec022d0327a3cf64f74ec07b65a1ccf097cb77bb751cd454642fe5cd60bc04d0732f31a169d53881417bee789c21c38bfd6b16e70ea2e719c13036068c40cd1e82acb3add98cdaa697126df892fc55e2b168fcc6146b3c55ee42699ca28e7f7530fa6c7e24c391c71d095170d01439b7d18de9d1a56991739ba849617f13465e38fed734555ba0fe6805f587776011ce91544079ec722b1ed388086a68262a63fd23c5fe4358bd167558ba1297a7203d64bcd4bf46b21187fb3b2c2a5d9d1dbeb4be45106cb7bab74a2c92cbbe494a6c435d981c3eee11827a654ee2a758edf1f926a378cff7bb0352245ca5d2c0c0b588e53cbf23a9df4e27a83575333589dfb2a83339a0d2fc00ae74a37de796e885a3d556750f242cadcec86208b7205d3651fab98072eeafb603b31d667f66cb5bc986993d2a723c4873e19e023eb416b9baf2e2cfb04290583f05c899dc3a1b63542000227046f957be02b41eebcdbbc02b70e1da967560a23b2e5838a29200061f6c9a953ee4cbb6397fb2524a1c5084b100ae10d686204aa635a09c9c61d1fa25cdd8d133e68ac4502a8ae2c42cac90896ae081ecc2880246f25ef772cc9ebc2c5093094bf4596ec4b76b3f2b5cd40974acee7fe6dd38b9054c7e659b95678bf7a2c10adf852bfc965326063ffddc0c785d2ea0984d311f4c039cc6667325183963dcdf361f4df91e04bc30ff77946617de2701811155e8a9beace0dd9ad9d6213a60efe764342704a84c1c02f3a668612ce7cc91a8308f5858297aea7358c4a6ebdab366f274a56c155642a275eaea41b77af0039ceb9361d7b9372d35431a414aa2b76b09003ef55453d0e342a2de43292b973337a424619ec41d294ee5ba8b47edd66e7b7f7e3dba290d5e21658f869fdb03ec70fabb6ef676084cb2114ab106e721b065c0fd36a71b0a6c242b1835c7a13d26175e94f447544ed889068bd1039ab8a60eb2e7eb796dc695866e56ed9347d1e22530522439dad5e0516564d8c118934347742a0e5a5122d20a31ff227869a6656b526dbe2da227882e0a4c2da458345c89285b05001f6dc98fc33b539aa8b3b3b020d7df02496058040ae79053f1a11cba673b3406d049d96d033e1d8de7d2ae7ade23780095937d89136c91b681103eccc84b244de07de0bb839d4f0faad0f1f66b41db780c47da089d93dab7f97d00100460257e0f2e19a9580871fa718bf648f626a45ae93ea133d715893ea6e711b139e8eb148c0f65c349b96e58c54108573c8e172b010660ec2b67eddddf74d2823d33b708c4b3e39c7ec048e06c055d341dd07514ce3d10b0c342cd49c8c6345c6f78085ce188abcbeaa887ab3dee6875c456eea29f74db4fcc9452d0be2d3b7122ce6b9f46d41279b25a1a16654c7d6ff34c219633c3aa0105444216f4e3712e1d717b1b46105200247d5818f9687f12507e91982f5d33505712427f40863ee93e5fbcc4eac9238545af6cfa2533c8e2684dc1ce93af4a517e7b207f54fa963b127b35880f05c043cc56825ef3d0ca6fb248169150906288bceb452588950678dd8ec806fb0590442de4781a0a30a04bb377541ded2760008cf1e5159a1fc68a3446318b21a9162742e0d84c302e8b37b5357591776e28052139c0a7c96ba96c1812178000399dfbf5a670a32ea3159e087a01c7e5c8782929fd22ac2307410d145b02044ba6efd48b7ad0b0711790aa06bc4f859511d7f45cfade753b3544f6d940f0d5f02803eefd32243d23744c42d8a68a631a16ac098d1885daa2d782a10b00594394df5db78d198f08baede4412492f0347a0c205cede26808a582ecb85679aa02b6c24b454bffd5ba5ec5751b8dda1b6ea6e81ac6e80e0a76ae81557750917e7d62d5973aeca0483f253da19f4375d09500090ef37836e61b9abc4e8f4f97c419308f26864f1eb03e1aafad4ac73e66d7ee4912ce1923e9842edf2367531fe5c1af8f60860658a7e199c37949b17e1ae974b9eb1740bb9cf0fe2db6fac31ddd5e01777efe40e5b37d97888fa55140beb5c6268869773240f8bafe12cabbf5fa75af5ac9857dfdbd57f2235aa2aeb0246ab1cb0eaa6b2b7896c7aea26da7289b2841d315272d670b9836c676bcda4e09254dd2e76433d33df6447f8194ce06e27559825eafd63799728e4b4bda951653046073f69fd09f83a1b573486673b97336ed3967372b49b81c5a9cf2d4f823bae06621d54c91098e5472dd4e9a2d4b47e60d8ca8875bdc303c76571c1600b65071bb72ac177b964b9c1dec0b701cac3e26c4335d5ffc3318815338d4c96379bef8130d380e0a6c1f6a111cd684fadad2dcbdadb53ea5ba9c55f748fb579714083d0a849d205ee18ddd17eefc33a70720cca538852e70a6a1b203d17c2ad810733174907d52f5c420f76da02913e6a565645b46a325c73d0af7a3678e86bbb75a19bf03e8949e6d4d9934a38246123ed8f5ffb1f750c85a6d7d4e561c44acddc57f08b30406cd4f86efc37cc6dbffe3294959377b36f50da22f3ce52c4134a060f9d3d05fa56d2958874096c76c81373342cf14ee3060c326e77b6eff19be2cc1f0518bad58b8151097a98c10fd5b5bc0b42283c9b0a9022e859aa2e53682a10643e4598a724fa4678af2b55fbe8b915fe2f6268ed7b2ebc2ee3362fb6a40f2c1238d18ae6574d787adaa9a4b1485eb537c61348669aa6ac6f7b329ad108fc6d1c8b04c6d39288d5804e934469a24520bd3c6de52e43c41482d9384c0e2e94c95e19941064e90766dba2ae87be8ca017b72d2b38441dbeae1c9a18b8dba66a884b868176d425821875105a3a9dd71bc14f2fc87dcf096672a6a64039f8b76dd38fe4e5953874527fa980ee9ac28b81e411d715a477256ea55ddfa97111c528a017fea5e6c06b52a9112c75b8158a1b03c73af3700a7cb0a3f3179a1a23ba071216a86d2d36a8afa34a2b5457c70895bd802c89283ce92a9dcdd8963090b9f42026f05e40c88bb88647d7696a500470f32b0944b121ee88aff03c5c898f4be9f97534d239959b063128116bb66bb2673b380e20bbb91c100ad97497196dd44789671fe23e0c1bba99ba9539998a7f201a8b237aa6ceae9d97417357f780fca8114ee4bf251557c5cfc4181d7f5a1b5ea279a6ce336186e42fa30105b3948e29fd2da0c4638231c1876960c3adbc727e8fc7f54979934cff6dc594165aa0df31a6192128b9382a7af8c655dbc0af6836ec47d939dae2fb039c3c87f080a1ded040477025d7707c135e3250a8184e68bf7a051fa3e4b2bc88eff829a75373c707e828ed0e2078306983ed2ef315e7ffbaf81a2f8c59f53b8a930af9f6a42a74b61ce714ace5b612924ae7a8b616cebc1eaca31a8f41fc6de09d6a281b468b5165f433a20069c4d1eaaa5d784388438cd89f334d04b28ca574b0558a97c5d588c01567a3c632edec5f93aafb82de3b8049841256225f07e6c0e9aaaeb2c616c47b445faa5ed0a7a2d29e1432ca2330f5ec255c3fba3c49dcaa2b8d01d29351e823ea57b5d4e3b3a4826b73a9d4f1c514358463473511bfe62715dcf11abc63c082aeb8c14a029cfd277e4bd2d1f7b236e7797b6e09ed90c32e1dba4abaa9a7528f0c6c8ecdd9c7b708de5b27b45c07e2bd1a34144a45670013545a753b2e756b7bd6a667572d7b523ec240ba7808b046f208548206346e66fb8b9642dab6609b86cee2e7b4b59c914d7365f33b32af5335e1dae92f753bde20cc24910b0bcac2d533c288f8bc36d955f6091eb946d83e11ff78d8e90eb66f9f7b29573731393ba9241eea5a9dff0bd6e48f1dccaaf80e59b04ef5f0f6fb27b30757e9cfdec4a95d3c10e11b59db7acf7e3094745be8a78c77f0ab93861794c67c417468422e87a15f19e9f97786f8b30c7c3fb4bdd5e42fd28f8c19209bcef036470f4fb2c4cc12e08baa01e734ca8f85647e8f8016976490ea3d1346e17ad6c52efa107db61ececafafe405292f754c665a3bf37ecd814b0a217c370483c27176e94bb779eb37b9af2779d30dbfe887b978c5af4751373ff45b2b36c4d09bf68fe636009d7702a015e4e526d4f8ae61d995c96efdd8779d2207f69d94da6bc260543fc6e8f3419c182b194376f67527d8ef0c3b63f19426de5ccb211b24395aa4cb34399ef8a1cd423642fc4609fe7dc5afb9164d852227bdfecdac74dfbbe5a8e0c7717864b3e757723faa41431871f153388ed24169fb74e8f0ce59acb9338e7a40e019226890b2659e520427746cddc1f7007a7e57b0aa01ba337e509cc311f49838c66d8c1c7b7c476e022e95a1ce809cf8178e16b795a5a4386ebf241138f3b6cb7d4827f3a00765d3ef084b56968ba9436e13ee332c7a968f6ccae6e356ce996d64558c7d56b99968cbfb0988034e5b489c92d88e28cecc01b4356d7a82678c850805d193e0882fab10fc1e84c280d05f44897fa02682f4512b3983b8beca1e3f3c055c7ab20dd979658eb9c6f3e2a1dc68b84bf4fd850ec14fe3d73484f39dbf597e8c0d470a41b116051c4d1fe1eac90ded9008506e29f07bcad3e0dae6c8b5b6829f772bf6a371ecc1cb117b5cb61d2a55798863932423e498a3d728db62cfd1ed206fb374c7d31d3518962ea25af12d7fb167af19e20bda5b3bd67fa4022e63e0d19fc7041222f24e652536397f4d9de907140bc11993e644171a8eab0fb5367f113028e510282e8b57294e678baad8ccd008b99236ec0ef3cc825867580d14bf2518e9130b7d6775893374ddb80ec62dffc5562816e88eea5e676386787984c4f9f5fe0805268604c146d37f3f26d7d5996576049674cdef9834035686097af7a30342b4579b285b30e6220c2d1587f118e324b968894a2a7132806f3c4426188f69713201bc04be15b7bd1ce5489a7e61e57d09432202c4ba6f501f9f9d200bfdc4e184a29dfd22e02cf32c1d74afee51ad9d025ba03dc309ea79c5f9ce8e539612b0dc60959818a569e51561641a72b0f9f556e110b1d3991f7a449348c2a05b44f8abbfef924ce4b13af2481399822d53fe5b64ada66776b99f9ac0bd115052368d4bd9420bd54f99e31da2960125dddb89b2fb8b7b0643465acf4146bcd8966c6ac3b845805810a0909efd60eaa3136ed13db21cc65b5c73b28f3a8b4aeed2cac9c1716c0adeb829916e33bd1a6a3f0f8c166a096e089c03e3ae500ee78033d3322c81509f34c6e965c942b3624bbdd84054164f167b20d2c6213af614a4df81ce592f3da19a6207a385d9404600f8fc4c741c75dc79e4986f2973e28cca0b565646d4e8c58696cfae0979de8bc48d7d58a5bcc2e8ed5a7cc9c1d9eb15a504e4bc8c91ad8b775ace8f01aa62103609d93234775a5922acbc3c81c6a75377add716b64f0a06cc8ac038ffe610f4b36fd3f97968f9ca549f900c5e0f81e3660f150b957d96ea57c310a311b3eb48922e0df645dedc62d137ae071b9e89247848cda708a703cbef9ba67bf084641c285a8d3e90e2b7ab7daad80a737cde47be661370e2f107fe1fc6e814188053ed4e36039cbf89fe2f914374e4dfa6aa35d41ce80752549b35a31276274ce84ec75de1b446745ac87f843375f9e333604053960947fc1fb770c500a8e747c5f57f11c6153a41eccd6e641b907e6c95b01537ed8ebde49ed1d130c1150d32bef9b6a254d1f06b8957edc48ac400159e707024477db3c10606a68ad778e22435e6fcf6c7c946a3e0169b8b50b4f936c30d95ac796e2a095cbd1a18c8904ae47aa8c6177010fe1210a78f530f9b4c03bf1fab12c1c6ff3840a7798b532708951d9693f50e1e90846a92c5735c1e9e2606eaaadfdea3d29b267d1b4bc725b333ee7a14d4b572e7db26817603ee636f145b7fd5dfc6c940c1c447a597ddd8aabd55569538854a4bc5dca3f6a090fb88b563f62c6c7c13f4a5e124b03228926a12a28b46b5bbaf24072f57977c4c1093dbb99ca1e293ae69c812f8bb67368ad344dc79803f452372d6ae3791d1b9ee776f84787f86c27ec3185fff1c140ca1f443856aa5312058a53133b55f7ec5732904e17efbfdd2f4a10eb2ecf7d9696efcc5a1068443058a1c24579942cfcc34f347291cbd35ca5738d7260dfd97c65713c93f4f0382eb5257e6365cb1fa0b2d43e7fc1674206d34b0f4fd2fc289993ebb50d58b648a0373fc7a8504a2b352da5c9f39b79926de117e5a60d373ea43d5c15e367e441f9529d99634c45a5954b5f671915799703b54579937d46a5c65f702725b65e1c0d1b4ca32196468975546773c7b722e173bef2f72e7984988887cf93e9443a2a3df6262d8c144b8353040cadc6291eb9be548aa3bf5feee6ba5c1f7e998b3cc17eaca44d67e0b9aa9ca2805997ff68954d18859fb40a6a9b8f2d893557f33e78ad1838f961189364bcc3ed7eaa8638affb8e01d5a6e88e305112a2c1b2655641669063b2a154bf7e369165651a2ff6ca961a71ea0a8c47c083234881441bc6aab9091e275f0e4769596df00141ea528d151286752b4c79d7e398514f471ac9c18f8e70236863b8cb0763d6e06475d73fe8cf88d34bc231762669c086c8aca2f3582a2674580e675025e813e903e7f7669d3ba162e03b553a830131bd3d038b78dd85705d242296127225ca4b62fb064b79a1823ca33d4f9b2bbca26d4eca8212fc5fdb64d13138d3706e6649e3d3644f5bc9c7dfd73ccc41dbb5ed5bbdfcba040655aaa3c0fbc6e451cbb7a1183663e908dc1becf24f3d79833fbd462ff472ee30e1b9842eba3483767724b2ec8edfd6c4f98e85d306f62a80c0331c37a958886df6642a40adfcadcc57cb581920f9fe2e0386afb5ae098937be7eab015620e4102c2ac57cd0a0229e282b2b4c2fd91ca69fd481a65f9716cdac2bc49acc991d926035fc37d4b8c19ec22c09fdc03b6f8a659cddad85f9cd38e868e3f90b2b425aae47052693c748d68a39fb6f1fc94b9fd581cd445dd184565c79b6f7dd42cbad730934ab12401b0d50d9b80ea135e9e048feb8b123aeb0c431349996ddf834e029418f4112d0fe59a02d98a1346b723a8d9c11e7871a08cf2229a757a516bbbba683d71fd65ed3468551d76f89ea5db89017ce0872f1120cd52f3ab9848a3ac7cd8977a3b4186f3055871233d1b9a78005e9c1f5a77be38cd02000a3001d84685327ddc0b1574e40a90eb8997f73dc089c856ef6d90377c0c4c80111b19fc030f3f2df2df6fff354f263f6aaf777a7bc287a069ead70f49a9a8b59823e738e1d820fac7170f5592720d16c18ee512d73a4b715dcb3824fe9a373eb1a94ccda0c7a79178968b7726d2e137f34e88a0b3580c2cacd6f1de785853aada185a0fbb66f1bd0bbdcf8c10b00fd935d17f88bced1634f49b17038615c7a8c641d51029c6b14540ab29d39ee53a10a46bd3301b15c3e457b374340e29320ce2a1fd05faa600e89ca8c741fe22f311b14a8a09a8f993e29ece93606490c4102c70afb692023940eaf1b65a6eb482f14e9de4b8c1a69ee605d4edfc6d4c9e7f9b59bf0b77bf93737fcc3bd9d93c21756055637a2991c7bc3924c374a02c7ce76c85a4616595c98bfb07c3d35416d04304582cb51dc582c6f3a474d005e6c53d441f58f1f3c4c0ff2a26ad9fbdaeb5ba06441936a22d988f0e1da5fef7875210171ed694b723b1321c9dee3ad8a5c1ee7c92c472833581f37208770908d832888f9b7959a52d206845d7db98bded6c5dd99e77256a23864d281451fc08e1cf67d29c488f8df22546266819b302ed6f85235108e5566ed0109cc857386b84a06cd8ee1d4219cdf2468b726a94f2229c50f1e306f3e6a1d435b49b5b5e33b170510f76e7a6b8ad1473896219cb3e839f460b04260485785f8255a5bf7ef2925b8bea556cc697ec7a5b441c2c2b20edad81ab066caeee4e27082d195f86602e29a38c2fd37696b7b46427629585c39c8d1f022309553c53da1175e74fa70cd1aefa43a0d7aa3f1019efba1c1b4b987a5854748c27949906fd207f3bfb84717c2fdc7997309b020fbf474ce3ec217e4f423f108e1622e3642128847b34224e38e0b13c9db228d534485feb30b1293fa5f4e88b70ff49ab2b5cf09f52eee6cb64b189ee5a62f4ecab5bfa83ade568f0f706ff063fd792c34b93bd9763431533d254bd29d73e96115593b2e0c69326d3aaee9bdbc6f6b9aea80463cfc4cef5fc4380f827b677567bc467c393f6be429203ab7d5cbc71c0dba47bce9544d73863bbc2679db6a2a04bc55f78135cb18b0dd395c3be9c2ec6e2c92deb9515fb60bdfc49b35b6cee825320bf6b3dd4ed02259d50ef80e78769d21f9be1cd1cd25ff3fe406d060cc207f3c080b1ee6618fb1a8635ca82d81c05feea0eeeb60c6fbc59d89243d86eff9f4bb50edba255cc222e790a9cbf0c352cb08220eeabec5764cb0afffabf8167634fe07fb94edbc26aad22eb5fcb071fdd43ebcefe8740a8819340926f68f6544b45c67a8868919905f607572605416c518666c186b2d03de005f82c46100e822ca1f33aba354f8f416d598f75584fc06418ee47c76b48511d677dd97937882c189cb19ca6b8288ee974de45ed6b15e061c4944e343500598e8140f405378a3427db31ac9d2231070d637a31aca032edf0fbe3a81f65494034525309aeec67b5fe939bf11b14ec4481241307170b19bc4519162e0eb4a36a6863008b919dc1bfe5d1f73d306cd5b396e2ffac426e42b8a9603f9953d7caf8c5b83ff997737a0bf25e827ddb6568eda24f20ff70c6cc942495a8f97e39fb6f36d08549a4a11d0d48b2d3d04092f85fbee730fc9f69e95c28966d5a4b6d2360c0ecc8ad86b95bd9cddcf5a52d1a907d170dede06fe68a06fb105ea0b0de47dcc5c61187248150126ec39d70828db59a85e71155b9898aa74224424a0c91d28b0bbe94f00af51972fc028c9f076fde35237ae197422767abbbd7d84ae046ed7dd3a65884da11d3a44399ff1470379d1532585fe8f052d46301580389f5b95fb620d14de4cb09917d51515a6d228a8545c405f9a81eb134bea68e3d8c4b8fa20d27d23e669dbc1444421e51fb580f7f5707a00ddb52b5cf266658d7f9495ec20f983700bbbf31428f84f2bb188cf01694c7b042e7fc35a1280bf3f8ae8c362f15443424ac47934165117f9fcde1500338176a9af84cb2b9195bdd904ea45e3c7b469c839e769f0503dbd4b1d418e6e7e29632750bd2afad462cc9a39fcf6d98c41b3cefeaa29e10750e54bcae0a8ec8a4a185eda385baaefca00b9bc31117931e701e8891eba07bed2a9f47ab228891694f99d304f52ccf9062b5009d0b406c1e27ae550424afb58924c45fa432cc727ab249063a24cef6ddf7466f3c1dbf3b2db9490e9cd1e6e2109cf49a9140b8dda049a29cb93fc57c18b38e4a6ac7b8009801d8d864248454996ebac1543011c5947ca810de93794d7a38f080411cebecdfdaf8b8da98690cebd9c569c4ebbdf43a8e292496c5bf2fd2a424c23e6d263037612b648f727e74ae97b3667845d812d89bec8c754d29627a0f8390f8377f626c6e89a8e522e118ab82ff9d2cb6d5818d0fec1ce08be92a783b8a15f67d47d2573097372f1630eb2b2cc3840209c00e28f1c114e796bd886ed0c96ec50ae642361c6c0c54dc157d40ef8dd1a51c65c59fecf615ddc5bd4c1122b338b11644cc9631f95eb717043cda3203f72271ddc6529508e9bcfe37e5b6222adcd12e33e7425f835eba38b24c8126f8a71b9e4f1e8215b056e79d42202567332fdb05d2219837ad0734b63e944509842566380227156060dd8b3c05a9b5099460b752e2bd39e62f8902549638676463ee45b7982b0aa818ca37bc80faeb72c186bb88e1eb97405aab7bf74b53a6dad071fb53d1d46ad6aff1eea33943511b60bafb42090437d7ae4adcdcff1180e0207b075ba20d0efbaa48179dfd0a2e54c61efdfb056be1f707aaf9f61245f530a83b4bf6104333528ce228d1386492f1ddfe77c848ba1b320c9475d7793c506a5664153f0ca56bd719bad5a5b0d2ee0fb3fb48c02ad5acafa5b076dc763adc24977d8bc1212fd24fe99d4249a202518724a9c10a3370dc01099eec9f9bc0f2b957e1a424bd278ceab7cfbbeeb6054e105d3680105c4b6cd71bfdaada3228de27906d9116475d23d8c3f8079ee70f1c5991d1fa1136fb28a06e143d1058d763d41a3caa11c94e1edad3bbc253ee8a35ba0ce111bbbd08aff460431913371133a086e69b519a874ed0330b8eb95f7c38bccf5442c21f035c39c3e7c0fbeeaf2e42c0c408939b42b303657a2df34d6815cf9aa682de62b20907d65b680c9e1b7d5cb39ec6fc13153f69fc5c3d94e794386a133c390195634657abbb8ac478e53897c7643403b39cc593d5af7a5a8a0bfe4663598c975c044c2cfc490bfa3a69db0ce3a75f4dcbb2a4432f42fa7082e450d9c9a559a813d17f8a5f214a5ff30c1ee00d1070ce5f711e800507828c8ccc7616c548d4e8f91085100065286e8a87a727cdf87c17da324167a8bb86862539a9ebe0b95308f433a4851aee5c8307b2dc226c477e2bb44466a6184327973e60f83bf03f55297fdae04745a1f6e41250bbd0fbef2b858b8b6146eeffc09440f83d7b454a2711546d68da299399a054dc0c94725c15b0d8772941b32f19d7a2e83ea2aa668eb024b4bebb449ae3864417a14aff26e3f4f16627ec871806506ac05a83eaba83a6f3e7b1fd301b479ed626bed14d83c784181f2e519df54fc07dcfd262fd10a94a86126f82ab87b88f9894eadcb70199d52268cfccbaa180a0886b554e90cb04f41a254f6005f06912dab09b0f62696e0ff4ca9f6a3a170e0e78294ded342e7026e95d1eaeedb577ad7c511a0ac20de98c0576c8c3086487676b124729a7377ffdf19f42e2e5027168034d86188ade3c46aab9c4edf88842a665fc48ca4d20f96d68ef4347b2f546e430e17d31eb294de6e09508ebeab98674d011fe3b06ba936d8e8b93b6b130bf2f6db262852f3e2530bf06a86a2745c2793315cfb847bafc313e58ee670409fc25cba442a7a3224ef743b50ba13f4533dbf85c32addc2487e8099444a633edefa8032968e3ffd66737a513bb87daba23fa7a4a22a0bb98b9bbed01220edb47dab0cb4672c48e1d9766c3fbfb6814f744f111ee36b5ef6ed7c800317abf00fff77d6b879caeeb555ea23c086cfde0737e705249a83f9dc1afe8aaaf5655696478e3b5f9999476e17f66e843392075734ffd80b1ee5f827bfdfe544ada957979a1a02feadaaef7d3b626dd9856dc7243c3708755db6dc149724181d86eecc9ea0a371b6b3eaf36176df44eacf09445a42c90b36e9dfdd252b85c5152e4d9b5461930b089b677a9daf25bed594d2bbc7fd0672b3bdccbab5922621c8b26976f03c35b3218cb851c0730d905ccf9393b8b2391f58000adc2cf5d9e48edfd59d6baf2b99471ae35c0e7cb0cbbb6673fe9c36df068ba068c10ded0e5c26a654e9954261447f4bdf23b0a6e069d68d7c7d6fda4efe3abc1c20976b1aa0dc8571992861e995dd7f6ebcd2dfea47cd0a605cccfddd7c2f9d60c0290f0d79a9d4d64ea4b51dfc8278509b12a1469338a1ef44e306888518683db00ad658e51f52da0a8d5e72372d009007d02e11a438d87cda708cbf4e9801b494c13ad03621e41992e9dba127550a925841e6bba5ddf2bbac33b50a1c28f376ba023ed0d83942a3b5ae3281526f5d833de3e9445bcb8273ae9f611d7a5f26062b8b25ed223ea189dc641fc85b26920187a79d4b014d014679de8b52169386adee73178118bc53c6a36b223a3d248f76c2b9259bf651a7b5fcf04b7a356ff112588d9441005be7ac4fd0d55209a554d3a45e244ec355db1292c6f9e824c945c7ba28054bc2d450edf45e294b1e98c3e874f1a251803331c620408c0fb4d7199f750d0a981d2aff5c7641454a98de03e26a61a4b6feb5f6b09ed46a491bdb7dc32cf0daa0d9b0d110cfa8ef2ae872e7625418d7e5d64e39382c2631fe560bf727a25e3d8af23bd4a3976490566491ccc045f34f60b8b663d012f2afac4846e71b1eed2e2baa2bbac903f60c830b02096c1621938851d1b32c18d6bb8d871463601ddba6cf481298c74e11168277130c9838b61973c609a77b14b25cc3a8253d879803df109a2ed0353f10cb0876eb7fb11a2cb2730755dfec0140673600ecc9959cc01eddbbde50f11a577917469d31cf6ae447af02da239ec3f4a363e9a813e9c8bdd6431191663d21cf66beb9162bba739eca53aef1469a53aba3e18a6a439ec3cd4bda1261ef6b7c334a3633930851dc328cdc13e477c610e643d89c97710c96de2dd518539900583600a3bc6dd0943218d0e833019b6c3526c47ef08c542309ac3a88542cd61a40a839ac30eb1a01d0cbad8611004030af58a2f46fa833c54da8805ed7771b1ff3cb9d867954e9ac3ce23ecefe72f6ef61d8d697c630e687fb4b5cb93d9a572b4f04f4c53f98eea6b65156523128998c543e942860c9f65b950f41ccdbd43560ba7de2bf65459c52a85a9122612c23db12112e6e3c7fc4eb065638d406be4daa2096334c414d051c0f7d0508ad88535463bfabaefe9b20ef8ee7adb8af5f52057d2d8dd1abcb01b7b753d3e5ed236b1728bc808db303906c33a8daf47562c22a31cb839c839a0efdc05f9e75527c4867817b3d75544de1821c461d204083f40b022c5c550ce837f3fbd9cdff5dea15ce66e47b5ed327331b473f95374550c500c9c6e7cb34c8d6d61edda76f2fd2861ffbef85e12cc9a4ca77777f4d3c2e4967e3a324c34df363af76a64bb77e7cb6a3c418861574ad1bdaeeb8a44f26258bc5906efe86153d3b48c7455ecbaaa4874d50bbbaaccae1a4723edaa704eedaaad5d97a6c1fa2e56f96ee7ccf255df9ddb9691ae5fdfa2088bb023243d96794254d81026734cc7e677e4c8434ae68eddef41ee8611c2f79a7d70eea3399a536da3639085b2a12fd91b94975e3a02a67afb3bbc90edcfc6c43168d3fa5a30021de8702fd681e2400a34c869956a7c01115a910eb453ddfee1044516ef257b4219513a3db5466e5a252b061d695574676f44de9db05ba27ec80befb4710ef4a675d3828fbf6e10c8a2e7d6bc56e39dadbd8dc77c6fa46b1545b24d10e1e3f1cabbd9771c00f2eeb4cc4f07ed40072ec3dac16b7f55c8fbf65145fd1875a81faf93f7fde68e7e73e3f6e3d9cd761c00f26e6499f8a3c2ecbce550e5601a1b1f98c5004ef58b8acaed5497df703dca818afeba9b5bddcfe6684e64b1e61ead8cc114b414de6c9d7da004cf313647d822462963844760f25e6cd8cdfc38f640a18a20f004c136f169e253a240153647d8627637d76e22d1f9b6ec5e14450c6366f8306ed6e2b945d8b16b45300dbcf136479c9570e3e5b35f4701c9e4336b80e6a2361a599b678f43b4fb0f6d2ea28cd0be7db35064915db3aa19c84269160aed9b9d81a9f8ee261671591b3820001e05440a4101c1ee75ec5eb6023df747762f8b027209410111223fb206b878641689e6a24d0dd23ec4c7ada688375c5abc82be0d08bcb43b9a8b326218a5a7d3cfafdae1231611dd404d3486a14d51c524b30c26e5e1766d3cb21f61d36c8ec0e46647606273842d24943216d1a9286b17afc4588a9aa5c5bac4336ddcfe0d70022010810458fcdc8004ad3e637c9513e67c4866e0dc5e1420d7035375dd01318c7bd89beb3d1e984ca766691812ac67405947b288b06a388ce33d1318a09939d22359441b82b7b0f6a176432350f8f71db0fbe5c8a16de265dbc85fd608e58e95fbb5e411fa7ac561b8fdd7ef069fa5fec45ef3d607980c95dcec939fc020ca18e39cf33be09472bebe233e731732ae2aba222bf87a0f0c74b6883720bf1717a8051e0e388896677fd9fb826532252b21cc6557dcec4a40568b7d62e054f60c0da62b6dc770b8d9d96e8162979d05a6c100ed2bb00ed2b3c30ef02a632e1303e2300d3d55a1313dfbca339767da91205ade9938a6b99efd31bd9adbb6d96bb612a6d91a8b6b4ed19cd89cd99ca339e79cda9cdb9c4a5ae7ce3c9edbc3c5ced57e7d4e30d06bd39ab8e84ff6b95293a0e63578e71fdf9827cd4bbe314f30359fcd959a04f5969af356cb5d5a2cc354ce5b288bc1d44c916152596139c9d054b2fbac70a947ba1582b7b2986fa9dd22550c60d7f4474b7f5650963f2be87b72b33979eefce15436190cb40c275ec3390eb37fa6a0f39d05663da019e7b5239846c69dd81728194e6a381cc6606a3b28c5cd4830098944ba8d5ea19c7415d3ac9cc4031f89740cb25e4efa64010385cf0a6afaa3cf0afac0b8b2ecaf0caca379727086bb3dfbcb48cfae73367a85fdba8a69b45fc720cbe52d9cfd04b35f7fbc6a9ee6b25f77a9fd2408a6b2d601afda49f6e9a25d95746784af7687181a357878b53bc4d0a8c1c3abdd2186460d1ea08662fb8765506eb17afdf44fff98fa477599601aecd99518d2ab6a32994c2693c96432994c2693c9a4612c635bdb016a3151a321b51a97c683e8ba4a2f6da4d2cc156d241ec8ea1fa8d33005b6c3525eea0b8229c89823d926de108cd0cd5450e851b2af549d9bfdf49430d1abbf9c5e3d93c90ad35f17f4dd79d359361e26cbb475308cd2d3e9e7b83bbfb2f19896e98c0c03ed38a80777fb470e34a7a96856efc0141898d53c30959d32d3bb2c7b6bb8d98b434a7695da3f2c93fd65abc92eb53dd45cf6de81d99b87f4ec279b919e623b00acd8ee84623bcad036f1b29b6cf60b87d2a79cf409a33d2baca0720cdac12b4ccf0e793a7b66b2b0876e765907da71179dbd83200beac094135ec1244c055ec12bba7f9866e5d97ba85728cf60b2abd8aec5b82a5903ddec2c75421d78054c92dd95b3b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0bc6f467a4622912ee948f48ae5a4ab6c3c4896e9e361a9af0ef435e9d5bb59f69cdceccf09acc3017c237ba6859bbd7b50b8d95f4fafb467bf9e6dcf4ae77a85f2d26df4caf4d2554ca3f2d231c882f9cb1996bc602efb09b2fae78f570fa8b9eca5c35426602afb7c383005d4ab259e17bc1ac25cf6ec0c9fdc2c5bb14ab04c664d37a164375571b39b361e25cb74e62a911dc3283d9dde9d93613b8e643b55f62498a6f4ec48f42ae5d983f48a3efbb42912899e6235da5269875229a654a2512ad52895782895b85229552a695bbd646c8f33d74b2ffd59e99d8a643bee3f9d608672a92d95616a6e08b8ae598c457b60a02b97883a674642b15c73f3b4ce97a6ed86ccdcebda8680eb5873fd022a2f3a6f5a0fd40f784b87b7646d1781cda2343b123db342a012da3bd4503339a653f9b9d487fa347c570ed3bccf69390b39dcc99c29640ea43ea621a6799797efb2215cf96cced21a5c70eddde1ced64c71b8f25d690d571ed62e50390fef7cb55e226f0c58636ef50163cf8744e65c992d6119d9b0676b0f09b5824e8e90a55f40bb914e8ef6de501a6ab33fe0d50eafa6f59c2a502727e66a365b023fd2611aedf2a41ea69997d9453cd02af5310db18cfcf584469f7b64549b7859f569e289463a57feaa4366aecbfc6c09d3c45cf9a884696254b2376130dbf4bea04513f77bcd024d458306a4a1a2a19aa951e33c7cd6a83339543478787f86871aaa191ad67415d77c15a7a085f4c8c3c9a23b5dc87aef7ff19454e1728d270a6372ccd5ddcc17605666313b87fbe6a31cbbb939be57d939b331be5772bec3386302b2342b39053f378c535028b20cfca302b50216112396840da2f3901cc087019a837f57c0c71f72e4b0612326a67bb9ddd3b9d0be29d0200df4f37eaebec800cf34621a85184c62724c3705ed4e5aafe04dbd3a1d029399254f99253a63a278558c53b65f0426abe0dcc6eeb3a25718e96e57bb33ee8c1c5f471845a52bbafc5e71972fbda0dde8c97df274bc0e7bd89934e7d3736513f9446ad1a9972b6e8b640fd3e4905674aaa592e674648f64227d7af54e1742d8320d318d5018bd82222c9b224dc36e27650faa88ec9935f25ad86f5af34a5dbfa40e6499844c4226a19b846e4b0fdc30033274b1c50c3cd0ea1b1b70e1670b3f22e0820e3768f5658fec913e5632b9baa01d76e59730cd2fcba8e0cc3acc1ef0f21f8c10c6d7af6d87517a7a8f4094ff53070ceb558dca73f2369220a05a006e8c0c0115fd3a7c271d3b85acedd84fbd9ac7fed2abecd8dfab9111f2db7933229ef41d9d84f4514db23d23cd2886ee3caddb4b95744d7b6f36f49b35728229ecf4bdd9944eb246284c612fbd3705700b8329ecf208ed6c5cecd87740ad669f75c8cbb9a3918d5e09199dbfb8d847168b319815a2824b902d903b12043be871c28997f32e41e084134e38f1a478394e38e1846c811355c8f5cc76447e89fe722e9133c10417f8c8cdde93eec0cdce1d0bb5e01d8741337af7709a7821c8f983c18f78e01d760aa2404d3c29b27367b43b8de0ddcbcdb89ba9ba192bdefd7033196455649f8f0b4e9a0acc61d893d3919c9e9726b1fa4673be0ed428ce039ebf1c9e3f29ea7c22a8a33f1e2753dbc1c57c9ecf5501900a7854a0b3c4124b2cb1c4124b2cd1759d903f1da0d56ab5fa7441bdb8a0be2b68f027457d3bbfc18e3d92c5030192d7e0cf0a243530c175663083f35633856501b18044e08115665d6ce0634505a249cba002e1a48a0a4417cd01b10353bdd384137f5258204e00440a9cb040e88041023b1978c75d201978f70b2403ef4e174806ded10b2403efb00b240307f239638ab36c3c9e0744501f0a52f097e4cf03f59d400617bbc10d3e5f0a6a430105b59b90e4043748dd77e41db12d048689c1c56a9267031b7cbe0ed4d19f0dea912c860000062e3b3bb702d5812ae4e454212706e6652789201c051ca0172105e028e048027a12c0134410410411441041045180021460c711807300214a85ec0a6080808076c009b1b93705ed4edc0be4b288d62471342e6af07a9a6ca19ac3ec1db30fa1c59aab4df4803d542eb30cdff47b3bf0bae9a0bc1a86061adfc51ba9c46c474516cad6e2658bc82bc718630c0cc80b61169849956346a59a99c9a152cdcce450a972ccc8a89a79a1cdd5a0f10b637ac5d95061b0bb9f4a9523767f1107c34e277e681043c4610205185c8e812326f0bd77f84a4039e6c97e3f6088cd8862f6ec973d9d288dcfac89ebe934832f876fe642958ae3de89592716f6c7175bd48f26e295ffe1dec0364c29e593b2bbe57b0fcb5aa2c5fa3d36a8a18b3ce2bcde3ef88a5042f07d8c29486811793170b107667f76e52648b8bc1151bacfbeab90dcb711b1dd674b44dec5c0538243125dac26bbf1cdd1359272ca1963d4228490d439a16d70923a23b7c30049833d1f9344247e97b61eefd20d4523c5ade65d6a7adcdd295a69ab81576e358fe383a4cec88dfc68f933768ce24696c832b76d24c3e4431823acb10cf41d46d82a98f351e6e65e35110f7fc4db08688dc2130345d6016197208c11807177c4cf88e0e7c6ff1099c98d3d14c02dbe02b875449eff3605704b7eca6a240b9beb6c8d64814a003cdf0868735e0b9ef950187124040568c1e7bc561f0acca6ac40644f30159fb243e141f05acdc154bcac292b68f13beef24f3f2989ce3194f9ba7c3a626e9f8ef77e0c67a3597354affacec5d0ebd929777722228bef4686616f07dce962e77743dad8efbec7f760ec4262580c73d7af2356daf942e2125dd00a8987f2f518f0d42fa27794f5c7f8c95c7ca923024776c432f1ddbc4e5cd1dfe846cc7618b8a23f918d3c2e6882b71530ddf873a18d392e2e29a3f7ae463ffb56f671efe4f21ecd25e525e52b31d340291fb3cc932f5271ddf8139451475f57cebcd8aa29069150a2849f18e6c7c43c143ca6d731e63f7be2fac27c5dd7fb0bd7d3c5aee99a7a25df9799852a42645b6e767774cc02e58f60764b6498e86219961136cbf0bb251ecbf0976020fd7306d115e531ac66fc3280b10cb7fa82dc52f0392c527aea4e45cff5b5c649021b446998af619457bff48ae1d3313551363affd42b0d1b037def3015be2159ba25b61f7de3a7885f934c2a422efc7bdc80dc91a5cc49d3102d420c6a452e3ccb27e29b2f96208a618869002d684286212627f0ca71fb7347a77caeb5f103f42ada24d775ea7805fade69d70cf1933e2968915ea18048211d4d375ef2300d33e1e74ce0c4cf2ce99e126ee6aa13471f2326a443374884b4a7527cd8e1c6634773518b53a0d9b10b919fd73e3792889b64bb2143ae7c33c134444e6796d92ccb8a8ae6c4860152064a4f886ce9c4b2a262929182424bddd77e5521fdf9f9d955da77dd98601a22268c08d310919135c13444524617601a2228b308d310a19a134c43a4b461806988904819601a225be909a62122a21a8855fbc53222145a226d22a969d86714a3071260a0f14af4eab24586b4fc009d8a8f2d318b52a0f1dd7847808c5108b6f8b383b991483f5edd42f3c43366698053f133333870dc98bab1b6b458a1d86299e8033417fb03345e4616ba70547a1343d61835ad393e659ad78d58c3461725bb9d05ae4441b9946918c2c72cc3475dd48f4edea872a31d62ba8d854823d27817630decb1c6df678dd7f1c51a31368d2fd0fed35182506cc1931a7c5fbe1dd06b11bbde65ba710618eceef716d237bdf4aa61777777777777777777777777777777770dd385b0bb4df02f4d35acbbbbbbbbbbbbbbbbb9e8e23edaddddddddddddddddbd7a3a629ec8061aab017c2a02a6207dd88b6800b37a70aa4711bc006625d16dbfe0c2859fb9104278be1766e17fb0405647955e296d92cc46edcb41d6e964a2cdc1f84335f884bfee9ae30721ba9496966cbc8ee91ee42e1df21d7cc734f77ea0813ae23b1b5cc3f7ccbb01a5e59ab35163e27db95dcc7d97c80fb763381bbd7a79b9dc1cb3389862166c8b2ae2b204b8a9cb069363b09bd6757e0c532f0ba716ca08285036bf695de756ca8ec88ace2d2678d10631705ab2321752e404e1b478ab79635c226f0c94cd1520b2f10bcaad2c503f9a6beede74185c70e107a725ab8c01139ee8e0b43806767712ce862a3b9fe6e9189dffc3d331cf0fe2e98825193f7da63c56e61bda3f53ea6b952a0fb5fbc1a27723d548e91c70e1b5daf110c337faa36af906df608e31e66a5c787be38d9648142fa7d27374abd4373b3708c2129aa461095ae8f090146470069e2414210744d83abe20139d6f7a3a2ee698c41ce41bdaed3670e3398a1e8db26e2d26c7d07ecafb885958b30e9ac2636c01a48631aeb053c5095a4f06631012e2020911e8a086d63b6317bdb627450fd36817fe29e1e960f2744452939fe7440e310d33e1aa1caefceec8aa12a61aa140e53b166a423b167a3a34db2531591cd34d368a2ce255b2e8c137074f47d41e2d0ac89c96081c630ccda2e66fae76f8b783b7d32b9245fd90a42494680e8ba7231efec13ff172564442b156e0dd80c72aa9625553a9a6f6e1c5255021dae7e1df13f8d703ff7ca69022b07bd70b1a5b487cfcfc8cefced6b4e98510df38eddba239f8b784895e751da7e1c2c38e836e935e25810a21843e2d62c4b4c7ed31da6ebb66511115cf57d3668c6f8b25ef9e1617cef939e7531e6b05b42b643ee5747ba47c3ed6f9ada2b0c76b15783a4476db6cf7aee8f05abd00562fa01d7302003eb66fc7aa0fbedae314e8f3b18f09e9510a54f4d2d46aa71d8596366d764bc42f41ba5601d31d62ba5996c46463a7e2a1e50a3794f97aba21da8d16c53e2648a01550a257d476a5d3e65ee92b2c371d4e533dc114bc4a45454be10a8b3dd140c2031f9c56472f8c67b9e92b15156d1147582c2afea6a58229f895af5854b45c7330a5f297051593e5d68d2c2801a7d57197618a631442480983987d4cae5dc0c73c761f98ede2a74561e7bb1df5277af6ee61d9b74c647a0f6431d1290c92ac0f6b80e660ec0215fd4786d9eea1e2e745f351cd47fcb4a4cda73978927d4cae7dabafa739f87a2ebef074e11f13484f579b52a01df6d145df017f7837f8bc1191599a77838fd920de0d56357773b166ddb4aefa4a4063e57bfda0f449c9cae9a5bc77c417dd4bbdb3e13e96c37d2bf7b555e2dd783941394850710464f9f079e243f53ef37eaec1c0b8082c02612f810f070707a708bc023f07182e90c31040108238d0a0756384371c19e2a10309c8d0a4c5673ef68592fb381b8f39bd98b417a4b93fd1a9771c2c87eadcf7fabd480c03ed1ecf7def8fa757d126f1b0e8d593af8b68eea9bee3dd070c42c42206a312490c11fd2a02872811997c914444e2ea9eb82f48124a0ce9221003f00661bd73a7b884183b72cdddb703daf0ddc663b878624cd56d2845d1bb8fe6de0dd0a4bbbb9108725d419080eff8c911cd3dd10303edf807894ebd23900802e5102dc2db34f746b77f8a6896013af558aaa7fa3bf74edf4f710c137e051f3f8528319194121361115e5e6641fb8d49d90d91d1748770b4083aa46491942ce38bf1cfc658e57b5950791d377ee6ca337331e014b35a3c2be014dff81cbc123d5ec5abeb3aee8e1655662e6297984d92c92739095b20c3ed7e6509286fac8a59309392145c84e1bec73a8499bcc682bc36967669bbebba906bd3444caeec6ebe62b41dc4fa9217cd6cfcc5e0313b5bd41cf70abb22b458733d6a3902daa524f493d27718ca3b2a63e5dd8ce83daf524d95616abb8cca30457a4a45a9346326b108ac8703472592edce71aa992baa3b7098c9cd1657f470664e57aa5ec33d1de48ade3894993093b7b5688cf78304167af1c132f09a871b8feb3e9048c20679e42ee46cc838ecf1d1ab264810e01bf0454e37022c73806605b1699612a44bb248b00cccc226e5320e05ca4d8722e5324ef2c134a43bdfd663bb26e32987c274944321e3292725c074941b21e329dfd12837dd08949b64dc8894cb80da4924d2f6579a275521af46fb8102b209e1fb467fa4a98d6a562356b51986294eda98b884be772f42a4c640101f64ad7d8831421efada0a2f31177e7610396fcdf369d724f37346157f80f31da6bda3db1fb3556dc7bb7a08f4bd0a218ddb0fb8f2f0442f166f170120d715d9b34c5b1bcd4199a357fd43aff8d921dcedb745fd88b715e0aeb42a1bcd7a39c13ed6920b5c5c817f943c298224c134f2f0129e02ccf28186ab6219781f9085a3270b171280c214fc8e963d81f73cf07490de8d23419e8ec6797260039b8199196b8ef93a373e46a689c76e0ccbc4184552d20be50f9480832bba10230071c4376e44e9b28e87cb7fcca23015b577239effea0ccbc4cfd163a20f444f070dbef14e004ec5c71b9fdca0428cdf1e1cc25dd165bb23eef51424f471e6760ccb64bfea104e74a5e531ca2aea1d7b8c3f54d4b3331c10e8296a41df19e8d2c2860afa98d831c80a027481aad629880951ec4910c83af111d7e191e855e7e38a3a161ad5ee94e89eb8a27880e6e0e1219c690ee6886aa036ae8e5300ba58133c589ebbf6e0bb4d2ebd7b8ba15db4f198a34ce3faee63ae2a9d740ab302cb60ef76d021a9c037b0730fdd6119ec14b301a52fdd13f01a589601073368c1e99a823b1087930c8239ef6224601d38431783ef2e135cec310df4c516d0777fa7487b97ba0c74b15766492abc2d840315592359d8887efdb2464446504310705a22fb73c183d3e2d607e01863b4b2e87717696cc254aed7125594cd6504052ffaca2fbb43a5a26c548ca0e0576c7c02535804bad855ac0f98c27ed38249287d867d5e97485467aa0aa6b0c31b50fa4b8b6b49af64c020292ef6531928182ea6858bfdaa825e2cae906160520c7ae4a2e26287676066d8068673e0f8818b3da2816201d96d7a9688369b1e2cb0e8c1a26749677d9b1e2dbadaf42ce9b3931e2597b71e4b50d470af67d9b16be391615b266340af9d5e75d76d7a9634b7bab0b0e9b9a2e70a2d9a8545b3a44ea7ae1e9e4cbb215d8280922af07250ae7d8a3409026a237760eaba44d9c4bbdcc51a914860ea7afc8dd481a923f4e65e8fdac6c3a64749737cb3cea04dcf921e2d9aebb982516c7a9034c7994d0f4fcf15bdeaceac4dcf15d407450d4b7ad5ef680f4c5da73e616437bdd40359453a95fd12e366b3d49689eee58860e5ae56bbec510cddcc0e21897aa76b9e7bdd54bb784b2527f73aad1d87715d46ed1e16f77a4a6d22de0ee5e75eef6bc5ede42ab9d77be7e77a33e925455cadae7d60ebc0ce691ceebaaeeeb92ebed7e85d73d1f1170c06bdba1706185f740173ee7372af7397ddf7ae93d07117d7a589b0513cb5dd491ec5ba9c643bcca532a75035d727241285106d37de7aa0d41aedddf7b6c3ad88b7f5e041e9a3bf8b72d227d4a44eafaa78f7aaa1735bd49a8b8441dc7a943efa8c8d7289722949d7eda0b8a4c3adc77b0da3bc441a0a09a5d69969f3746ad3720c16c13075d2943a31c61c086b7a90ecd0dddee5e648e7ed07a9f9bdd742dab61ff2d2cb4bad90d24b177d96eae893daee5dfaf9aca02dc43442b6f7edf9eb5da54e95553cfeb9a4f708ac404947b9db516a8771f6eed6617daf1ba0bbfbfdb8f1e8faea407b4e92d469eebab482cef749258a9222c3a4d2ed032ceee8a5c70d01a3973e6be69defad86c69d76feba935e44b1db424c337fbd837ab5fd7a83d1abfef5d9a26f3544de18b7e78ae6b73f1add2d5f40b74fa93d241777d7d43be346fa8b026d309abb4e3abc5aee04dd4b8865aede6ab24bb21dd4dcf5ad7672e75e8f20a0dd08c9bdde7590cfbd4eeaebd205f452678689e64423f953cb9c53c42fdb78f4d6e3dd868f3552c781bbfdbadbaca3779d1fd579f5d53d34cb44736dbb212fe74edbddbca5326f3c687328d74d4afff064ced2a3d7c663e6078af1c9063779abc92e37a76da58d074b106036381b39542c839e4c9a899e4c9acac6c3f412a386f1cac6e34cbb0cdcd61ea53356c07782a6b61aeca1cc32f4a2de3b5398ad26bb33cdf15f0ce80c007ce3c1b1b9d309f5e3b6bd0013cdf16574805b8c7d74de6ce67c561f4321063a485ea6684d8b5200b7284c410eecb4b8c346358aa7c515702a0a07ad594b4960b2a435e5a3c209661852697169b346d15f84200e405130932fb4a6add067093dad1e9c2869cd4c8b34e0b4e6489bf5746e02185ef49ca6362ba77a411ace30b343a545ab59b0832b28f1518b30017d99a2f5a47085244b5a24309461496b6adaac1c44879ed6dcb459712cd1694d9236eb0068d0d39a295ae51e325c21d49a549b5565069cd63469f5a5810c530cb5668a362b8e181c01a8356568b346c154f802c8a4cd1a850e17e8b4b0f6e17daa68b3aa5e20d49a2bdaac4054e1a7355d5630862afcb4e6499bf5b121c94f6bbe68a3aad2a2356b5731052f785ad3459bd5072a845a13a5cd7a230a40adf9a2cd2a80317e5a13469b7500423fad79aeafbb5618e5257a58369ada467a258a42536498aaca0acb89d65a5b5c502f3000b88a247fd31ad59bd69cbc5dd767157d54b1c73a379bd131f8d1e766332fb24682c014fcfcdc14c0ad23600a5e1ea1fdb8035ed7a390ebdb8fcb62194ca190ba0d5c69e3af1bdf5ba2caadd8a239889ee8c915889ee84915889ee8c92adecd15e145c1642d46c7005805590760d6914469aa6e438d88249e8e8c6fb0584de6186e77254cdb58c73bf26ef4314c4aecc8d371802158a6ef35142fb30cb3cc3bf2ac409f7cefc8cba18269de7b65b8ef6fe3f19ee8958ca2d6b26d3bc026c4cdb3950911115ef5e5bbc8761c868a0d1564a9c0ccc498580663997e0685f95c604c335f525e52767747861d16ff7844eaa2b8d2c62688bc316e1f6399d7457128a45dde77e5fb8e8e92673ed3cba9dfe310ed77268d8641df9909d8b0c7e48e1c5fa41414be336995c281f6b13303282fe109fd6b095038726769524bb57e53b801e95fef0dc8063a791fcd654e4e59230b79ef1bf9da0673f4eccafe904016f3b353d837fb3bd2abee157cf647850e9781d7c071b819f623595c2323443ff28137462b0bf91060ad9417b95b92ba9b9b84a26a78b8f2dd922d862cd448fb8aac4ac027050d2201a691cf504650284a19ca881452c6cffeac903bd927f030335036dc4c0e373b0659291665437aca53ac9194937ed3ba6ccaa45589dab1d08d955f6bb646eb13ddc389dcf0881ba4b9ec578680728b5bcfee03b2549ebd089b23a62ac7b66dbf361edb9319a679379b611abed95517addde26d762bd6d17b9ba715b6644d22e3574d82f2514d429f5273b056ca2508e868c6ed8895f3660485a94bb336a4af58232af6084519599bd24dd6488a3d92f20f944a324e51b4a75494d32ae3a3dabd1c9b94a364541ea1a36bf22b21f2fcc5cdce1b112c862bed4c73598ee654cd659f5104a3674fa25906d8d15cf61e4d055931d9bb999b1db2a80dc24196123088fc4096dd00bd92cf5eaaa44747e72cc6645688cc6e6493a698f04dc4dbccef469aabc92e12cdb53d8265fa35f0884ef531ac4ffc64360b09d6ee112cd3011f9065c3864ab3c123e25058f9f28574609626a01d3d5dfba6c911d08eded3cae167cb554fc7f616de6cf7b68d8879356debe1f2eca65ea9a87cce39bba89d4ea7ed64bb9b7bfacb0bf6ed84b2425eaec19c3fe31ba38880b15d5fed28db758ebb75989df172eda777459a37209aede1ddd8fe828279cb5910a0fdd0ce2ddf98679ce55d8edb7297baf5389de769863d9d46334ecf4ed8c6a3e50506a6f27d61969acab976f4c236f2c4b6596679dc7ec0db728de5a7ab9e0eeda722cd1b8feedd162b84e346848aedfa6ed7ce7296d3e73b986799ed725c96bf08e16f3fbd83b12de78d88eddcbd53ed5e6c8bede2adf1be54e6c36c56bbeadd60f96bcb51957e07747161e9e1e9606dfbcb8bb6bde52c6729d2577339cc69e5e6602eb71eda85b8d897c78d08ed6afc9696c38d47cba9f2e52aafbb69e0f6f074b0fcf497f34f7f697882db0f19ef561f493ba1bc6815c6ee802c55e52d3563ada5a519a6a5e53b608b6963396f3f444177c59ef8bec9525fcfad97a310062bee44d16409600e7fda88e8e1dd60cdaade8d1ede0db64520cb08133dbc014c23f6d4f20bdc5c6d3bf7b05522f0aec0ab75515c212f3c34dbdd300968a772c272bea79697daf22c83394b55f90bbc30ef8dc81be34a2ae8eb4b96d359de52bb9bdbd2f21dbd6ddb7bd497f3c6a77a73b916e9fb627b78375858be03b654186baa2a2a3ee8bdb0fbe1c24f39025a448efbc59df371f72e6bd5ee802c5ae5e63496cd12819725e34df574a8bccb713bad8bb72bd23d3c1d2a56bb9bcab7cadfaedabe55ee6e2ecb7734334b75b195aa5c45e973f44a8507b54cf90b1596dabdcbc2c2b6877743f3d12b9595cbb58b800ad92eefe19150a257bcfd64bb1c36490ce99566b543dbc3bba155977355396f3c5cbea349a7ca9fbcd5b4fce5a7cab68b77426de5e5a70af3ad47cbcb57eabba7530f9895baf2775fb8c27ca5c2fcf41ddd72aea7afa89ceabc8bca7b783af8aa1ca67645ce39ee8eaed8e514f45d0c01912b46297693e92a33ae65873900ee82bacb76ed2ae77ad2b4a3c040c2a6571c85373cb9f0470061b9f6cec855e10dc8e9fc1d8d7ab9663b14112ddfceb7c56edaf91ab97c95ef802ab56b394a3dbd9c5b88d0be11c172956f2df6878a3d6d5dcb278b56bbed2adb4f2f6d954565ab2c2c36e50554b3fc928a15b272162b0475ed45fa6ae7da6db648ff273b0f372018cac6e66e4fafd90e007cfab7218061ce1b02b2c33ce38c6aa76f2a1afff42da31affa4623b8a69676dc68cd319d7ea0cdbbdaba99c6d3769e53f4acf7c191a6bef21434e4151402e7fb32b7f16f503fb76ec9bed46574588ca4d6c52b9e9a6daa95496d359586eb69f3696afbc371e44faf2b19b4c57e9c162bbbe2c477daba7da3d714f2f87816969b15d91bedd0520cbb3bb9c7bd5f2edf496cacd9d564ea6a31c5589d43cbe2e554891be99c5c0bbfc6ee572fbd140b09b6eb29dcaf995e56d44b065f9565b7eaa5dbca7b7d81df0f49473edcb725ef96663732b2c5688cab1afd4ae485f156bfae46ba237a5769d913befb2f1a025316e9177319b2346fc78b523f14efbfc9cf5881cca36cdc16ddb0ecf9b3540bc23ec3647f46af2e75cede0431f7c3320a2a08b59ad399be306e8d56593d4c30a5542373bf0001734c869021a526084160c82083929b8021590c08235b4e099807f82090fd0cb337a001e60ec0453103e5aec4291d6f0825af77b178450ca5ff2ddc85d30de2e123172e3bd2e84dd0b4401792820ef624fe0a106d7a928e56d3417dfebcbf3f04e4a29cfa97ac53d96ae0480f68ef4f26ec6f66e83790773e5bc1275e5e84a972bb32b5bae94a22b5964bcbb7678b77265cc3b952bcf42e39d8c2bdf4c83bde51dcae95dca95f28f69b2bb5cda8e3bfce4aa8abbe42417afea15db9b6ad453173303658c91eb95edb84795a35771269e46cc0eb61e0033605e502e2d2716f915b9858abc49867c8a94295579526596d9aafca866f5b18c3c26aa51f66442981ca3d18631c7ac3e3a155f10bddefd808373af187fb8913227fa0fa25537e4621a73a2bf8a6554839688918bcdc719e315b51f146827c1d363b852c538851debd57626f2c48df192ea636ef4583b0ec83bbaf3dd09e7b1fa361ef36a95c45d0552ee7bcf2c6676639957bb266e7f543bd27d5ad65d60c3a4763b465b2a45d61a5e41c5a7bf9c4effcbe9f4f2d3b1d076371772efbd39e1c603c34c54ab2fa7d3e8da68746d341acdd16864639aebb08f84608fd847d819fbc31eadea6296cba12ac2264279682bc0ddf883dc66b01821965d5cd0978d5ea9f4ae25a319e3d699151e73f22c2116452df77459e2ca55b9a22ae29494f0ef8a31c618638c5154b915638c31c6185d5c48d15e2c232fd2c0857f3ec83fb9810bff627c22d1bb8420eb309907ca3df734f39e0ebb0c218410420821841042082184104208218410f6dc1aef301aef68ccbb13fcad4123c676a70b217c48948004da03f9dc407920cb0689c78304f978b6b0dd1161d0165da144f2441e1ed1df15cd758fbe419a3bb2f5b01121618b961ddf9120bdeadbfc5e3784314a795d22edf5d008315213260cc424484bd1655f132641a0cd114800bd2630f7381b27c80ac2a4b9ec0fc2f79bfb2cea4711cd6d356da3a26afac2ab9abbeea82e81ad441006821d3bf57e65f5d4af0941208beb14935ea130a7b2dd9019965b50a8eaa7a4b9c7c35b8fec4669bba7e4f14c413b4d5b3ddb836d60aa985e4578be3f3f586e32c34cacd6dc0f4501841046080f451611c2f864074b780115bcf0d3ea77fecf0504ee4a1c3c9c43dbe5e828ca269bcf7ed37a289b6b9baf897859188f2ebacde8a26701ad0de99735729d7423a28f7ed3ea9e126d5e7b48200b65537af6cc1ad95efa4deb1dab463ad59de976efee72df1768179fe0a13c7459b87dbeff76fad8f300edb8e789c8f7d98e6e0828f26e5bd1dc6b0ee52810ca2434daf787d3dc53c20776114a7e746e912c4a5a5402481fdd0892e8a3438129b9ef464021fac8b2ed1ecf7da33f284a273d5e84c91bd0924591ec4382324274ec9885e2bae8dcd22cca66f4762cca08eca38f2c14d9b173aba3475c4664f6f970b7e7e7be2b898df96cf292ef7565307bef6197ccc1cbb97ea1264140dfb90f217765edba2779b8bc216074af1e2caeab478b1e2c2ebcac4d0f165740785d363d5af4f0f45c81dd1e9eac8bf9c9f55d52fc7c0da3bca22d42bcfe5ea1307439096b00c271390a4f904213f1469d4f4c541f73089b58b944a2c0b959c7df5cae87424c75a3fc42e6c4439154c525f0920f0706f95c28dbe7caf7f37e6e3c8cb291b44f238112768fe7ca167a43f1b0e65d8cb11f79d8037b6e3c7661d707491b0f29e3e788d4e77ad51c7bac60562200ab1df632d2654fcdbdc4f44ac6d3cb6328cbc45f36c9e8f1f1a3aa44860c1a7befb55d72b9e7f2a58e1aa80c283c61cdb50a6608f1e376ce01ef7c6a6811e3a56528065494e45df6f834d7120453fd4e36e9b0db12d4220459232c6b091afd401b98806e5c41bb918f895b2620084d40d00424b71a005c53500d6837f2b9ba8be26e3ca26cd16916f9d43bfa91a39fab84c404f42670b36a02fa999f5b7d7c030a78477f223b5b74b47082db4a80464d9aeb192dd0c2ed16aae47666996544f58e7c463ecd35d6a3262d413376463eb7dfb504596619e9d3abeedd26bd122579b29fe5ae300533b4a749415e22ac059d8d7a5a40c5183a5d161e3e301fb6f1b894ec74d845828476980665c89021a385948d01e89a01432345c54451627640d5e02dfc40898cb31a244ed0931f9f1e16e80ccd61cea8f6c9d38155f16e603bcd612fe861810e16d41c76861b1cf79f4e98900e0a0a4c4d79b27db1056d67d884483c20f19096908a20d2483d295d74ca935ed978367646f38ba76303da827aa512dacec02b551b4e67b1db17177e13822cd5108907ccc22104120f64e150425ad2ab219e908ac0ab21b8b022b422242424d462372152cf06d406a1d306045942cc805356b48039a01bdc354e62df403d477a5041854e94126b3244931f91ec19a2670826d815a7a01d4b410b2d96346b46a6d5c002080f102440ac00b2d31c902a80e800a102480e109c1d5e6eeb911d6abcd69811f31a40a680c2910ba40323b8f01db6820bbf64464c0dd10eb606e78b1fc89af104b26080302658d633ea993a5383a15167dc5698d7facf987a24a6dad3e802c80b19940489921f885082b0209670b5d68080072005e3e800fb003f918c626c0d4e174f874a08a857433cf1825743704102d1c5850722288824412809e2072220825080080222080322e802220888a00b77825872838400a30cccc1b741486a810a4e48424a67088fe6e7a7a7e70708b25e82200bf5f333a7a6511fbae003900f5ef8d0051f8256495648564a563f582df101c8072f7c085a255921592959fdc087201f827c08f221c887201f82564b6ae0d4a0e2c2d7c0a991d32b794ddb36938a8a8ae913a592a0ead4145412d49d9a85fa5381aa173b35c8349297a8ead414d49d9a85fa5381aa1775a7eed49dba5377ea4e0d1a499348250cb483015ac2a3446707c98c23171e666806ce3644a27112090506856525c346523404040890234070580608143e70e15994ac64d8880a28479aabc11912a35933c2b8f02e412e42245249c57456517103ab0ca9cc01aee0ac206165c98acf0a1458563acc65e567c7561c4c4cb509151c3d48680d4ecd915e5d2a43bd5209b5ca1c70204b35b4820466e110c2ca12c8c2a164c5a757433c598102af86e002cb0aceca0a0ecb0aceca0ace0a8e8b5dc159f951b541252474e3643b8ec576cf308a9353223d59226b4d07f806bca8d67c80658e442a74606aa7392b9a43c25343c5d6e0b00c7c4d32e3574d028037186867636787470912c87ae1812c94ce2ca528dd78a89c6e3de2257dcad06ceca884de8d4b3584430838940cf164082e64d8488a70f4a886700861082e0cddb89d6a08530d8d54435235a485a1211c3dac634387657e7a6ad49a254a608a07a6aea841526b766aaaa8d1a95151093d1d351da8f980500d0e1866f84016cccf4fcf7d590259289d1d243c4e2eace2e918e2c9bbb1f302ac8a8ba327c582948e6a483507918632af56a9428421c4901062c818e2897c72e14b55082429387a522c50cd4184a307470f0a8e1e1c3d178e9e215cf8c6d14395b00c7c03d12e252828284886cf926a9dd033841e6831b4069f32f420d102cfc9e6a0a458219430b9f0409440204b80f4f44a9a68546ec13013703ef0c6a8c1b12104593540b0262768e8c22f9840c575a2e3e449b366bcc756fb840b3a630c2969ee0cbcc2f103e6a0f6ee0607778484186219f821b820c67dd22b21e2c01c7c4f4a4a07b2844802a7c6587287204b881e70ea074fc00287925e098103e6a0d08d325c789494ce4ce9d0944e479fe08494ce853b9709383b171e8646adc9812925387a7a5806de56f6c1d103593087c7e1e3d38445e58a2b784e5aad396f595bb35283f305da6d3b3b9b15cd9a71f8d932512f00f884da56675c1a35e2e0b46276a8353aa85ab35393a38409385a286182cdd30cd82c3356546698b293addcaa303f0c0066cc807941019d5a0247b8b27065e1c2c28585eb0a5d7481842f9e5c5740c2174f7e7e7e809e38e902095f0039f9f969e2e4a7c4bdca3039554fc7b4dd9b6fce673b4d8bd7678c337e36d44ab2b1ecd9e8da9c733ed2dbb64c9b28dfd1246df449faf62ec7cd4ab623954828b61bbda3a49748b6dbb49989de1f6598c87697bd8f3e89853120618e2f725d63d0114390189edc1e2e47410c575c163ebb9ef5f97ad6f04229fa4504de4cce8edeaca1eda0edb27791bed7bbaeebf044e07d59966598edb26777d12edbbdd1b957d316e93bb2d975ec326917166410e306e07214c840869efb4a70390a6350726709638a5df57494ec157f8833d176db63f617e355bb99be321375f1a6bccb71b177396eeca1877783f452974adb4ba548af7a3adee9f6226d89c06bfa8e46b94efae93b1947b13625de922dd2d764bb0bdc74326c91beaf7494a7546a4b3d3c1da3c7936a0fefc666f98e6c8759d3afdac3bb616aee590de55d4980c2f0749a7421723c868944efc9f72eabeadd88d7c6e3c518af1cefba2e8a7d2d7691be700c538e80f2639a7b4f9a8032558d60c51833cdcb9ee50851f0b409b5de9e9529a01d0637be22a042b26bcf4473bb5637d145e7ed3ddeccee80d29a4e2fa6d34baf9aa3444194f204f4fabb2f22a00fc273364671f477bb1ede0dcd86818a1f1e8623173ec6460fefc653bd1b714484bc323e1dd276379db43dbc1bf117b8b9d7bb8e9b135d98ce94e798d833f0d7cfd1d3a3527432c94b1dca977d79e5310933593b793944bbd06ab14709eff5a5dc78bcb328478cb17b50710f39725c2f06210a62a8a2d4bdcbb1cb71bb7e91be0cc5e52bee002e47c10ba13b7946ade3eb3746a278224271f9be7b5521448cbcda710ff3bb678b749783c7b5f1b8aeeb5ac56aca0be86547b68b57f6e86e84407b65b6485f78ec3dbedfc3d321b242deafbf5fb68777435aad39bed176efd0f6f06eb43535c79f2ffacc61c915c0e528cc21e7ced29c57e477910891ac7922db1979e276fc227dbbbbb9fda0d0dd8c3d3e8a627c89b1e5298ade424497dfd19748243a8ba8d6dc93afc93039a64311ca32d1b985783418fc609386248e18378211c3b897d0bd0e74afcfa17b9d87e6fb02ed3786c9c715fcb9d77964971565440b14f22de756ac28234e8f3f7d8122c1a259d407e5a753d74f4c5ca48e0dfae45e672d507ee415f73a5df3d23491b6f5a07c5eeaf48ad2d3ebd9242eb20a1d978b624b3eb6c4a4391a50fae83cf58118d65148adaca239b9d33e5cef4e28f5e1e0dd0035dfb5185ff08e6596414d5b93ddeba4cf937ac53c48b609d1ed86f0cfe5e6506c87aac9ee767adfd01d3273b9b90ea58b7b1de547ce80f63b1427f7fa567ba8b926b8b9cd76508c7b1d0addeb1d0cba3c543efe49414bf77a162d14f12e97168a2ca09047fd97fe4016ea2ebf609ee4e5b4890f64c1d89213978acafef29b16aaa2328b4a80bccb6de45d9e65d626fec51a413dde88cbf4c9de5127973995b54df2f21d9d55daa4b98bfa4056c9c94bc949c949739744725df2489deb720755afbbc49d7b5dca80720f8fec13f59bd685b279797171d22cac85aa8d04a62e97dacd24cbb2b9a3371ed91f9d7f4f9efc40acf64f3b69ee5251d9786417d579acf6cacadf90bc8b7d43281b97a38eb246e25d6ce3c0d4d54d7ac54d985cefda4cdaa7b98be5d71f4fafe0e995d1cd92a10359251e98bade16d38ef2aca2bcd194955b2c757458b3c7caad124fca2f2ce53c9945f969ee3aacdcb5e9b96276b13bab1da8242cf5c5b19e750ccd8c400000009314002030100e074442b15834a02b9bea0314000e98ac4a645a9a87490cc31042ca188300000000000000820030000030f5ba3b6df8e2a98bbaa483c84a878344d4b604ffc14019da9479c7a96fc80fc692e36a5f9ca7b0045075062ccc4d8598e78068ec1d7394dfb2aed10356561fb0587c09cfaedd38f052e3d2e184aea53e1eebf7ec97b837f5ef25f77b45a1110d4d31927952191c89d0811658e1a3bd962c010e0c9fd3951df86a8258cf69750b521a0e9898dd2329eee3a1bbbfaaac7dc064b15c93c2b082c7ce29c0e927c30cc9bcfba7a856e8b136d7949c7018b220e2e0e8c7ea63d7a17ec53a9555a123b3e39e6b62fb0e53b8186b9f59157566ff1dce0a2387490a9b139880632b89b1f3e9bde1c54df9c466c8164567bec965828b1bd728e6b8ccaafc998bf0f592f2155c4566b1ebe79180c12d0834943894e6701f3705b71603592d1b188bd132b4080fe777ddb3149e901feb4cb2c53a4e5506eab5fe9f55d14d990842b1b8b9fd63baeafcaa71f390bc2de396b30eaed052f1be6598e31d1418303f8ecb1c2c78427ae8a3d95eed9fb728a0a2b5876a32e9fa5119400b1f501cec3a5275b4eea20ba9c5091447cc29b3682af356255c2dcaa095329427dab4d59e202723e869521ed670c0cda76a90fe59da341d79249f7836f8fd2f4db7e924237fe1c774911735491ca16261c1446bdb29e4ec1afae4b96b5f50c5649f29bc752b51c3466c4c82c78101c70ab92b7b394dcad4fff0428da5e40297227a4829f6432119d4e5b9b8fc8a04df19bee011441ace7e6e85f3cdbb7fc63a371d090eaec6709b04de9d3a6b320aa27df9f3fd949682de2069a96114f901b706c739213b9334bca03c2c85871aa136d203805004ecf46fc34144a81036ff0df49866b0a126ef2b169f09a2f2175d896e636ddaacb1cf1a0dd6ae6bad3a61ad4a9a2a6ef2312d7dae4bbbcf00c459c0b651198e1d6251c5a9fb01b716c95fae1bc1694909d12f3c18cdc1aa7017533acd4b8afb8ec49182427ae18e7d1a067a00e2f46040f1b5fd1fa6d296f2b44f092093f5159bc2f46476d617c9e8fccab072daa5455b9c55d9a8159155d3c3ec2f3360519af234f3545a58a84c1e0ceb0c8aca87dc9494c6b4a596f66e409930910269b8c9dfab7cd34ecd5ea60fe991842601bdc8c41e78a8450442da692d13eeaef5774bca3be4368930031a3b91e6ab4f37dc345377f432def9736211f8e1c9a1cd233e5ee8d9d820fc53f2cf1c63b2b038e610b6226c49053ad42b200472f2bf7b5c3e3b7ca2a188e5edaeb2e93aaec504df8ee601bb242fe9598369ce2b2b48745acbe2f62c867a629b886e5b627bb7f5cd91a552fd8afc7785b0024aa305e75be86464515b2b9a2f88ca114eae87648486f775a4624d91fbe8ce2b2d9f42f0a12b7c30905ae5ab2c459260d101c98539bf7b7748e51c7a739e496afab937b38d00beb6255b2faa8dd81af65ee19c38faee8213daf16552d29b3fea20526f070930695bab5327adbf54b09c7128e797d6fe520a6081a9e3304f2aeb8d49a348df7717c8a3b5d805c2f6a004f410918e0af4fd338f58002074100b2e818aa8704299e591a49fc245af5474c3c7e924626f1dfdd456bf4dab2cef2e1a003aa6945d29e80eda6e89fc44b16d08aaa63864e0fe1ead746ff2fc5cf3c84bcae60cb50fb52ed7bbcad9a0db9491a43387ccb60228e27e6cc05abbab2110180d07b6867cf44a6b6d66e3615f9f727e2b5a2599f2f87647d43dbcf05ccce06670621c90b79f7dda98f1bab7e1aa75b94e2a1468819eff1c231691d845ab10872486ade8e1c99e0ef44ea7e221c2a9309fb8f910a9753546dd1e41d7045cb7d7b57bc44ee493000b504ff8070cd63c5e7cba851a96835177438f52b07234102d16f1fc464a42ccd4c9d3104caa5fe98a433180ed97384845c72cf62cdfc716055f479a0b8701feb94b23828dbe54a45f8da02f29dfdf2e527765a00df4e59a84c0ba9c4c33abe55cb93032efa2af9d4e3b6ac7507f45c3312e82ca3b1dc2f23536e19d2fa13a9a86cc2127ad4fbcc3d17d484b934880b896800d6a8bbba405f75b58da4b4c2626728f625e00b9699daace5f0dce48e4f09bf50c61fc19d9d4d2032c820e0a699d9bf0beca1d8dbbdf63af31efd3dbab6e6ac0932b2f6658b0952cced7c02fe59c705650652faa6ab2f3e12fa3ffa6f5ff1e1e16f5d07c4ef3f6b3c22816519c3ba88f82e1be7461e14f13c2e7dc5bd6696d19a30509855c3e99751a77a41550d80583fe0aa5aae687840e8e3ef98b77ba78963bbc1d65dee4548380fc4fafb34201a5351097bbbf6b69982b53c054bf9f404681c9d296066095f9c577a6b2759674a446cc80ab4b1b1bc76566b413fbe37f8eeffe516ee7edc57479ba631470d788d8b7f9c01e2bc6e0fd971f4d65f71c4489f52dfcb7f13130177af26d6111f0624baf955ef51d6c15838adff79478d25dcc055127d23270a63fb912635e48d4848ab2435b4a072e9289dada1918657ac5d83ec430161ee6aaf00aa2c4e908c475b2d121470c251cdd8f6a96b35ba805a0f1a0d91133f61fda395e40b11eea86185708f90b3cfa029f099695407e4ef8cb97c4008a7190f5877d1c31fe412e78c09f46b2e1c0b492edfccebcf2d7213c35becb7109b56e43a367a3eecdca4046d77d5a60ad396365616a4e1a81ec7ef16efd769e193728bff38c372b29df07c45629ec3d37812d4e9141b461a56ce000d6bcb3bb2a3a214ce1846d66e10fd307c51f7fafd6131ca12ee79fd2dfd37db27522be7f626e04d5e90f1ce3776eb7bb4d95d43b5d078c35762400b68d9d6c5da5d106ab81dbc30f5cfef43df4e9828defc52984bba11367581c2224efbedc761a0d41ec3dfb08a9abf78a18b0bbaea8519768d47311dcab1dde8bc5222d917d4ac0039bb203c4140bffb82c80f2645b275947867f37e9bcacc0ee45871ec2978b03838d64fbb1911c9395ef6b66fa137f8d8a9292cca2aff128c79d5f171e1a889a9a44e6cb4a1596c7b893345c48ab401b960bae40085861c843504cdf95a779790660f73d90406a01f74991002688770a82d0cf426efa04fa63ec55dc6788cefa26c8e4fa678cdcc9816e4c8deb222725d004cdb824cd647f31e04336a73163796752db03f89be21b9b1aefba2fff174a14b2592f863cbfdc8084abe671282e67786701e3de91b605102023c83200893d5803670c4a299a15320c30034791ce8dcf79d4289a35050916f21a60c36ac917c5676ae78301630706aa591ad8e4ca3547612aa1dbf1c5f2ec9b0468340b930d852eb0ed137d1326243d94383fa228a3ef5d06fe582c68b4aa441b6dad283abc603c6fcc6b586f877dec7106ac876da41220be5174291a34da7c05cc7f82d10a25d1cc0d4b69d5904bc8efab6fdcc55a8134f7287d079bb050fc802998c9d32d3b75268d549cc047ca5a7121ddd0180e66510210fce145809147502b8d2f09946c81e3fdd0adac4658acc52abff36d6c14b4081ab78778cf8c60b8065f7c05c4b37d562ceee8a450d9d08a61ae6ddc33394eb7877a1eeddcde35adc2bb8afaf60fc13b7680ac0cf235a3f1e8e9e13c771dd698e03a76ddfbcb7d1f7ec5e883f0a2c3c97b3f9bbd8c5bb67704de7a22f6200ace0b9f072a4291f2074a891896fa74bdf5746fdd2d6871bbacd4a741e8bd1c2ff5c5501b2aca568b72ad716776a4d73a328f8d25c02f85aeac99da3fc62db58673833f92a48f5dbadb9fa1e1c846380b65b895688a00d850b9cd4315e65f6dcef35d9ca7d739694956b5f92099307b61769aa9807bf369c4f843a84f1045d933110e29be671c6d0b95b42e44d9ebe07f6b86316737b1ca64a973a3aebf292a7cd219d978f8f4c702e51f64e447941ec9c81730214f701052f22d42d252bfb7ab739bf14cdfc91a11b5d0c7b9d109677df358f6fe9b6c1dc2c67723b01c7248e4ea63cae43359ca85df4ca0fd794a82707f6b0a8261f9157c71a860e4c491601839b7ae97214bc08a73e30272685ed92723e17a9dc61ab33901489a5d044875736b7d2cc253782fa3838b5f4750eec39f636ebf4374b512932016dc2de2c2a0436616a608b22656510e7c7758a6b77f0da539e4700916b74b5c7bfbb9a3ef7b7013743f7bbefd04152754b1b970e05e17d92ac1d413c1dbdb8f037886020a923e2e90c5fd45ab4db8113887b6670d7441ad25ff994b1f051115f773d6a8098c43d392e665226caa357bad37ad72686b7dda939a613b3cd5d58977615b830126cf65001839447043f43e169dfb3b4c2780246be902b3495e413df65be42c1aa48514a67bd57199a94fc48964ce6a78439938367f8ebc5fa52a1cbb5c312a112f49668f20be5fb30f5958390ff335715eaf17d23d25d9397f912cd823e353aab11b747b0cf4eafee3579ba76b27ee226076c6b3026a946c04280f74dbdad5f3051bc8cbb0e3979dcc7406f41a7acf352f364377d513d9a9a3b2167262cd423fea873bb048f5fe7393c446f00955efdfd51b9a2821d644c9814d015adb0411a1f5c0a5d118f86769608820232a608a141f0f2c868588b361c7e7202cffa4216ada60e0afa64b0dd99ade0104a896ca85482cc2781d4c057b5008ff62e0cf3af91b7051478528c7213ff494c07b034139a047d01f2a5f3790a74d3e941f90bde0f83ee36e2c00f97e18ffdbe193700605e1d6f50a84187541e822519bb22b55f87375a33e0171405d6d6602dc1d6813fed2513aa1ebe769f183f13eae63190f71fae8f0f30350cc973324360c0de61f0f121bf71f0128aac6a9fd470d7fde4d2bdc41193a8dd370edf097141bef8bf15184d9387dc815306c61ab8e03ab421fe2ee3f42120dbcd5d04134585b43ce439036cf360b2b7aae89e928e282f2c101e4f13755bcd68086312941dbd8246b0206b10c46cc6a3eb01194d22286424c695a35063e116db2b35a8b5be20035c41c0edcd4f20edd4594b35e8f5e7436ac8526f60e9f2ffe924057acc91858ce7aa37016f6268db87469898297b0c1d0ef7add98b239117728a710bb5a701732a926af519dc9883a1e1efb64f81206254b53e8cb1831ae04415b6425c27b7b32bb2829ee91638135895cb94911d4ef4bfed3af7983ee5672a3a045071c24c7e9f55819ce59ac155b976447617051bd95492d16580357b4b783b0ba35dda963e43548360abb009e9d1a08a2cf1043c6b74e95032978cda0f927955f9e0f2f87d910189757045644463f7f04e4f0f838a6a595254cbc36daef2c10bd37ffc9ad35cdd5628b93e0e32ac5f87aaed00b8e10ac7b5c3f2841769e6c2cfaab9c394925744d91f3d0fc0f7c19ced00b866fd291e18e505e04b87c5a005ae74c6a6abe3a6f5d6b827d3cf8b5d06698780f2e413110c3ebabc9e7cf07029896d9afddc214fb37394343764ef2f933d5ebf6aee6caa8ebe87901775303852370f1e6ef200096eb24e87b2c514222555e8d13770eb2ccb591c810e7d265058b510ae64aace07bd07fe658571cfedabd4540588ff859b755685ed5be4627d0af67994fa93117d4bb295a27a6db9beaf54b71e37ccdf57995c7f538196c79c24a07819510456a20cf0a2a524f7296ef82b68fa297fe458fd6c2da785307b352d5262bb44d4bfd3cefc164d5fc21da60245a63cbe962a38aab89e2932606496251c4014192597ea14ab238b3636d61a95d83bf5b421173271fd35ffa7404e263be157f895350a138c3696c584209cc5322f0b8ec6753b3f5e8cd7d9fef07b2724226e8c79995d650559659899056a4547d01f6f52946e64316c3863a2e90bd04a410042f58a2823879015a7401c12f284f94d4026ef94d21af7201fa97224188db6ae8e18a56ff3cb321962d957345e2f585341119496aa1fbb152857833135a0a1f13c71d294e1af4b7f11a5c2d5a23a1d4bf83feccf87fd267b1a056ffc638d20db9ed7d056a064fb5fa8fead59fbc5c3ce196cc5e77adfe0458a67cf84bec6191e6fd79cc82bfa3d2704a32a87199926659a5028b8dce382b4b225f507a9e638d274493af01dd6794f5ef0f3cf8ef8603752d2a91503eb53df141e89bfc1bf33fbd0f569025c3023ee3acb153fd06458ab7ed4c29f7223955857c2ed78c6b15ecaf939a6efb37708d670590669205fbf3d3908f362e71fa0c248159199bd8374435be84464e56a1d97febc04f1f596b20f36be1176d383a5599dd314216154add8705c4fe3ff10b207cf5a0b111cdfe79230bb5e6e293e232abffe4efd32b356a494f15e2ac3f939adadf6ebc5e38abff6bb6179439d866c69907940fd1c1ca5dc69a3b1069c89d59ac3d4ee009b86263117bc195a38d1b301610bcdef957fd5bfe8c35b55e89f1445a5b14e1258e0bff29203015deb6b9e4baee266160dc125b6a98c43cf2139bd15016efd32fa5a47284e0fb05705ca1dffdfb72ea79a3132981d49b3c0ef3e500a32f4b1009f6376a90f257caaa6882fd4586332d8e7649929c98d8b1abe4fceb579bc702cb8b0379632d9e048774dc19ec53e1e4620b3b652fc55b2af84c93db385a82a7d852ec1f17e3befbb045dc03a8f8d78b08f6ef19f77be5ceebbf14ca9b78da123656cb332792e3beaf5924fa9760ffd139a5b736f7d287d67d436f3e80f57e953e5c67cb2c60ee59dea394ebb4d33648719396a67b5165004c8e50a59dc747e39f040dc814f194e4788e081cb571e9c68ce209831511c595961915101de5bdf98719a5e027a03745167ce40ce2916ce5e0b96efdd82d6ab07b003721d48281cc67383c905d61709530a032c11819dd058938c5165cb54c190ebeedbd884444401c05edbd2c15e083578c29356113d570329d3cb89a6fb04a36dc69306901e733526e1935b1dfe186133d2226fb31296a564c8dfc850327e431da7acd936a7f014b0449974477df494d45adcb5577d9ccef7f24d602749df63ca2815913cb240edfa1b8f95562b4a926153d99d7ffd66d0f505915f3197bf0d537d50bcdcf93c09293a6a2d5c2fe8c1720d9e43755ed490c1efe92ca25910bc49a0ddb1b77317b3df2270a6761187db82014f40167044226e2909b07ce3ca6c75cddb9bc98c28304ef2da53b9fd490db0a533db2bfc9838762b3b53f57f6b4a77a96ebae7cc909c6833922e1a7bb21118f8bc4001ac959f49ddf4447f27e2c2c2f656c437363866b881db4d9369f4eea83a7c71a26343c45fc900c084d0d47fd751191e6bc994823e37173235b137394deac96955b482e773ac485183704ba2f54a9f7581377640908fd760c3f9d723a944f0c0632e0dbdd552b5c1c06d04e028e43707179406183b87a53eb6c85b392808dd3e4fa816e45066cb92bb6fc5b8860cb00909a92a5af94cf2a6833a61fa2503b1706bd866bb983583baea2a73219409dd3b12263da9f21fa13addb8b0595179fcaac21c8e1183ba79644c62c99c6dc3512bc80608c8dd15f98be0cacd963dec3546677813d98a0810a3a7a1ff3bc3dd821fb55ac7dd1983af8ab9542af825835fd7797958c270be77ffaeb9df57bfae93335ae56592cc8b8f9f4a2b69d2558e2b441896105ff7220897469ef3916ea26d0a181727f82dc3edfec8d4888ccb8d96a09571c944ef86af1184bacc39d5d6fa59c2aa57dc63bd975d9792ac221ec8ce3cb13a6c7ef99136c649e900c1d09588f255ad83c61314554a9c4cb3e508d93d0ad0b846900fda09cf6b3e8875637a022a24cb040f1d81326638a5902ff4ba85b35bf8ce9b06e6ca142ca016d3ed45735764ddd611a083c63fcc151b9c78b162f80b857b18a459e90a15d464b3392708a48490539a10368e1552be5846cff8f86645bc2fbc1e4c9d178ee21d8f1cc416d71d700480c0aecfbca8edd4ebcb4f6d6203e635a12e728c9c554dcae800205f4bc0fd12c347f8d3644744f805f210a309c76d0466bed3b1ac49be20ee193026d1fe7d5dbf01cd8bfadd04e456b05f4c143e43a2256fe2b178bf636d71a6977b22b3bd9aa0eaf35f470683b4f30598f06ef5c47c43b94937cc8ba741a558087d95aa9eab4ee43c126924740fc580baf5b0aea427b641e671460347627cf118e8a181db8829bb2f989659d5e6a82a56a4d65da108eee2dd84fe675409e3515043e3156e0f2895c7b7424b75d2591b9d522e097d37025402426cfc1ddc506c1373299ac0b71419d1594407fa4421137dd673ded746ce8b595685097b081c056cc79dd396fe09005f09aaa7787bd0454f8d095ef76e0e85ac8f4d05edafb8906f5d532880340f48f0e83c647a66996ba441219453c9d94d937651d7e532818650a53d6328a3db8cf089acf8c2b4f9cd1defecab70f8270abbe51b9581d9aad44b4b9db702cca87e8cd13beb9edd236884c31bdb3081f4ca9a054f8edc691a6f45b200868004fee2df524baf6110d6642137eefbd236fdbcfc9b56c33cab6ee288012785e2c217a24873aa760d072321a492a441c3cf6f5442a45e3cbf7c368e3392f42a5aa0acc220e2ad840449a7def04ff17cec9da22a9d68ec44976417ba48842765e6ea40c752355ff48520d7051083fff7e669342dc7f898e14feff881cb226b230fe004a6a7d3250ed8e6268923d9799169dca728174dc460b18ee5814717a63997eebc2201efecfa72d5be0279961808065b265f7e42c47b462e03c0cc585bc7bc4685e5dc3f90175ea6589ccc08d25df019b9ec7a17809b9b726601e6e55c20c94fc3865cd0cf2c212d9de2ad7e1d116beb0cc4d137d99fe81534b791b2fa43a146b928312e55a0b3490fba777091c76216ab84e5a302c28c7017e8785fe37674cbd6ae78fb9b72d289ec395ff4a2407d21dd4b1403a205cc56bd8d376ca8621ba32e7fd894971758f23d1cf92d8036f2bf6e48097999780b93f1d829dfce64b9948682933c4faee1d298edd4cc6b0304095fd092b240840af44637b5b45ce8a3c2a3c0022a96776cb2ca32b31a127e290f16e8dfee851d1cfada247e678b6f7aa98ea90bed1b2bcc201ee4c9fd7799c1f4b7f5abb1ade5207b6fa64892a3e9cd9e602ac5f1c63f1c70fc28e62e5f2bdb958e0f84bf54b12c312d96eedc3c49a7dd76b7e47e2434ff83bf8442baf8d2aee947b27555038165d28b99e269ee7abf70759296b55f400e9f8824330c05ebaddcc97f507a412d05eaeed624037ca39e51792f28ebf3f0338acd78895b4df6d3c922e1dc6b28a64255e83f4d66ce4aa535214007f3a76937d1405f6ceaedbabbabef2d49fe6aa235e991860ee626385addbcdc476e50dbc78dd66d2b48c144a84f63ab8e3163c141c42acc4184264c4610284f60e780017b405a5cc8c11c513160422e776529ed4aec343ee329e1849c5b2c70378316c6cc2d956d0b754b56a87f926077d852979b03045a5d832c7a205626a6c78e947e1f832aaa8a85819980220683f9a02382474abdb551d2194faf483a3990ae2b5d3b4ed2ea255e3bc8f2765795eb63beeff7651d2a33757ba47083fc487cd6caccb95467275be2d63261afd8a7b75e4af06a506ffb673dd59819812871b541970e74dfbbdc2a17261a271e7dd7240e4384c2a606345b541d6a42a206ffc0457faf2f9fb3eca0d7e20077fd749c91ec77ce3e5a4ba90a6f9c8854361eadfb8fb7f9a803d1a28d2f7261ae293ee29a3f2d99fe27dafda36c0f12086a8828d2056b8a8d00aa9884f1d62d7d51f030fe56112ecde7b7dd19188b1154f7b0a73bf342ada7b4cbc15030698b58d0118b08d5c125131f59becadffd70c1882c931591180cf01581f19fead1ec023c70a61e9f1b14d5e679b850de07613f5f27ee578b9ba718895723e6fbbec3ef5d60c17ce7b00578ba04e0dcdfb37d3aa40b9770407b3d608b667032434ef4497001d8a364cfa31ea7385abbb50ee6eccaa5ae32065050186d65d5bead9d0df6ef86ae7aa90f9a6b86f731adeae494270bb0283c04c6b0872d79035142aaa598073b2eb280bef86cc7da71d57054796e69ffa701d7cdb1256a6c4cdb4af14f18c994830101552cfee777287057b8767b750146ae830b2a02119c3d1c967cd2a74f6ef9844cafc772fc3d91e245665aab80a9924cccdec14af8a40b252704e2813146638dfee09aacb2944a59747f7e71096684d6581765f2fee7cb163f616bb35b1bf048da57cadb8b88154d4376094373f6c407a65a9f06730fa2462e1725e804fffda0393176cd1864c460eeca3a286042749080c0d8da3246362f3284f8e6e7611399d630d9db1d390924e8c082e11a0bbe7608634a0488995475d57ccddf7f872058465e2162f3fd55a112701a44de6eb5330c67f3197134f06b87cc44d4457de77304c8fa4d4388aa0150701fb7116c21fe3e19fb7d27dfacccbae2b8abb92eb4ee4c72406ff445307c1a9dea7f4af0042969882718737650b587eb5a7e7a0a00de8926813cf261e0d463b0d6b7cc9d570c8ae91506613abb17c5905e5c4897807acb284e892758430997e7e55884b54b23ebd0a80802bd02ab12a3918534031fd038331574541068c4ed8c0a42009409b5165bdc232799a3655f59c4b27708a5c54133339879ad79265ce403506d5a31faa7b3cb5b3051ac054ec0ba9bc5d49b4749f55a676f9bd7579b9bbd4b36ab856b5ad1d2efa0dc14bd331f2445df38504681ce06455de16d3e1309bbe0e2b5dea84af94e88e662364d333110697f937b46567474ee575eeb33ece55e254689ca022cc6815cb2efb63be568b8040e87c098d5ca825e1964050b2eefb654eb58f9b0f9c3d9619a03db55eb1b7aefc84190664ae8ca3c75db0b8bc23bf00b1fa6eb071e35e06aa5b636a83e726a1a275388940761f9511d600f349e048b14dca051c14db6599c16222052d0cc93188e7377be5ce4f4674ea8eac026540eb0fe59c83bb15d84f03d8f1abcfc6a4904885fee8a104270d56e11b0983806db86b87ef49fcd1effff3cbf93b0df79c2d16f868caec0bf86a842346d33d31f62bf7b65c6a00bd07ece0b099d3c05df6bdf4fb8d69bdf0590c6f775fbe95c0a1f26ad82e3d6833099cc1f3c96816902a6a8efd9993a621e156389628506241305997daf4d221625612321419b26e41076878604dd9578c3eadf3a574420ca26f87077ff005d62a777c5865849d66a48a7bf731a3908410ae1cd5f4a625efce39b75890d9cdfcc843f6acb6e78b3cad2a03dfc83ad0e390679f7c059ddf52bcf06cb875d998a02b2acb381898879ece1ff7a626f18aef8d6d91931020e25a0fa1e0b218a24325a233135a342479a94026981e901aa0e7927170b9f9b1b1e14cc4e283234f2ed43f7124e1aaabfa95a4a7c78482c5a7695c114d773a26644557e61acbc8ab5e3749c984f7881f8448299cf0f563ae77784ea26387ba7313299fc2ddf46ef7607c49aec1d1f14d66d8c3da8af210747feb0d43bf4d13b84aa3d67b35e02a4f66e2331f8dc33904a6a614425715900860bedc901414c559287337063c0230b877e56e5bc13daca84121b7b5892d4676447f80b0a05da5c8c5dec7eff53e228ed3b8a0869f142f6e1ca64f8e2461ad00cc92947918723d3977fb94a73f9b4e7be8e775180bbe111d7e047337e4e290215ebeb0ded5f500fd21b2beca59d349a2ad7fb66210f1c5f6c36fa8d69250ccc15aaacb0bc507e178323e7ecad6fbfc4424602af4681130c32901f81290a3ee605821769ba61a719ab5f20e9d307f52726203eb0508019904df3c92c7401bb2af9520897582d186f69e71c8903765f0601eafbfbb04c25b919f295b7c45c0a699c0002a4fd4419f0fb8a8c109a1b6314b8b06b88685e86cdd0a1a3de81c08f0520fe2476698fdba8307bb1c3c6606ca4a1ef4f8c68ff4346803c9796664875463de711640ce408b925ac61198cdce67fc7b59eb7073162318972487f91dfe6aae5167ce1cf399dad41661eb2f19187b60986717e077e25b30200cf643160c0001071539b6c31489bc4aee4aa2248ced1a7eb62070e9569d7a547508900fb00f56a537cd4c10a76fc1ae1b7dfa005ac32c01b8a0d3096e42c4a512beb67b6ee803f2fa330a38b204c6206395b4cc2d07698762274724e60ff68a3e9a6819a6eef1e3826e8f2ce5063fee5404ecb01107ae2dabdad623a8cf2a8d4a1b692805c45213a87dde9a344a09209b33cddb2d21da009de71669454ba98dc9a3b40398d908f9e81e1460e8d0837b2c2c42593e252398017079e755c854690475e314cab116dcd239a15231744f2914e3dd1ae017c12816f552ff8936d89c69414ac6b08d1c010da94f8998ebe706b36cbb9a3b00c0dbdfafb287fe61d8456df971f8d0194268552fcdb34b2316913c572ea2e491bf78615dc003ad19f3d7eb7bef86bf040fa95b5959bce05d06abfd7be672e2923317ec799bd62314c5cdcbf4706aecfc9bdb10b6671b685ec8480899666e18bc37e89f5e7781b8e89bfde9a9c086dd1acee00df351c18776db6af97ae38cbcfdf2794f0243d75e5071b0a25db96dddb0c845bb42c031fae3d3806a5d9ab778eb846c251f5941fcfa3cf1b0a631282dd8c05b310be628b475d55ec7680187efcade648f7f458a6aff8739944dbfe4047ea597fa43f2747d6a9a6ac66d306a97a4fb063dc9e061b4a4006604112cc6806f6a10a12d1c997110e14a44f1ed649cdbb8d2f7bacd57ed48adb563bd95b37d6243746bcc6515ae7a7123a621bcedeff90e552b759ea194f9dffac95bf3e6bfed276f4dfbfc62abe6d364f81a85fd396920f15429bda3e9b8a1ee59e1987d2755fd09e2c665602f09ddbb00c5bb5af7eaaae923da39c2019fbad023d47d6b721de28d1fb8c4d26e4c086000b0b11260da6684890e5406d09dfef6b32301c52d05b9482c75b2a254c35206b83623a96fd3f2af9add27a6b3661c9a812641275056cf61ab22002692e86dc2f353f24443f784f2ed302289f23ad0f16af168a13773137aa48a22a66ec72ab4e10b792aaadd7bd07abc0a1f2d7ee3fa6f9c79e6b6be23c9e76f2bded23899759c5de11e15e80dc48127756cae95675058f2e12db4a06d5abf0976e5a33c320e884b9cf85abcb9f6712f3d696e70e5286152dfcaf4faf93e3a304f03d1d2fb58411e304deb5718a31be334166130891ecaa4a7458821ffd529e64af3b40244fa1139b1b2e1c1ce0399799b847794281f1fec28dd8a94e47a5d3826eafbfa2e74d5ba3e2764a45d8f37ea3c922788326a5fb5774050b1f4ef0b6f3461d98d5b93adea7c861a4e67c251a2ff5b705b0f54aeacd0262995c3545078a310e63f8a1d1b4aae9ee7888a7edebe6738fbfc230c89f277d96d11c73db10706450f1fd0a963459d633c266028767b6b5ce7a638e64e9448eef02edcfcd217963e682a5830a6400a4745bb184f94b6d727f3e0be8d7fecc1610c37e03ba01d33174e325f2c586cce47cfd963ba4475dc028af424475b6cfd160aba1d31cd1ab2bb1b6fa6f0dda6d93700f897810b30e24ab431fbc0eee07482f99054af8298541fb80527507f1bd31415937df880c421a048e1a36ef60a90176f607b507e4e909fb5ce6976f739a647fa27d36285bab9c07f1b03b2ef59f0c5f787a001ac42371303b7952cb90bf67b8e95b57f92fba09735357b0aab82207036f8c8ea713000555ed538b93511fc933ab9fd11675114f6240b8817fc14cb68104c1aeac68e150052d00417e829e9de9f8e16a30c0d181610d4039810dd0343b8d0458967c43d607288dff07df96562c7878342d3daede240c66d52a4485fbe161a8379712158f94379e10e5a2421bbc4f711070d9c87275a8eb2c67106b28f4f7bd93014d036e8bd8581c71c3548e29cb307f4558706df3d77c019040df6c825b712a037c1a738c50757bd468a744e24ccf692d02580a00f2908880f4bd30abd1de3f24f045c630ec57311f5cb8d4855bc63d9870a1d4ed30deccb0075b283c1bf1e0976437179c43c8f086fbcd43ef01bf609c889010dc4f2638aac05f644a4854bb4dab95dfd1cc2d756c77ededa8d7f7664cadff9598ef44af0ca0d0d10a9c3c2bddb0f6b75044d0282f655e88c0af694e71594cf086a76bc56e1777826fa9f10d60df3ad63d8d63afc87dfadfd40e697b27d0e82aeb035dccfbdbd15a2ac449c105ae9c66db296084a94ef2ce92a95215a07fe8a97aeb930191336f16e8a32e1ded619649b45f43abe6d9b475db36305df8b8f36357ed3ac72a443aa050dc8546e01469c9c0534ae4885b6ad831ddf17ab54e12a0c4db22569b5083e1286121724745c305ee01f53247b5a267a597b9daca10d6cf9ca633d56de5cc5abab1f13b6db42214ca2047d2d9fd5dce55de13182eb919875206346e96ba0d65fd85f4dbd3990a1943565fbfa1a7f3f6353645164dbb6133810c65d4dfe195d38bae926349c7eba673bc7870d0d4a3b4955481839273858214a3787776c850dd3386f004b7396439014f46c8d19cf3a02a78b845537f978d245841bd4c435ec3e89b7e4f6d5a486e4c01a3e607ecc9c0876ddad5254a2c3cdcdb84c299ab8442761c24c49ea7735115555f273d1f1310134986f410ef75b3ba175ba801051cbf645df873f7d4a15a03f9d6d521e74bdb3385516bac84192060f37b7aabeaff1c9701f14df9486eec1e0341424636b48501d49b4e41e6b96dbdf3b6947092b6262135e624ad1feca9e7a47fc0152e5f15f0fd801cc98fc1422f2a455cb8f49185bc4ba3d78dc0e5dbab8022fcafbf78624f881c3fb8443faf93f6d3b6f781f6c48ff0453727788a4bbb76e13f3a64490c2bbdd8f321dcea1162801687e1102f49f1b4747fb6bd7d55b7c98409815708fd4a88b74db043751b372f2e2072def05b460ce01871087c7a1e4c4f71b61d27969d840e18c200db902c13c7c850a0e4f2749e6666d2742e51da305e51b40fb5821924b729f3e3d1db71a15e7f741422267539e2c8b891440132c7ee5ac96ba10c8b69cc82c6a2e4b2f8f0b10e4eaa6f6d397398db612efe4b09393b1ba6e2c47c7b7a929d90f0f13abe57617bb5b152b140ccf2a13992e15e0508fc9d3466ac4f382223b26fbbfb91f7fc871b0b1ebbb3b33a94739fba7819a551398dca9817a18be4ff03a9ff91920dca561c0d9d50919cac6cce5f779ca5545df6c0738d010263036443c21624be0b16e221bee116d9757230083492008d0df65b07267cfcf71dba1a90b51d3637396c5de8172a2164419f2ee17fb339650dda68d59728f0d647acefac045b243d4c9ca05f483c3e3edbc3ed9812ca50f96c69756b496274942a46a247137732d8ef7eece7819f3bc3645ddf17ac762ed837d53073fdaeafe11e31e52ca7a2e15a5d5547f8990b985240a2dc9778e3028dd1f21d07922083d7c6c399b8362656c37a7f0bda7bdc3357c76805ea4acd5cda818803b287a54a698d4cbb7976d319afe29c2c41e7c72054b5aa654ebb5f7b5b6594156be563da4fc1b633ddc552c5009738058faaedd8ef3bdcac01fc57d67b54f46c5d7f66a7b93f6285a6f2c90a18fcd5fda61da62eb53126b92a39daa68dc528e67934bbfbd8a2436ceafcabea5d4ef0baa411a60c518b2b69e449fc454d1014fc138b787778a88d5c81b5f48dcac86162705cd71b7c44a924f4bd5ce531a1975c8980e69ff4b22b58aaa081b56ec7f2a2993da944238447b368068406ff14362a708040c98aaf4970abc52dca344b94f50554a7f74ffebb2a9f1dc47703e83aea7ef108ce4e9f3c1e4ca35534b511f9caad196b5c614d59545afc25d4e7002b91ed186739e8c62f1c786f2d95d67e64cd34e9c1845d933c524fa1d3af7e2c117c6d7ececa694a3922c6245334248166a7687062ad5404157e8640221f6175877754d9582dc8b52c839e885a21b3541b5988e7868b5267b36c927fdfc19861ce86c42002afba26328626b2a3f9703bba49f318d1de2bddcd41feab52946a9a588aad482946e791c2d606207321cafd10b09dd5996eeaf76fe4437726c0ed8faaced6eaafd5b4e395100089e5037f630ffc4550397bd818d9204f80e10e3c25f1eecf02a1d2881cdf329d85f14204655f7685873943b148a51905d275db92718b21f4e8be4100290bbc04f5a029285c77da1a0db7edcded0ff9a4d644f580e9f064cde273647b6adc52bf8ba8ac2ca477131e4366d96961e9b165eb07b7b4c1c6f0b0a0a2febd8364f019dc1f79fb35022cedf4422849a9d061e272389b2456d0574509c29dcfcbd4096eaf32ed3615be1c17fd9f343a783368525c3bf2bdb1fb5e0d0394f97decb62fa8fc0f23a72d5b9762f7f01ec30bf4e65133f22b47659c8957d29fd35612f3ef57837237b1392cf7cbff85c97a58681547cbe3a84f0430058b444f6a12c1242919e4cf6d95106877307457e2018da838e6100092f3b2d5fce9f47db390ddeb854cbadce683e533da9fb3b074a0439f0093cf259fa38d0ec9259b0fa88631330a296a3bb4f6a5e967ee779bf66c7c07c83026978779f2be6198019dd41af814b4ae9539a837d9ead3d1a3a24a077f7d908a4c0509a6c6ba08445de45911413b0d5661617ee4c64cdf147bb8cb53c7ff11ee190799f956f1e164c67900b753b715160ec0343f73ef9bccfc3bc8fa37a3eb61102d104f4ffa38139af3f8e3c2100522144ccf2d3713389a1405b18b419e7514b2101caa227a97006831f1ee6d6a8c692d419e3d19e08ccb6dd3b6571a145d6fafa093b2111500f89f87558c1368e9d25c79a08f1e55973346b8a3655007500442988f6e7e8bc899fcde07519345b99a257e219ef770a635e9e8f351891aceb3a778a224b2e8331f829bcc2e7fbde973e09b38c8432b67b038093344457b47345d85fd7c6d3ef0b7fc5404ef978fd01e564e233cb7fd7ca77c26bc8bbcf891a5c037293912c41dd6c84441c129e6978113bc5162b2a8204618c89e7a7d9687dc89ba2061edb7a683bc28bd01da61db755e5a67d7722daa7e343937e50f7e0e81c28a95a1307b4ff23ff0c38d7b828c31558479d7fdb4ee01939e898f831bdc747ccf4063ec91aa3f8026fe066495e8b1ac64cbb844e1ff2ef34994cc904884bffa1539829ffc79079c20a06d815490c0556dc9d97180b35a41676284ebf083b6458bd172ef33fbcd707d1309a5d358478564a4f643a776764d957639f3bbe0d50a40319ef27c328114b0509184106dfa7a5513b808e8373c3b5d544c26e982c695a433d9057ae15effc35c44a95477511cfc62d1048a771075a7c8557284a10bf5156574aa1707c9e93c89980925e05538d8c85a61be465a68c2ccd35ad35d76f95640ddfc085bf93eba49864c8ed2db27e370805bd639724772e3a68be38e381774b5ba3441e750f65cfd01b695b1453da099ac9869cf7dec4ce204d7105fde3183c4d243e61391ecc253ca32256bdabf107889c07a6c2da98bf8bf8102d5a8b879d7fef597b6a47869985cbe2e250128a9b705f9748131e9d82c8995164aaa1d472b5043d3316dd2dc3bd2d89c47884be47f1d30eb62e9d0d391f9b353b8b5219be8fea1b45c33751f41f97a48d51b13a365b9c85950343434246967ed76e1655419e0529064df9c13d0a15d279e9cf24ebe590cb48721f624ccc013d58d198ef7916504638cca2eda7d2d022415e04900fb6201b6d8f7e742867f2a5f3bbac2a1a738fb7aae76edac25a6b41a0cfa1d16ab61307aa6ade1bf241904074156ee81b64498d1caa51804db3d3b6fb9b84fd0a78991326e5f4771e1d67026c8b10948511f5233640ee29fd64124ddd36fae85c257f13bc518a7a0b203b85758cafb0b30d2cf8c7d96e8989b8f4594208af8351a09826d31a946e385f44043ba0d7d97f8e2322f191ecbc664887ac673f821cbdcbcd666342bdcb25c279cced0881fd7d7bdc1cc270708307c7174cdca91451829a622c825f6c83b7886d8dd17cdd738642cb6be28a6f63aebdb600e178af47da289a64778c089d6f9c9377e1dd1ed2bc517d9ce71fd831f6ae79a7b3c809b4a12452103a73bf506c1fed362abe339121fb395b1e4d01491c6c3572fcf672f9682da72394bb51a935946362f98bf810b3e9f27ed99368e193029e258f637583a9fe29578729663a080bc45bc2687fe036a98a853656b36e29d6d8f5c00a5ae1cf764b83d41475074b9042233e7c7bcee071e3f4bd65ae913d6cc217e261f79d205c94be4f3306ee41361b9cdf8fa1ffbfa6ca6a54a0a7113a00678a8e3f25d16d4259b7419467d70cc5d19cfdbf0b45266896503ad1b759473b57a787751586c7ee4d9545391f393455165355d1c882ab22f71dcbbf81e2b7beba40f119baede05e754cdccf195a8804bb2924e0db243a90543d6a8f2a1a702cea3c7d65d695b6e6c466e9abb5c26fbb4efbc1feb211803e77d948d079d87cd283948c291efd07f568458317c59676155f152a7a8f4ba29187e586110f757010f3599425fc77cd285eb67d37c965328d4b11d0248fd6290eaeef13a3a27beacd5ea581fe856552ce42ae828d9eb06d8eea7ff613ff6a2ff3a152ed51fd1fb6645837cbddb5c9f42c22f1b3e3d45d4171daa47eb5b57c32fb11c983863941989137873ca24cad49001ca126ad98bb7b559faedbd993b6b051e0e8e32f1f64ef6171877a5c640a06d8dbaa3b28e3238faaee20ea027935b4c070ab4e61c0e28e885c8cf286a06a58c5aeb9e519cb210b299fd1148c2a0ba74a7faebf553fb5441d174049001211f2556518c2e53c17100ec48795bbe91a1135adef350f1ca92c804c3687960f02c8619e4e0214987db195db11bf2153d540575ca24a1cea81e76b03c5446b92a2a2f2bba5a80c19a428dc8a24bfb3fb6c06a3b875cc38fd15a2ce8817d39949e691e4bbffc4da9f38dfa6ad2bc5e2314f55108c3556ad48e65ec5d71ae6d4f1535c964505c49f498adb9d3c42e6550b46dbc6797d45a0db215c3c23375124bbd71ecc629cf43b88d99d51cdadf839ed6f148d605915946bac8865d54727044465f20c80db7504bb863f62b43ac6a4d6d10e1757a7eaefed344d703e4b7cd26cfbaea5775832005c945e3d64791e3741200283b82c6bdeb5b29db3c9c1f1cf4f54cd84c56e54649f91390bb1c33e3a7f5baac1513ab76ec0053a3bc443045158dad58e599a658f10df9cb5bc1c7c84f9ca4157313548f96c1b3a5029069707b230824be5a8ae48e32f7ebb40142197a79007cec3d0aae12d1a144daab5c3d98b1c195828ceaf92fc6a17a9a5495f1f32e394937242f30d1200df92a0cc548a188d740a9e47bff40c75d485e4bb87466829bc926330c8814bdac03d89832a55d2f084cbeb795f315d449fd12883308a181c9617493426d5fd1a35f881f0dc4b1b397967683998e15d265302fac74aad7bc9979acc54306551860640d8bbb70a8c694af44e7c957a1b495a512c8e0beb43fd5b080bfb00e0b57679f141cb2028082572158b72d17735138a4ea8ad370147b744d497b41626c711f5d1033d59dd31ba60a04d75d456a6294f7752a5091840c89701d7b1507e8c7bc2e6323b54a4dca78f5d5b68703a6edfd8280891adbc051066257a94428558c9b797be30a54bd3d98c5409b8d63d31a8469979d010bcffcbbe53068137ad5a3f702a6a93c7cb88fe6b7885cff06e40c89ef68b8c78b578c78b007a57c1bfd64dbd81a150b38565f13a95a5cd18cc028a886056db67c7dafd9f217b353b9538ab05043b695b38a4ca394e1685a92dc7f2916334fd62643df0e3c004b0cc06582d9f692e19421185f1fda4974b7c2c638ee03f15d718e4056b58f430ff7cb6ff8bf85de8480974a58213847d5c743c48b4203abab9575b8a9ac7f36619541016c448569c2c04a47abc236623d0e2cc81b93dc36194aeb41eceb4aaa61040265514ab1e89896aa9432066bcdc63f02a11cda6cb6bcdcd9bd9524e2a39df2641554357f899a3a4cd4ff963783aa473e9a620f568fddf0497ea3cb9552d9e29ffe2b06ee2cb6b1bedc5ddc70e6d54364a12d4273f142fa52db382843d2fd2d0fe24c9047784362c3be05cd4ccf5ad1b3668d1d5ed8665457c5b988f6c5c64f95d035045aa0451ae0c50331970a2b3882f65b3da2a9371ae55922d4904cf4d1f2d72a379ee965092fea0adc222b9f507e741791beacca308aa0826fb1f23d05fc36eb03c361319b0d27cea4e01769eaa8dd2311a54694ef41a97c96c9ec3d77eb89fc3b8a999cd60f9512e4f5b0c8083011cc01708fc8508ce1b50c18a0134bca6941678caf82683d14c186df428a01a521aad1622d3434e4c20610e3d63545a18d164d01e5d1a9cd61599c266e5138ba0e059cbfde8356c67263ebd5f5e48862e1619b0e4cfbeb0a1aae8b92fc540e8025dae8f2802ef80c0d81446d03bccaaed9e241e856416d58671886fd2f6b944eae285c79d8322b295b2f20607c6c450d6b23f60b82e5148e8c5acdc8003eaaf2d5bc676fe2d6bbcb90eb9d912e451e48c96879f14a97707d9c8e228f265680e0409672546b39cfc93b0c83b04e54bdae074e8c1bee042ad444e03f664a2608d42ecf307a6eeb527cbe5cdec010fc1bf92521d1fade7a0b625dbd2a86d1ddf9d0b3e2fcb3d3a9712f37bf37a922e8fd9343aa61b75ff71910061fcbec76dfbdf70e4410193a099c0d4c9175412a0841d3c658311ea4cfe970d769a7721ab9bfb05d315023a548aaad1124e4182d8c63be87722fd723c9432e8ae89804c6b25dd0dcd5763599ce547edc7c490a1ac34eb59062bf4ff2326b0eaa9582512515ff38b2269d0be1676727d912492f3e95474cc6384b248adaa6e55348e3e12fac713ea83c3bf64661c602e498bcf658d2962bdbde3798a4a0216fa1031a83828525d1694e535197227642d9b5af917542457e670455559b3888cb2bf718781bfa42f01155bbee5d5f52aad8c0d5d1977b4c13d62a0f9842b6afa064a92e8e49738413e18a5a88019ddc5c3cf6dc434fd0866304b709ce27c2e8b98427e070cb2c815d9b520bc9e20e8c446dc855ad70edd008f95eaf70ee49f57eb0845c87355b64a58d4442f7fdfa8d8b1a9430050abd9778d92d4cce0913390a3e076ccbfddad8e60891cda8ce318374b9f7f85106d7c84ed0d8ef36594ea637353ac946c2ec0c87c31ec498e0a9985bf37283f582b15b9239114771ce40754bf6947d1af8ca804063dfed420a62008a48d997d841233cf89f4d42b9a059730bdaf11f6091519964e9b870d2b013b2ab6c536a43290369e10a086376b9aa22db5f2dd33e6d0fe879e8878723e2d9531b6581eca6fc079fae82f6bef00a5e794a1f8b7f6c61b80c937d791fc2ea1d77851459c5680c310bcb2d8059b33d1ffce70e87f4978757ea74e0bf85b3519ec7373da899a13ce808df483adcbb3c26bf2416b817f82e739cee3ad764de48e6cacc3d02fff461a8c345a2f73f4e523b18e3d33c85e59a6fd4f57e70d95a82b0135244a1395481812ad7576345610974221c1bb19f22872142c7eefe2b53564e0ae9354dc73e226dc2b091a89ba69556e800f15b5a1c16fb7314871033f823c84fb1cd3495be7722b8122692457e108ee00f565f4204ab66b7e68a6376b871cb6c9076b28ff835674b2524ec82f62b879d67d25125bea78e4b7864112518f898ee6a1d633df1348d3e785d5323def642a4df570b026f167ccf51838bc1ee27f198c2e2804c3b4513451934ada98e15a64d09aa499fd9e35edb42d5d9a4ed96d6062843e938aecdca7fbb07a2c3e99ea7d5f17ed3dbc0cb01bafb316591150a4eda37cc66859bf824d115bace090b23e85397410da615dd50a9237017dcc1b1c0c6aae2ea4a8209a8736d6b22ed2636c53544dccb48303a98618583a1508cf12ac658dfdf685275f709f8153dd555e541502d01d54f4f32ecb016da2e255e1bcbc6b1bf611cf89ac3e394b87f3d322d1aa8ccf080980383181c8b1a09f0532892f611a60fee3d09860446d7920bddb5c777120256bec1ea259f6385dca78811932fde8434a4543022308d117517ce198cf37804172c02230440c2b83e49ed9fbf38c10540123b821ea2415019913b0937dd960cad281c55207a7729a728e72c21b098ed4b148b6c0613de980b4cc2f3cdfc69cc8d1c7c66b0cd9d9a378f7c75bfb2aded6600cc3ed46d20fb95431ec294fc3a6bd2b09299d38aed603c2343e1f53f56f3dffa691beaed29eee1e330004f5c447e81fdfff9a07fff9b9576112a62841ba2163c89362ed880fb743e0b9737c1042533a7f0be103a81a24cfb9f067538549c2ac36e18fbbacec005c3fa4e5897d0ab0c6e1b237168dc3dcdeb80abc8670922591ce51e186f8a7ac99e29b2aa31669a9cb635bdb3b3dbd5126564897fd8ad128b45f7dd9dc842524238e06f0e0715cf0761277109ad57f47d270892cce4b768e7d3436c89700558377563737eadf1f8e80929538c486bb594571bb14bd8b48145e53ceced539888f60c6581fc11a4c81aa2d803f1059132dc7a03ce532a95d21a745cc8462742b55e70a702b9c531f4faf44c6d8c6dcd450160cdb40d43bbc9ae16c4bc647efc8368212bf61f801a668c106b8d22515210862078389be778ddb495ad096f21d131c17b741745d9d47e843eb0801c7c5258eeee5f02cb9c67a33d16fb6ff9e47946a4f213e48f3b151a6a06aff635db2e440ad412964c9dda5e0c3aae43444d0f5287681635cf5117b982a9e02cdd0fe933a1a5eaed1995be00a45010e232ec4682654214a80eba7b3cd72abd06a7e6daef1229c06dd98c1d2f66dd4d27868030642fee88d0c1d4c5330412418051fcdc9fab8275f13d242bdf5011917d2b31043a1546153ea4ea86ba016cd86df92c330e8306df4077890ab2c77c5f5b175ba807524f5c9cbc3126e3db28f5a7ed16b701e8308723390f701b5f19dbe1cf0ac8772c4277b265ad52b2309270460043a116bced18afca6901c3e81cd2d365bc1d78ba93e98e7ecb9d91e3a63e888403cb92aad09bb5d9eb0dd98d548b75e17cc7c544a8b6a82a232aeb90fde5bad26c9220e9377fa7355f98ddfe263e04c98a0afebf254db8957fa782fe57643af70db4c67e0b2a77bbf98773c5c51ea60d1c440ee1ad48701c8cd653e2b96c48e795118e4a3962dc49036f1260a708f8cf47f21d4cfcd4e034de9d4acb030e63d812026aa322be3eea19ef2395289743658a331e6db43f1385268c2201be4cf24b11ef99d36849816b5c2c12d0fc88c1fafb6270361f0f0bafd20b4dc98c31b60242e5e5348dc35a47b3f5816cc03011d62596246896c03e077ae9480f5f73da17ae38010c0246555ea28f03c4190f4a0639a0f1a308d924cb7ebf206e5bc018c5832b2c31f864c4c2151e1ad466a2600b1b7953a69b9206506150d1b9c9115656b7f4e60ca9008accff7285910a868885859d2e1c5a0a8138c588f655d971ace9320d7d06e0d65343cb0f38b3796cb1865b3c45858d007e5c8c8e3201a886e5e3a486029848f6c76feb4a95f11e83a7e57f3113ebccb24c8cac19419fd05fea690c74af854e514f04af9c9100b6001bd205686eddaffabad580678fa25961979c0f114493cdeb024cd605f7ee8ef33312c5a74367e7c2cb3946c61c1e8c60eb756a168670bcb56d0b23b0f2b04ca025310f3f40bbb976b5c8a914be7676276d6a5da52a47abab6e60d54a5e7f5cd27195a400977f6c3802edd1bc054ef95a610f5c4a0f9be87eb8e77902d5ecd78f147b1b2705c25dc4dcaebfc0560b202630273e3124ebd022d69ea93f5c40d0ed961f4f9e15f58e323a556f084842b0c83d4a12d012c4db46ae19bb6a5111dfde6934c5d297226e27f069a8943ea36807e419f5f98ba1b2206e4c573c550ada94e46ee01672d1f51aba54ef063402181b6496c9726eea185166ae6b68cf45842f9f9bb6b63b35ecd4f95194705fa262f67983d2dd89b479e0956b3203ea4b683095a7cafd36f67362677134aeed368253182ab81478f4a707619e2ace733a8cb806de5c7752af0972ed845756d4b84e721913d79526ab3ba46224778f2ce68e5988fade68fe04a47939144ac24a519112655c7f2ef36eecbe779a0406aab8343c0030da9313a6c10b250f52b6a7811c59d983d952071da91cea79668b8c2f9fd0b613105e56e40e59364eca50228c74c32e079e076003d6fef076c1b4a2a0b9ce842ca6401ad6d59a7206255f1ea464c86443ae40fa057a79a2387c583255cdfc4146ab7e1b748315d138bfb3c28a2adea7d745c6dd32a77af3676d4e5d548320013a0fbbc3114024643d7c7cc1c99a100ccafba444b68f70dad97e0afc497a4d32891952ac75934819e95ae10b73c25d94b7ed62e43c01b2dee10ab89949f79d5d7184b647baeeaac55e9274ab486e6073e69611559228bed4fac95ace2086914fee0d1710881b315077832b860294b1abe5772bd359ab428053c4b13f37b4d0cc97acbe36a21f9aa4d8e0d0db4b3f5266ff522f82c851320e73cb2e32231db9c5ab9654b2822168a915f8bd270ca2e5cf40b4bca0f275ab3691370ace183fc93b9f7e953b18cbb6bbbc4b49526eafed4ad9e02c577c7f38b4e891ac1334227694ef1c593789c69809c60f9601a557c7d4a95016046e88415968a9ae55ae4631ddf4fbf6299dc3389e908979025d2bae391e2463bc7196c5e7ac7943a298d4ed2f6831f8b9df7124d8865cbdb437d2829d2804acac7195a8f2a3f47274618281ceaac05db3e564a58ea017c6a03fc9cfe09683f0a6476ad8ce5a4b4a4cb03637e34ed228496ab64812bc4a3ad31fc6851678d16a5d733ff400a42125365d4bb0df72dd84bff5a5b01492cd0da908c1391f30ffdfa43ca7150e5898c781ce706d0443974bd93364f76ab1612162a69064629b9c2dd0a03344596a60f4f2ea2a8881d1ef236cf8dbccda6ee5a2b4bc58ac1eadb0800d3191a33a948a7fb3314534642744b36430e74309d1b087b8c3843b560636a871c3db89d33383a5a27f424377a813733a9dfa8349be04715e1f652a616464c15917bf05421f3aae52585f21974208d740deb646b70d896897c70f9ddddf6ecc2544e6533dc35f1e8e3d8df3fa43f1b6d124574725c18cedc30623a793c6ee577edd9925314ce9028f117ce00c3001d132d0db34c7e3c72880dcc04cc63ece0765c3636fab004705bc49c6bb0565caa3aed98c3504bcff2f4a0c9c4e6c87f8c91e680f14ad361975d05ab4576c47904a9e95a2bed6cb624d4cada0e918011b6a0ab34c0f6567838866e4e2eabde13487ddb116c155f984c52a7c3ecd1508eab163c5bd8e1915843565a24dcc740589118bef14a98b607060bf4aa250f932869606508170767708dfbac0a4d174d744e802b7ed398799848bb8734f32ebcf97be86e0e253bad285955e689a08c68af2abbf34f1023100e4185b5268d2b801327827e331fd5a3c0deb2cc02977fd8b1851b13ca1546b0c3f1eba4c8b00a0ac5f9571d91fe3ba437c1763bb3b3052c297cfa6061766798981442883a48a4d6bf5da4ef30e80e67c215f83768c4355b513492400b5c4b185e7da9e4760969420465ee7b295c113f091291115208022f620cd1e43d367962e41ef2451b5ded2783865c25a728354850f23c1ea99dbbdbf61eb8f273a449df914cbb54e687206e2dd9cd24b28090b78034a91053565d1e7d1cb0f6694955befe3fae0b5ec07a5a120ddbfc28fa6aee9374b0fdcf7805cd2a00643564efa95b1b49eea0f19f3186f9abe56a0b3fb46c52205a3ef57e597f0d1b2aae01a0c0a2103c82b82f8e6ea214490d1fc293cecad755c67be317981234429c0b98d50aec805a3a9fc960a9b21ab2b464a75f71ec184f80b11970980e704ce604d532b3159e69c9e936c361fb0bd2bcff04276957887ad087c28f0fb9b22372b9db56b3065d9adacb13a7b51576988a2f29d081e19340f0555c9dee90f9234ca69db000054608215430085e03743dd5a7ae3e62c39227db6aa58accc97925b2c780c86a17d0cb9a69871bb9c9c45924104f027b4be7f77f4eb39c6c5a4d7fa4caae87b24307a71e97a01c473d81c2926e4d1d684785417c7f3e926fb2cf54a7d00c94665cfa8838de7c29913874dbd58c38c9bd3671a62b41c88e58ce202faaa67131890eb3f664ab4edbb4deeb8ad3d350b668b2259670d6752acaad840729af439ed239086902ccdf0ba24e4bf138d36f2d8f80513133d30eeeef4d53c935384536ac9cfee3b62ba7757620f9e1541a67e9a6dfc6da27953c4cf5ab349206e28a0f09eb57d4112cb2af9410e0c403b393be343b39c99e33e73ddfa63f15a675cad477f93b932287fdf58bd467af8dd405d89ece4810b36e91f3fe5612c94f222c4f1f696df0db9c86b0ded449770da743ae89bb3762de3aa5be168d21b9251241a32605129b4dc6723ab298f649c5a176683c8302afe181f1e670b6bc6b7e9b3ebb38cf790f095d991070a31e3313cf9546ec6a41aa1e92d85b62ea70ca9c133ec4ea006028a44bc60477df2f35271f2fe2ea442283799e277beae0c02e22488edb5828b4ca122348b4a4c1e24919185cd58385598f4bdee258b3ed061a85298339d34d485b0dd1f4bd16757f46e9b138bdf004053e7cc30dd5a3d8134f111a914edb60b7491bdc7fee1d5fc1c7a7b5aa7bf6b7b877f7c2337e53c0eb70056235b440725704cf7ebce7ca395fc98a9c7bf4a01b6e91d0af61041556fa1c41c10b411f296cd0749b03252f75a22d86f387ab2b88e8a237315aa0136577f005e5bf899f120ca80fff490ffd80068f79afe91d79b86d5008349e833eb799d967de5005198fd8679a9236f46bfebb8a76f8e10251a8faa27aa0c097a3dc9cd37aa7148871be27a69fcecffce87d5f7ebf03827c216bc98373dff7394c7e2657f29e915a46c0eeaf9aa33a48f1175025fbcf48b28b6a86fb496987ea63486d126d7e66f1698b9a9f6d381c9abfe6d27d76d4d1322dff5001c04e79c75229efcf1c9e70156325722656e02604b0676d3ed299d490d8e93844ed72fef151d2a76a296b2564dee72ad17c7e7df1de6d4e73c1cd02037418392867ba4f0b202e4a0908a46b89c110c14b8062c384249a90c0726609b456c5a2fe52c7d957a083590ed6db7b039484bec89586ce63e25b49b3b45117bdcb42fa02113924636006030c1b3b3957720158fd17bb80777fbde34a0c183ee48557e6d006f6924ecc6cdd34a1ef41cbdcb0c63a4d491d8362121ea235562b33981af16872e24f5fde35b56772142c4b7bd7016fdded3f686e7e63778baea511f8d14ad63b8944d362224c12b01f2df4a736da4321ab0a5ca4e8c031580fdb9b6bc0bd3147317c8082cecdba0de0942798326153811cd20902c92d104bcefeda4de735d49ed878d4f2cf8309cfe02bec035589d49807588640466a49031cd1a0442434903720d3d651c4cdcbabd6fdf7b922791042c32b33f3e2a221ca5216a605a1db5568a43cc9573b82f74cebb5a40da6111b54256c16792c5aeeae0a7083b71f510dc419db64cb98a8a8812a9a7af6cd0d139ca790913ede337f216323a82cf5143ef552c552299db2460ac985bfae99c5b1e20c3a0d4bf006511c84c5ab28bbe9a63f4992f00638c5a9c04246298f0144170c1fcc476fa24f2f9f6066fc6026c42715981149c882832b09bf3a13b7e83306219c8ecb8c93ba051a98d18729c7c8b1d920543cfc5f15c7ca8a23254fc5aac99ce87b51308e3027accc9a4654127475ac2e6604819e55ca9b88606712d846eb5d245414ffdcb2bff80e4bd217d8151b0e1d6bf9fc53501590b3371a3e2b24368ceb127e6ebbc1d40186e034691035b987e158e42f7a78de75c07b3a845d27164540b2ac5214d9004efca366299dff5d076bbd1082b6f33b0046c6100023b571c3e9cbd7c02b1320ad6049296848a0714783707cd160e9186611923d94e163ddddff72d5d4865d5b2a9da198540e2c3926d0575f6580255519aa42a86b7fda7b8d40d40a211407a2795a0871dca6aebee0fc8293085a568a96c8e548fd2adfdf97e38d97bee5abb106c045e3eef76b6ddb224c775805f4860293165f6292ef816215e1ce4f450da24d6e670062959a134d6abe61b80d6f909f0bb54c2cdef5085e3069cd81769bc2de143ca6c0b4f89f514565e2b235fec0db2ecd19e960fa10322545c08f150cd823894b4b255bd0a3226e98b654b3cc1f0e8b480e8e83568474edae0885fb9e1dc4e0ae00caff2256196d2a270211b3d5d98a75d6bc0b057705f56087148fbd2eebae8bd246d5de795db6499bbe86a52cc30a09d2d038e44ae29173ad1dc84146dd40e572c603d67a9da937b538ef20c8d05b18439c8cbdd98040c9a5fde76229c44892e1e6759414c610139418b9eb2e0e08487d3d5dd9363abc9546a00cb42b1155d103027895fce8b1cce6b7d9369f81af0c3299fec8aa782eb1b7e4c0c446d491c26f6c8b882f22057bef09f295fc516cbad8b86ce38e54714609818552e3910837d9ce3b3e0c04178d07170724f8d024eac36d7d490a40fd58b01916e05d7457aaaa41346d79e67d3658e4f81f8682bf2e0f90b58182d9ec6b747dc9b698a2065919732f874660378a53cc06b8cd928244fcbc27695434bce805d22c22869b875f733818463551ea752eee479bf81c56c661c528e2ba6db6a6951c3440ac5f7f7582b89521da0633b00217089edd33443219e8fe432a5116507910b7db919cb5b50a92a5b34d2c67d860fe4b377825d05359b80a1f4a456c71a125a6b099a58ef2ce46ab033e1671e7a3d7fac9bf771f25156608274bf9b7b2b0918f7a880dd4fb050c1b0f63d78dd1a3ab6b452ff01339c2d3b801ceccdd462a5be6897b237e01fe732d58fe6f6acc445da3414c2c373caee3b67c98ef40ee4a26388c8ba40cf741100ba0db510d0dc5e5f94a2c5906f8b98004150ceeb54237ffa06861585347f625bcaa54f2757bb53a69156073f6dd6df06b2f92006194187d0c2e6cb619d042cdbc5631e75ddb3f9d42a6932f25b421bd93a82457c7e3fd56d54d372b7d39c892e2a9a7b085e174a0e8f3c830d641f8ec1bdeab33cacde8b2cd39fc9a466fad09db509640e0cb90302c4c10df3b01de755f1eef5dccb7f59f617e86a77b77993e340af0f1235f3d17b67f40254d794f340b4acfb7a6fde2d19a85de7206274c83cf8469760814fb32184da9e3a187bc5a811e17463a7445217ec931ab416a567748e3cb912ae749ef19bae390f7ee3d41c5a30b3abb82beedc67fbf61a6d730de1c0e29aefc927ed11703fc987548d9cdc186ff54c636e862e8b535e2944e5005b1744394092118b1ddd94286f01d29c559335806059f1fa676a08d7eddc04092287068f06e725b6540c87141d2cf8992aec0f2b2ac593c9adbd35cb30795d8a76b1b94bee7205488423179f91629bf9f7e93c7de21fe1502db7769c73bf893c3fe8a6ad6895cd1fc8f115bb332c0b8e3cc4e99041154fbead3b540837341b034992c61a05699732ad7edd8ae23f34c0d4219d4780e42b1c5dfaf6fa7d74635837ff6b4d6bca9158385080522bbab8083ef1c73ae5caf263c2ddfcd115af4647e0a510c84d4237874b6e3f9367354500e0fba8e728ba81a4c826450be874e0dddb50406779750dee05908191e9a1fea8e162c0f622a31b7907ea3a9937290f61a2adabc44deae8d19c45da70872e11a440aaaeea844abfac86d6a0f23a021cac48cb541438169b46b25ff9671768960fcd19fdbcca62182b0628fef5a9ea44d47323880d76d98f1cd08088ec93926786fe531ab98653bc0bac189aaf4b15105540b3444acd1e1b6d764964f386bcb394d5ff9e87969213f7f2cb17bb9d779626015777c6f7c58cbd6fb9f7c5638c4b878a9d01db903dc58d8cea97a4ca0bf94c51db6f352bb3ea34a607abc293380a11397e8b2b06011d5a20a6d970161c5a8b2cdc2f8e011184e39441ab7c90b904f13895b0dfb674d3edd1f320277a2875cb89b6f3b13f2b1b9e70e32d22fef970e3045b1553192db76db2016b4e45ae49a92505acfddb9c31a8759c616c5a5a6ecf6e2e48fc47f143de77ccc72ccd885377899acea9cd4348bdeb231c699e9a03e97b8aa001360bff0a8760fa6e1f1a6b0c96e623eca652b8e164fa62ff188375682750492a89ff332ba483f2f4cb1a48df2b2da140414bb9b078ac33d4231d9385ea2697bc258d0da70561353df285e376b15d09a400fb93393ee207da7fdc7493be7e82e85ab7c8cf5bbe1570f7fb2273f4a79b1ada10c3ea29b3ab04e05d4351ec8e3b2cf9c0a313dda1cdc456c8673fa8f0d50d0e5c789cc143afd327a1c4fbbeefd533ea1cea5ab5f3c0bcda8343a8ffba01b95ffda22c7bbf8551c325eee72ee52b670b2ae6a4e7e859dc957dc823791101d5bc1dc53ebe53161047b79101643c8517a04576585d6020ed98036b0172d197e9ef7ff06c36a859b9f42e6c55f100551adc7c51183b8e57fc2402ba2246aa3f5ce26d19bc63573e356ffec87b930ae2db838f80f924546b9bfce8f26eab5679b990a0bc4a24849aa2157d775ea85115e9f7e30332042c8960bb01561dbc70bb1148e48dfc7a7fe62996ab1e04fb52208e5a68df246b8b4ec46ef4fad44147985748c2af173350dafaafda53be692d3200c24cee724c0f8a8528381ba875a9c465fe8698f279c43d4939b1922bbe9aa6d33d7ca32e66befb4804d5a218e2d8efd7df0890b29103350f5efa5d64545a88fe6d0de7d9cd5faef97dac4b44dd32ea86df17270eca9df2c96a9c5a96c123c1538656ac9ea9ad4be3801aaa7bb09bc56979c11745f23181cef969a9a6fbc0c5017d40b2629e7b61bb104d4c330d501f764892b3a632f27ca97e979754d5415dc63adbc9a548ddb387dc27698c2005e9e1c0652d5cf897d9e8733459b1f10e3f39adda8a8c037c84bc25157cedb51d6fd13df18fbb0692c3f840ce705178f0717de81f020f36f2673230b8888e901afb884179dc21b57f0caa787378265d21358c6be7ac59917e4f1a9bc3b4be9962df93da2c1222471a5e40598a52d2975d79084469a8719498ba46bb267098b14a450c549b2888048cd57910e5ed99e8e31b56c49b1a4af2cdb47d775ce46814002610e4bee9d123885d8229e7be05db4eed468d9037bf6b0dcaf563a120cbefc8d4f5721949637d3019d81e16aeb3fcff10fbf10d6987629c6da88e830fcb7277f6c584a2fd95da0c51e345004ddd11f9c8ded8601ff493f5d128062507af620831b594feb4c470f845214fde086527adcd7580b1e88be64edf8febc05f4beb5092649646bc2c91bf5e1d952a5821d7839247e59719088c5a74cf05d57f5273a095a7ceccd7886e198eecd22f6eb9570d204d5a35492ceaa872704f5b05625ea64546730dc2d6da8850072d45bcea24cc3cfab7b11028abc4b1b0e42f912578b02c174401fe46d1537af883456f6d4faee3d14660dadc20223f0a642344946727fc3adac8cd9209577d1fb2555cce10adc6d9ebd0fab2c7cf9e4138da1029a9c1b621ccb6c9aa6550b3d4ec757f892abe1bd6c501d4022bbd12e02a586c08ceb3047639c21184b4de572e357bfa7f06af1d8aaf28c744a4cc1a13cc510190480f4a67d1a4a86682435ac727e2fd811dc23cbefcc2795efdbad39bdc364d1b46bd3fd39d4707fa03e369741b89d20ba02074bb7c0c6b814b3481eb092c41e017ed6dffe84ef992b8021303e488580269456b43b2ba8d1d89c62746a023ac6505f9978b261c59ea9fcb04e02f4015ba5c5b857161a8df12fe60504a4df013571224c167243859dfd2fe3f584f4fdc3b5f0a1f206e3241e55bca1099024e017cb9510b3aab82c63b8c3fd68d1094e6b0be70c3dd2e24c9683a320d83d3cbd2898166d86937cbf2f69e1523243546040fb5ca7387304067eb06e2145d4de0d617364f075fa1891ddb9d007315a9ccad130bf69ecdbdb90e1e6494fbe57e0e3c206f0c099f7d0fbbcb1d6c6b93ee83d1fe90df14891b741f7de86bb0f364f02e7b1d9544c27d6dd6d023bbe6fcad6287b220027f430736fef04e21a84f39d26874fb52ce8b22cac9a0c3abddba97d061df631a859e752d637729faa1c89dcd0e43bbc0893bdc3895039e988c33f83a291d478d3ec0e1f3e354177e50d03ffc81d2d51a1d0142a9352e9e486c27d1abb7f555458c5d51fef0a5d3ebd4569091207b1d6818b192c6a608d6583ae8295c2f8000531e67a25916bd6faf76d1f209d79692b39d5c201a695588e0535db7b9a810ece1d255797a500abce5c8287548dfc65c736e7d8ce20c492a9deb16b2731eca97f0188d18447c062b33a7f8537fab28226499d7f46b4540ac227ae99da88db9289cba1e5b684cfb2e2e2ed7e4308b5171e11039f647ecf275574a214c1a64cd8bb28d59c760a439ce56e91a740c04771ca60918f267114c6570bb18bd8ac580e5ed793e9caee14994912dd2e5d63e19190ae32e606854a651dadfb10bc50d4d31c0b3b0f6ce1b96151ec28226e973278bf964a076eb08ef1ff740aa5a168a47538a3d01078e2d681e3e5830496f57ed1b9dceede5c4479add33d8b2840eeb25883ad2bbb2867eec7b0c87ff8095b9b63bfddc08e32c38c2b67702c9f850a0e4121c40b03ba32513e9309fa0f98051cdc11bfc15454a1cc9c0b8fb1d2c9d4cd603fac00f0f0a40becab74c1032b4204e003a32623136278cf4dca60f17fd4fcaf41c9d4e67f3eb495d7a67421c76c08e55cb1901c285f36b9c0750b32eb5a818102066b8d307e72944c2c550a3cd65f8c7ceb5f173e3ae4d62cc27386cfc55da93832402b880c248c19efa30050cfe496e9018afbc96f4c2036ca1e7e1482b9b84e03c2d70511582424251d6a27ce5724b86a231a27dedf3246e0e48808819d3ad18e51e1d33435a98b0a1adaab2f25ca1bc2cc770aee7e2fa03e65ee62e3086d1d0aea39dfff2dd54d17a6039b74d2519877abd59a66f4d9f6698fa2c8adece5e94984ab346f9adf6646f319493f6ddd2eeaf4658f23d1161844d9bc372928dcb22cc804e58ed6d6113dcdc25677859b711b3ee808cb1f7514aec3dcaa6a12014e0f9f558cbf00f316cdd6d4fa3683794e2e86ee30e071876775b72bc36916e3a1face6a5d474f77db9f2b2ce1f800d2198a4cf69120ce2470a6144566b773274db49b97dcb01a97849a6c37638eb5013d5cf488a0cdf02cc80199ff98261b00cbd4fab6d203241b9afbd55f75b5a5d22a946bd84411c7ac604aea039bb0dacdde39a5015c27d82c6af3f2331d3efd9c75af95e17c5351631851e4823c88685ec13e17cf1bb8ebd68588c3b52b615ab617cf77586d4b833f1af915062bb783802e17e4d47ea391518e07a8931e39d7c48261a3648aecf3e40f6b75522848c8e343371d602a3e6368f8471ff490e00aacd31476102ff4eee093b2048dc9c198fde673fe1f75d559a887435ee643609e2737c23aa3848733cb3463596a3aad037864685281751b1b8ded8fdd5ed04684c42211981d8844b2ed2364c24cb0a64e3d8e1bc4624fbc8ef85a43ac8edec9ef126cc79b8b699aceea036e1cc0a58037aa9c1fef4b13a4b0614f633996a5a911182cb65f85699937dbb8080729f97d72c0fccf595a83e64ed5225af763d1a48b202952de239f0a30cb374e0c9a7411248552a26814c3b575efa17740584551d909d1ef37016cb2eeef606ecf17b6f0d68f9e6dbd55e16c3ce448cefd813a5519ee42717af5549a8a8358599d6d4f745a3377e587bf3049f431641eaa4820ddfaec34e2a2da641fd7f62403dffdcb085089eae40e3b96ee5e891b57841b0dd095c4d93deb2b1b07690c301889dc0c9f50752155d6218dafc3e1085907fda5587258ece263db6c62956808b3eaf806294a7eb8d276ae30e2f0b564815eb9176dfff0b9542c790579588d7cb5cc565e36437eb15758559a5823eff07130164fce1f5a150e18339ec98da2afff37232c86074b16b81f5425c02930c48b6ad9cab70b195466021d8bb00425620b0213c9e15c9db156de42f07b54b13247b964bc08ea18f8ae2ca79847ac7ffe44ad23d6ceee09e3259fb54fbe3f9318a773f15bc119da5648bec6bb0cd0c63cf1035402f641e11adafb24a1ead184f56005a5e13fab468a474e8e1b85a9a4130c865cbe57cadd4ab86985ee5a79017bb86985ee5ac91d2be4c6f95eee56c24d2b74d74ae2a60e7a4cf8a5338193ead63f700e804763749648292a3008c4309905770147030e6a6302e6c3161eaf00506beb1fafa48ace74d31d6ae9ae96417fdef0e097cad048bdc5c00d019d4a860ccb76be8a4e9e31415bce13773ee5c3dc17308b21ad441958ddf2e244e2e82444d987a6fdaa911f84f376c9a29c3d07bbb21bb8978d9c163635db533db162b39822868af928868304303cff0d386c5bb277b806702b6d626a3138c800b9cce46c19dd2358c02908c870720e2dc1344593ed70a1a9375c74e5c4b0a5d6c694694f7142b30aa8be25335c88099b33adda090443c4b45a54a931f032c67098500db89281841b0be2dac74e10e7f211d4a982805a5dcaac8c1f7c00d2380c5681f248f260e5ba82b36a3a161ec481a1c8f71725c0fe11b2c1b774026fa7f0384361bf22c031e55b16d28de490c3383332ffc2335cef86818c8ae4a041b4f4fd933d83be616b3db949e6391cd3c4bbd0332f8e7cea848d938ea9f78d062335008cb11b954d1410576a6c3068864e0c9edc488a186412bae10e739772f73e56367af12f42af77848270746a732068b0ca6f4cc665931d37b02411c8dc75f69115c19267eee7cf1a167b8c89577bcd79a0d8bfa1022c5d9aff1c8d2d3b7cd01a752281a0afaa0492541d521642714019093ea0a3cc16ea3a36b090f768c362ac23ce7131169caf1f5494ce8ce1fb3eebc34674c814b3daf16f200b8b7e1c3e8118c6ba7c2e79147ee7bd301b309d0d45a75f30026a286571241e8170820c1afdcd925c6c71a74994e49e1f26c5734887e17b51675c375266c5c9f9d590619b1e3e4d805100666bad654d8a1e0c8ac5b574750a6f35d017d854a3b1b652ff38bb9cabc2a4ebedf542ec32741e3c8af52c2a8b51d24636d4cca1d20d26648d775ae5d2c80122e80a6614ebb12c4d496001f49d187c282fbf2aa2bb9cb1b99751f674b4003388c7b069e7b7d4c6c1484b2582504abd857c14c296da3848ac147db22e8a479fee75f5df9526b7451021c0f56d8329c69ef7c20291f94325d08f0c738a2be4bf806703a75e0e3c655e81e2fe7f1c4df3e73e85b0abab1deb740105716ee106b0bc76dd3cabb51c998372a39b497474bec19bbac4c8dbe2241d7ef6e7644439901e6bb58186323332f691bc2e5f210e7e1dd18cb20d8fe6993f89f8ca575e4f7fdabef6dadfb818fc8fef82408a175a84d06f3e84a0931e1e8970a117da35318bb92238948cafef68c24d8931509144f7832543e4b52e29a13822128a33b1129471d29009d1bc947090bc1b1107addebc2a987d092c5d7e54caf821c083a1cf0e30e1935bff302882c8560c511476eafb143218d155fee461780396c64cc1420d1cb273da17887feb9be9ccbbe10105ed8792bebdbc71661bf123d4de9148039224d8fad87a6ef3126ab64b367ccd59e1da1074a99e70534ab75097a5c8097d4c4e249fc500980fc2190c363c1c6a8baf99d21356f4eb5851ea5b2d70bd0f14ed50eb6c8a07b8aa65b4ab2666f0c40f8749a061f1707ca15365ecc097d30191a2bf2bde3b1fe5b42187c36efc98f4c63d42531d63ba084e32b420b9975eb0de51e3044c7c9cbaf5ab86edae5085015f23fcdf905c3554440f5fc7951943969402e6bfc9016a646e81f8a77db5c993c703979cb1b6fe0d8f7a9a87ef9e242dcb6bcdb60f7a30760b60100c176482f0356de2937c6a238e23b4b51f02a514c8f4e18f5d3b7621b796e1c729a00b7cf9d64e10590d370aa9939f26fbe6217c15482d84581de6314100fd269534cb01bc54269ebda266fbdbd1383987a64d2f645b13b65277f47811db094bf805eff803da9461f2647e9de12467493d203909bd6ce2104af3427989fb1543bd4e62d232debe39c67b7d942b03ae0c59d0e897cd08e7345085317d8acaa80a5ba4485022f26201574abcc0ad08b0e1026a583cdff10437ba012293db9073c4b795525db2976f44e7c436450569d11f96d33f1df0085873319e4323f6d1f8befc1d8308ba2cbd08d0171fc070c417f2bdb727a02aab1e9a166d267e459d0195ef674f945e8d5416919fabadf2ac9c615819122e4e715171c1245945b7366ee41ab4f7534fa6dd86783e846e16de11261681f68dd89562abc69263c2dd60a558a38e2efea6c89de9c92100436fd6284d9a2998a453e1a4edf6f49b1a4a5eacd29b107d86913c106db2c349e846d67734cb2204e9c5c63634db44494a16e5dfbf35cfbfc618a8b4c5397f17c5aabe044df39961eeb65a5aa8e02c0331bad996e669e355dd02650d62c043fdf45572ea5fc37324bf218f7d129050b2fb2568598dee86946abfea65e7c2756e11e4ca5aee69e21805828209840c3f852accd5e810c1fed6b60ff02ea40efd964b95c570950c67c38eeac80d1b21fed5f1ba17e55922885088caab14e2373f55ce040a02718f78488a1a35e38351171ea8121c6abba85527659d651f089dc5fba15866e36ac60bf22c5e07f6a2598ee19bdc442da211bea341698df30fc006c9e95a0367e31bdeace8824fdb1a4f4ef9b6a690c85edada67370a0886dee580cefd6024b222144b97284a3557d2c08c391363df27eceb072ace176bbc961dc47ad65bdf798de72673a82d66f6a169cf3b2de60f006eeaea8b08997231c51721e7a1c6a724eed2bcc84bfb36f3d6e70a30f08e5730a45d466379a3cdbdda699f4056543c0d254471464f47e54537a460b9080015a4148041c3acce2b1d4eec812da5f93b3d6501071fd5b11a431682f468bacbbcc82b3c068a8a349afdf156018b9a5d74cca54aa82c6828b1a3b31349f481c46610bd9f0bda1492ac5761edc2b2fda13f081b2a304283b757456e045d9f2174f799aef7b5f9492ad0ba688f8c0ed0898d506b16515f88e4ce75951e6140a6209240d86390bbea6291ec5b925291e0a3988e65dbc183ef4b0adb9dd5f731f70eaeb248b1a4c51374361b064000d8036b850691cb75b5711bf42cc648b8ada427d30d33880f4a4e86f8e77053296bec3591080b2c6a0351ec0658ef01004d740b1e055dee7f9ac7bcca3b0e46c6171323d6e8f0c4d13c25cd30a7260c973006ddeb58012ad61c395deebb405d3ae760fcca7ff0d9ed1bd36ccf88bc8bfcb5fee79fdc2556d1e5cfc7308f9808c5cc9289ff5f808cddbe769ce099ea696f471b776005bf3bc40d6608513fa2fca7be8f9ad1fa2ca57d23f8d75f3924af24b1e810b09748b05b90adedd91b156247610ac0791c697877fe1413aeea201a85f88df054aa098548851d96dd0be752224852407e88a63080ca8585c38bfda80ecef1068fdd0c85f05a4ee9b74bc098a2af580c73d34fa65dce720b7b7eb5408c9eb9afb04e61ce32625bfebfb65e70ed279d05f5c1385733425502925cc6b5353792c8556438f1ca60aae655c5857466d9a9823759bd664f8fef02b7d2b27e513262d2c62e4fae0abf27bbe46f10011e9bca1aaaf7265ec8780a83f040993d832f015136d4d52b1a4609b6f44e126fff22f66dc177981ae019829f18b1476463606f60e48d8f6e15d45566f8cae420fad98ab2c01229b5d51f832e6fee8c34464987576b465b41b37416d7a8b5b3e207c6d911fba94518e8109d32d95d281f929f191dc4a9c6560571c89d3c853f0306f6dbd61f0c649b821e9d30b85a1d54e8208ca848d0be42ba55a2293e56b7487f0f40e4c6f3a2cb128d076c3a20c9e04006902e16df621c18187da0d9b312641af9e72ee2e3e880924dbfadd368a4c1ab6a4f01b03efbc5c2059ca4478bd845bd61e035699c00e7462e1ce5a7ed50f9121dbe9501025f96ded0c3fc6bfd90a1e26f8893b724f3d1dc94f261f52ece24aabfd702c738b4ade51b0488478eeead00ee442127f62240de269f6b976b831e71bde0746a8adeda9bb5f7b0e963ab072e4ee9097cc4c49fa10eef85f872da7a1487cb32f8d0b2af9c2ba46acd99be9f03f4df3aa9d2d597cb9d559e726b665c9576ccbdf12cb788cb806694ecec8a26b8556a8cac06467d00d5c432d51bde2c6d78bd84c23e91bf9cdd03aa91c3ee1fc58a4a3cbc56054e2c449d0c55305c15a5399981a229dd53793ab8721757c7eeb17b2a9bee5d6de52a6e8edd6397591e889ec1a5bbae822dd819c038e57c1958827689b5b710ed22054226dc80d1c906c0601ff60899454b9a6dad7513111112b203a50c9b0c4c0c138b2862f434ced80ad1a9c9e1a045b0d6a47704f35c5d85122a749478ae584488ec6e6557db40936b222c7ff1b1c8bbdbc9ef222c0447a4f42b7774d16569acc7ca47788a1ea2b3e029446f1c8743741d0e4aafc331ba0e47d774e9077da4f6ea2f6cbc15cf62e5182cbda3b642938328d9d1a18aecb430079b4c80ecb4b0b324832e8a63edeb7165f8782d10a853f22b62df7b57cb52898db8865264156cffc4888d6f1ad7da2624479792d2cf39088844ae0d19d295bb954b93409268743d0b878cb019f8e0a327432a628d48834de01c1c099ee52b17358e33b0353b251986e14ca3c311a65a4031a8186c3c8c1617c59161a0ee6aec2a2c154288d48aa374e53e2cda08f8b2b80f87d63cdb35ad556a13638ca76e51975601064bbfd19aaec9f56a395317de6a9d1853157e46328fd6627614870bbbb9244d7429de071051ae8f5a86af5ad04b71b48629292a1736535d0b80e28d913760b3bde72b50c031c6c8f6d9ad5f8f7f176567c4629fc7deef18b2353b21cab6fe5d20228a43b5f882bd22ecd6cb73df5aafb05c340019de723a9bf3c28a31c6537fb9468a6297a2225a6119b5c870f65d9326c99417ecfb176cf3e811638c9f80c3e28d7e7db016cb65f5fd215befea7b65ab2f7875add646095b3e493d5d45961f1b6c3f073878746b994717ecd6356da44bd0a64945baf4baa66bba8a26c1c7ae4962fb220a87d8482ed8510e6aa335f8adc1972dd82d6ac1c62adaa4c521ae06a55186e2e0582d57a48eb40c915f1138e9119905bb511c8a3645222bebc23c19c514a0c5164d8f5013d5a288ab297d79c91bad89d516f6bd8fcc215d7ab8886913f3b070259a02a715c195684d6b5042d34a4e129d8793db82a7298040bc0421c4101a0923c9f00f0bda9852bdb516385a746f515353535ffd46b7a8544551af6a156d72cd83e24d664886a2c75414a5349e7e2042a4881118e5a495aa7e70b9220f327ce441bc8957443969a52acb72b95e116b41486ba089058b7268b9be48164c323932ae0635afb871f38883e1d15a1c4074ec9b157191e12b51d7502d2e9c87e5d070b050d3261d90041f7dc4115d5844bf68520dcec497b8a2284ed71431c20e9258aca53594c915ec366f722cb48143201157e351b556b55ab55eb562b57a6acd6ab5b5be1b23f71281410b54c8cf00f9e1eb88fc80b85e9197ebd9bd04110fc51b233f77b582b844ad491cd82134120b00dcad1934aefec2b2bc1bd88e017836a7d3a90438dcbca922432b3c242c2c773844f8219171d145788ad103445ff90afec048f41519d835de1e4e7eafe53e246d04fce83a485ac9c9f05dd346c0af5c774749bc538e6d5af968c72957c1bef785445a835fb95052c1f6bb8676cdaf75cd13ec466b5c924f76b78cb85e115bba9c33b1dc3d2538130b5eb150c822fcac90e12dd3f5bab0a0ec4e716ab55624fa29ceebc2da9a23af0b2bb58826d12dd511dd3ed2da14accb1bbd14ec26ca4d7120091e5e46c16eaf0a89632386266bc9746b3205b82414585e235a48ea5880cb41d3c4e16ed5b8e413ecccceb518f9fd3569edd4568dcb86661d71bd22560ba7b10cf01bed6c5cae4b29d9dd3ae2924e88cfd1c49102740d34cd1b68bda0f761711f126ea0e925e191e061159064f89e90e1b779843a68b766d2471a8b36b11cbe6bda069a56a4ce14a48e732588738be5c6b67dd114a40e4e1fd6a821b3b0540d2f20cb45f196eb073b4219a7a4f3521c1c1bf1ab545b1e6ca66f59ef3d4ab5e812565ffd7defb11fc5c9a15b6033afe17bdf77df91ee1729c5740b9ad31a9cc154a7358829ce7b1137c581d8dd60be70148f7ab1b5914dcb5abc2d1ad3b8cc8512baa44b11d32a74ba347a98d6509bd6b42f280e8d0b23cb503132b4a70c1bd321998a0c2f9b6037fa8d3e99778af3228df401e9525bb0f176156913c56912a498071936136ce8ab02a74b6e0936d674c9a699349303500cc9a39a80104211ada15574c9c6000740c08d78651c09a01712981291d67489b21070398aa3655984ead28db6ac8c233e1bd4bb43f48baf4f2ea9049bddad0fe663e522dc0f109d05770dbc418dabd1376dea9b216dea217d829650deba86d6c4f8c172c0dc49b0f4db3b824422c1461f3c5c0dea69d124687a479a042fc64d1846be20e285cd920c214e4b9ba04bc245ae9870a53e81d35ca9ab70193643e19658d88de2f4cdbc621e7135eae12d1c09406226078d0e4c891d17c5141043580e2a3f8bf34e0ea6ce5cae4b43e4ea56f0b469127cc32619be5e2a534abd2356acc418ad10ddca71c57923c2b466dec0cf6039b41c9d680dda6002adc16e1aaf15a2575a13338c3d24c3d31a4b014c6a1ccc11baa64b558692499736fa6622bd680dfe6dc1c677dea44e86d4174e49d6f1825ac14471e8149c0f57a3ca9d240b19de83cdb43482ed77de1cbe5a63d2a522d88de2501c570325439b3ed8c631c6dede913ebdcb1188cc280795e35d7677665ae120adc1770492e045a223f5baee575bb8c7d2fdb6b82b2611bedee9ad1c39b09919a3744364eb5d3619d21a52a687828d96690d34890e4598077c4d4e72246b99622b44a76ef34892e1a108d31c4882a74a32bc8e5cffbab05bd7740da53514e7886d4aab3baa2e473c33c5e912a4a72e4571ea374a45548bd660d734091e8a6aba86067b2782158d1ece41658ad35a66f9ca2693fb14079a641c4e4e866759b99aa56308a3961d3fb05208b67fc0e25de22d055cf20abb754d697ba7fcba26c3cb20d8172c89cd476b103b2186d04800984103260031021800018a30a2945993cce7a400356c1c618017d001107023e3a828dd396082e55eb6d8857db7ae36b998ae2b14dac946b0bb75cd93199a7182263573e614799e5e7c030a99f08b6763877e2b22673472f6926557ce1edbf46cf00644268221b125e67cf6f8c59cd8cc9c339efcba2cb75d3a397cfce26212c1882787d208c466288e4c5aa357ce0f78ab55c66bc19be821bc5d43c1541daa0846dc79603c22e8afd626b6ad39275ac1708ca6fdd6b381a6d61ac9b311897e4d8b89b9b2ec9b966bcdb22c9bd7675eec6a24319af68b44d6b64552abb522d1af693131d9ad8c04bfcb49807fdfd88889dfe53d8e1d24f5c018e260580349efc13746233b5a88d144286bc4686076695cce3e311d2e6738e52ad9ad09fbb998d15c3b2e0c330c4f0e26c339a88cf58eb5d65a4cd43bbd83c568da6fe79c380e8137b07af5edd537adfa1683596badb5f6169665d9a9eca0ec317b28fb4976c92cace85b1c82b798184dfb43781389a0ce077f74e6bb88181adacbe7e7373bf1064426c7225bcd710895b14b6599855df9168740cfcadd5c8efbe213453c7d7428c1a3878f0934610127803c11448a29de89fe824cc078365c867127df687b5deea1d0c7e4f277e7725d93b2bbef6497788b529a65f44d816fc0795d7758769ddccf3c85a733cbf0c95d9bee096e26bc892d61e50c53fa2c3b0e85cdd0ecd26f4228a538577c19d4a5cf4fde489e0df7f9e7f3c6e9d2c93fbd332fe3607f6497d8ec509768865345bfb0990c9b992e04e7c4f006fcdc93ebf6b5c3ca145bf736156df34e7d6a2d7b46f149646dadaf1813d9b52ed7e1e89d2e510f6b40e3a890d48717e5f4dabbdc8723bb17fe4224b76b7583dde08da5b7bd53c19bd6688621132a3b8c62a853abb53756b8c02b906c229b65f0ba60e81bd4d9e952186d72f9df77b9f7780b1233c59009d469ada96c2f95fb04d6f30deab89c1ce4721b477577621c1757a67a278ad838fa3145c81c7183c621d12537aff77974c966effb98401398c665fa205d1ad1bb9cbddde3c9c99d6d9c7ca3af5a33ea5dd9057dde8f925afff9bc667babde1ff5f69e7f6ee35428680e4141b33d3663fff96c9d3d9f3b3f9ed7d02136333f0fdd7a4f15921fb603f46ac197af93ead7754aade35cd645274629bc317b765da634a334cb32680631f2c3ac89cd549fc76e46e960ad83268ce211132eff7bb8fc780a21d04f81618a7bb8fc3f9ec2050bc17172066bef5ac368aa83b1af0e06d21bd0669fafd9af5b9febf17842f7787e03ce19aad565cfe703ca3ecf3eefe7d9bcfd3bf2b9f3d9751974df9cd5cedb389b909cd5db8acd647834efae7b72b7ecd5b5e8adb2ec5595ddcd5e034272969dbc72f973879d602868fed8bf25ae678de41191ddbe23ad65ffd8eb72f6eb36131e119914996658761ba7b5ecf46e422ef6ec422868c6e01d865df14ecbe5aa566c664623325a4717aefea0c16e356b90044db0b7182b543f3a3692609e3a09e8eb3f2004f90fcc53ff007d7dcf53ef314fe11ef4153b486a1c924b6cfce99dd7c5861d61606d419c9555388855eba3b4e78452c6182584b33b5757c2c1be6cf58e5a2b76b3fe066c263c22dcfb6e2f0b19636b0d7760c1e8221e31d17f7fc0c4ce3eec20e92736b6bfbd9a131bfb5ee4d96032440d0ef867638310a821c317f16cbce3d8e97e5758c24e15e5d4c23ed9c347b45db21e1f125d8fd8a3e771f2f0f00828166fefe40ecb31439846935ae56a27d7b921c7a3382d43d0ddaefc36203259d61c2b95944eb9ca5f91216d123d3eaebc21364a6cde13ddb7a449f12af73181a4f894f8d1b1551434ae55ee54ee95725d935e489bf5049a521e7fe9e227643394d0536e6c12fcd80ce56e0fc9f670367b855e4a11c9456b31254a93ca33ad7c8bd6e257ee4bd2a498b3c46ecf467b3943daa46293f2887295d0534244e2b3b9c99149132b74fb2dd1f1e27d114697240e5df864140ba0df1a43cb59ae5a65b0a14794c73f1d57033efeef75f6b25b0f8b6207fd66af634779e890ca7d35a94f9df43ff610e5c6d06b255958ea6ef5e4da1c498ed9dda8bc3d259ebb5d8f9f401316e8923cc4340ec8135d92a24bf0bab1b5ead6ad0ea672d7b3b6ab94d4b5226ab556a484469bda29a17d5262e5f694c0d71b5fec183dd29af06af929b1b3b1a56d64f421bb981d8410426865fa418109995c828dad550458cecae47fc857c6ae27d36cfddd2c5bf5a2920ff62df226c3cb5fc62cfbf2cf04362671350890e5452d7fe9d275f998cd6d91c99c232ea955597b440e34bdc867789349b23c96932f2cd39a14b5b426af2c2f2f2b29bd85a63ebd089ae0e90f4df1f45a9762a68fe9120d2a1f2d0a4ff9f9e90185136e1143ea542c58520cfb1460f5adbe4c600d60a2e1636b180d8c30be1863742d9e8d03199ec67111c1885f442632323231da7b0f8c9d182f4a0f92952733c208238c30e2247e2a2c2346631dbb6559007481dcb546d55aebc927da67f7880cc3883b599e56265fec60b74626d82beec4989344d7ae64eb93ad53ceb22c0b6f40642218118c4784bc73a2fa3ceb3bbddb8c0c2d301e112f82f17997142c9745b6de05afe305dde643f4b552ef4c4abc595575f24ade52d6a5b4eeb9d52d6aade52e84625df0cae265e5f599d709c85df417fd0df8b92ed7fb3eafb54ee3c3c9d62936d34c386ccbea19cb83cd34ae4bb01896d1666a59d6e575bdba1ad42feb533e05624b58d992b73c28957b8f0f8b2c253623fbf0a42b1c2a6374aec6bbeeaf23d8f923a143892ebd78449e169aa0897a3422c628e34cb5e2902bf59c96e22d096482ebd7816aa5a4b44bb3479378b4c6e35276f77b33ef0e068aa08906bc6ad5e09d9607db976dd34b8cf1d13f4cfe61f1925af07aa9262a9a6813d04461ad7a36227e2b7844c06b3a3a2fc61763b4175e0ad30a9e0d9223021e3a2bc0b736c6cb11c3d8d812cb59d9ebc3f63617218eab71a37879b333103fd1c46eb5b60e183859cedcae6429a59476962d8d5b28b4b5c88e30f68b2e52cfd59a7c96e35629ea53b3a41c495607d305bb392d5c956cb0f1cf069a8a40424797e0e595e8d2bc3c8f2e6197efd1257af96c4ca07b9e9daaacbb3ec0c9af6397073481b08f26c94b89df49fb58782b42cbd2ed644a7ab06b52c55b8ba418091dadc9cfab446bf2468c4088a4357927df11235a1c81cf083492e5dd8bc43f1e0ed5b1e6ddcbf62c6fcd33587a778bde79b2d4ab59c4be316cb57048f6c1de801a109917f9b4b08ce4d733260beb80a4f80a53b80beb5ab6abdd2d34557ddf8c83894a28a2841b7af516998b248491671c4cb4c91676bd1b917030f16dc14b5e29a6b8ef741f154d8a46bcb7086cbc14ef54458ef14ee888f1d5ba3c98129802e60dc8c476fcfdf6312cccafe2adb31b438ece0b0e0b39c61b0e2652f79aeee13e59884493e229de42f9ca8d1bbbd15afc3b438c5a6b4f87abf1a28d6c4b0a96b3b2d311b68a1bd8cf4d9effdc40d3e7f39f23d0643fff41024df1f3283aae861079fe93f349f2e9c2b70fce4592e72f928b73a310c48eaba14dfae0b49e911b58eb949f092c3d3de56a5ca7ee5bcbcd10b9e5069adea6422488ac4224450792a68e8e0d529028a9ee01f2f6137b40fc07cf500249f3f6ced0b9815d837d3a364e3336c7c1cc4b2fec6689e469ed8d25628db81ad8e76d0e5c8deb9608923c2d1152154d9a43304f663f27a0d0bb81850740f664f673020aa1c8d82f53bc552067479a74a907904d36a749f349c2a0becd10c3d613500825c552b1acbe2f67cb7e4e40219414954b001f8a3aa128104585280a85a252284a85a24414e56a749e9fb164860e34358e194a6634a171ca2bb0cf045608262757e325cf77130bbf0991034575ea188d1de26af4e72d0c9c0d9b53854d972a19e4796843adcdaa49bd6acbaa6b13d9a49262e4f99ea719823982ea26cff69cbaed82b391a26373ba447dde6e6193b81a594a1abef017cf33518b88ca6cd880144680e79b8d8cf264293a29697899f238ec0d79fe82439eaff0269be4f9cd3ad5d7d0b309c9d48dd8c9f346ecd4b8713580c8f3463431428cd2a982d082f0821083d0036106a185f003e1c78b2e550f808d5393e6679da1a44f5dba3396ccd09981dd6ca8c16e3394cc50f26c7408e0fb798b09518a42690aa52a948a285da19485d211a5d0c6096c9c6c5061a3c6d5a8365290e70d6c9eb766d8c1d9b039332616f09067ece4b9cd0823cf5b28559552552a55255a6119b598608612687a9f9ff14597a83c1f63e36a90f2fc0c9d195ee4b9cd6002a10b8487f04288210402c217088380b72eec69c1d257ea3145f4b718b993a746a3b53943c97ccc7c8c125783fa8c45c8a73cbfcd5092e73b6b1ddbdfacd37da2b5f96ee2f290e7e71eb090a7dbd1e1863cdd2b6c867a34b215f64e3898791dd064f1cc79afe6749f8e3a9c13009a3c93e7797ec8906ad9b2aceb59588e97e7dffb7b3526cab81a2fc7c428f1609bde80313158123147d4dfe18cc1d908a5a49794d2fa96a2dcf256d7797937a7458e9fae663902f70a4daf94be37deea67534ad56aad8edc9791690242082185b549efdf9db4c69d1ed8ccf84429554aec1e11321e9a2a96398cd88a7a7a23707d381878196892c0ab588a37647829e3d290e19f4c8652ba243cde1367b0fd28e28d12bb9d27ba140f372746fd8d9637486b3c2a4e02934c22967bb981f0c67a4f36a171d6879d4d5fadbb9b5e2a8a983797c425e911a353c2e4ae99d058e92dea7a5dc0537547fa1487c8f0044dd2083472ca9094e31111964920ace0957051ddeae15458be7ef050f4e6c81594482412a991488bd02115ad2a55cd8a7afaf166d95e276d53299b24491776dad43baf68ad3538b3c810463c6f4eb6ad0cff03dae44897b678e48a78135f92c785922b8e401c9c8638d3e6bdd82f763f3c87d84c22adc1d82ff62c02a74d55554d2613f88b89176065024d9db393eba96fd11a912e3d13e5c26971c4114da3bb6b1ea64968cebca93e7ab4c6830b1627becf254d829f4b54442b2ca316193f3c240ee6c98869ce1decf6dc8d6e127c3fa9237f805d6b508621bdd0690d4a2638120fb6c2dbcb62496bb0566b45a2c318690db4a13536b04816d0c6c6e68a184633914c62a494d6446a7324b68cac456efa6da35eff7af8b86f08b5a9a1554dcb29d82c6884367deb4ac9ce93dde733419c285d0dda6f029b68f909627463f7888038d024759e7d905e47b1f5e07b13982d9bcc37a9d83d22ea6d5a83c1a53178d40719be627a830c8fd1b9844986efc96432a9d38b2e6d947a9d5e4c1dfacedb5c926175fbe60aec967449274992d89f492016ad69fdfaf57b930734adfc48bfe31fd9e29ad2ba5e110b6d2692790476e8c6b25e17d6bab4daa679d045ec2336f3798c168b26c167a1856c3dd8cd25a153a6c16e2e098f7e2f1ec1723c4da955a55651ad2bb5b2d43aaab5a55619b5ca79723062b40691504be97d481e16408ef4b469126ce7a6cd11d964da64c74e3cbfb0990b05a2c1524fd22a9747e5ba3c297874612b58ee320ab66225846521ac0996612b44203cc24ef0c4355b853694524adf9a6c72411938130bf6017100ade837581eb55a5b0240865c402e8ad80c21e26a50abd6ab56ac564fad59adb6d64fadf43aa9b5699c365da2bab02ef74c5b9665b5d5dd4d598fe7e3f19c783c208f27e4f1a0783c291e8f8ac743bd6247313613773cd529bd55dcec5d11c52a5af495d8a3abb0c95de392f4e8526c8ad5eaa935abd5d6faa9f5a45650ada15a31ac8af42d1b4b2bac3c36e58d22e6cf55636a1e4d825c64ebf89d78f8a0ffcc54af2ff7a9166d06bb72d1378a6362b14697e63409e26c5d73961ada550645fd40512e14758aba1485290a088a7aa128fcb2c874e58a9aa21e23164f511815efb05ad146d231a62c717da336455a832e97c16675c542523ccb5db9587d750b93e20644a66f740541758755faea7604a36f3cbdf18b4744bfe2c82482f188e85329e24edc7958c466a445712014345e4adfb752f45241e372bdbca38aa358c0f58de268af794b0436079a463d333c6542bfd871352c3c6262e5a2f71861168c45d8fa21cb5cb2ec5976b30c67191059f6926541645975b5086aad225c9368984b42066bd39af64e514a8720c10b0033e445635e3001b89aa23c17b6ae85cd54585a612fbc691a8ed8dd348a034db4a64990da8e0d3b7afad9ddae1c67ae78b79d72474c04f197f708023b487ac13d62c472c8e922ec27b17530f00e0694e38d9125e830e4b2b2324f574e57565080c86c200c44267b3e51ee728982636b1ebc8d64b4b48ca688e5a2b3884422118be823d1487459226c7d645d5eeb638d46a3c35b3b87ec2f679fb8fff068c7f5f9ebf33996dd619f63d731ec1f1048468785d292527fb050e64538e507199607a5056f3f0ee14d83fd794f0eba1b78394be2da911dc3dbe7d9dd7afb406c06aa80de98dcb1f320bcc9544fb93f3ea79eddce0e933bb655541a884cfe60ec2ebf01edc9dd746497476c660444266fe0613b50f2e57287cdfc0fbbfedb44161425437dce6f9f2eb75ce460af4b82b175a664327585e4342976774edbe4b48d8bc8c5a6e04d2452c12e2e2e2e6f6cc6053bfb743a070cbb75ce4e18395b24e142c9121d2f987cd12578952c1722ae7d74c90929a005ba34278eadbd390fb1a98722a1fba8a096754707dd95fb682d4208218412db11732814bac3243613fa3b621d5af8405c823e55be3d24ced960f9f69cb0019b5924e8d39a204b5ef877a491e0cc9d77a499007142d7056f2fbb7cded1e896351a591fbdb1991176b647d70abd106461f9e896e7826ebd6be5ca7b2e089b81f27206b605baf4f826ba5402cdcfbb7c5eeb139b71b9f5ac14adc5d3e706209ee251d15a8c31d65ab3e7d63929a5d97329a574ce993d77d65aa594d97325455131c6ecb9b1aa2a0861f65c6859567767cfedeb7a3856e5d785cda87ce5beec5139044da03579d01f46d3848389a2b78f2ec90bf194bb36bd1ce5ae4d2917e2c6eb5c935e2ec43eba4451198a6e7430f2405c7c1d11f22c1688e6089dd2cb1c5506ddbacb75f9aa32cadd6a4e79cbdd44390547117395e3a598a24bf451d1a510de82c86859967c83a5fe4e5560d6e773908c4b9796ff70eb07e82a077dde02c12452aef2bb3591650a4882a41c85ae82b7118dca27de40a0bbdc4406894022900824028940229008240a7d45e5220be4297ae820516805247a48c482432b786b22abbc3172e81975297f35e1237e02d17991e3bc50f419fabcc396503988e68d91530e7aada310cef1c6c829188a3746ad8fd29e134a19639410ce6e19fa4aca574237b6167aca65282547e8290fe1ea60404fc1d5c1cc3746be0141575e74b7d069cb535a2e0f25c6809765ca1dc60095bb819e824f6a60413888c42ef18f96a7e01fd7651009c2416476f9757a8825415dc6d65cf03d9778fb2106810f5b00ca02503e42b91b08f2687e24ef0874a832ba9c071da28cee198154f0e679109847df229431c232ee91717965e043bc058159c23feb3994f71cfe444229e51b50fe32ca27fe017a78b4839aa0797f80fe5982fa07f40ff5cfe7201578b726f25c09b5c0d18a0f166badcfab8282f2872541fdf3c11be8281be828b750eed6d072180d7ccae75579cff91b10cedba7dc1fa0d7d0516e672bf410fe013af56de2203283f0e7d466795dfe6cdf9af06014756b567f18f5ea7692d662e7e8d4c75a6dec1cbcc1d3254c2cf59c36d8cd29c979ae5748f6014d6d53a585a2a9c54c236dc2070dd506db597469735cb44d97e41022458cf0a06fba74c591c6a24797e69c129e7ee80c84c1925fd85a1da56fce9612c6182194dd33935fd89773bdbb90f7fa043b96669e788fbc73f221c1dbf3380ca3719f72be712e6c46ce997877abfae89cdbc2794e29b5b516d78febb17aaec76f419ae2ac71e82ba62879790affb81cc5cd04bcbd7c5152cabb9f468a72905e188dbdb3b0259c8cd45a47ab88e92929dd69bdea754977379b614f21e31679830fd29483a7f27483b1e2ad3375d938191ec24b77eaf11b902bc6ccc9e8dccb943cc568dc2b0c43f7f3774fde1774cfb55c8c91fa45c58847f5f49002b9f275d089e7736393307cd1ffa0f075882d71658a370d58524ae91cb6820489b0e21ff2f1cad4293d05e49291de087fa3afed6591e51f162d5f5f96977f11839e0ee1e075d5b72c0cc51be3a26a7d94f69c50ca18a3847076e7685d09870829485914760e0656950272658ab38097f5c127c8cbab41271b919d16d470639fbd5ff2977cafaaaaaaf07b184683f2f8e8de83d481b8a05728c777fe3fcb7591315a518915a55486aa5a250de11ff5f4d6c178364297f51510afb6cef440e0adabad888d86ebaaaa70a632128f420fdf68239f3be811e276886319e9270ba83edf65a6f7a4061605c368e4df75397f7212de6234bc694360234204f1f24e9dc268e47f58787b95acaaea5975acbaadee0122f391ef93a2a4917a670e5d97eb7b7f36c39e424626324816236f557dd4466d2f57fce3456c098ab7ce28f85bdcc9d4231854e22dee64262897d589146143a3469e5a9a3801c4e50382121fb6847c2854f58ad1a0fc37201017747cf3c9dd62eeafc70a00bcca09688a81a4fec55b75c19bfd818988056fc7a28e89c9a0c77cbec55c643f3219c39f3b6c01d63d1f3cc21e7af51bfd8ca8e02d46d38e82b78b4eb0910c6f167b5e3d744f6a6081c8647bf2ead74117a3b1ff718233fba7a486469f5e9ab4ece4fef4c7f3ead6f3ecba2c3933d6816cdd825711d9b22cabb22ccbaa7e58bf5ebdca9675e8a90e805baf88d37889eee5f7504726d37609ab5ea1a96ea1fc6a06c0b56d6977f7c43e5d58d86da3f589585bdcc999a70f6dc46862ac129ba9685cfe5c623a5cfee0132336e6ed93ede127ca4f4e82d041283f790f14ece0c97b84b083a0d32a3df5d4629f9ae387e6f899397e648e9f98e307e6f8c9f1f3f9cbf19b10ebc1f2e3a9d9e2adef3eef75ccf31bb081c8e40b47301e113787ccf4d5a56e8e97e92bb6f193411085642d5fcc0f5e124adb971036ec977998f221295e14469c940e241e123a5a8b7f5cb4169130491d1d9c2552496bf13ab0dbb9b0de83b0bb9b476b11421a4f217c144b2f5a8b4cda88a8f303fae7832875fa51c80c44091523a143eec1c653bcb92da6bcd1a4789c89e580f9861e6ca4f9c887e58d6db2f16f89857227fbe2883bb131a6d0633df966ed379114d0f479ac304ead216b4122d1c96f352d3ff7aa3b76cbeed94dbb6b37d55d755f957abdacab713dd66aad83a6ea56f68917485e21144c8994cbba2eca63e10a43528f8aa51596e20d5e3247aca692bc9b6a8524497c1b6a685f114f06e6bf4408618dd530c69b5d98d35eafc76bbdabe5b9d4b35bbdaff5eb52ebb24cb6e596455dcf73a93f8cc6b29ebd6e5dd775e7bb5aaf81ad75de97eda5e2f3121c876579f97a167b633b0490adf997b17aecd25b27466c7dac91baa147c4fce7a63801ccdbabe20430f155b119dc165fd8d3510b59ed18639d4aa691d3e9a4022ca421f74fa414ac108391d3e954c591dcffd41e489bd3e9c48297be5d833c2121f7b38b0a5728128d9c4ea72143f43d9516ea104fa7d3c9055be4e43e66e14ca10ab8733a9d5090f52f2a070559d0399d4e33e85b9404b94fa7930cd890fb558c821a9aec9c4ea714001187dca7680f744ea7d30a5efaf553a4939c4ea7197cfab44242e39c4ea7169c20f7e7091688ace1c8e974a2c1cb0b725fd62a3c23a7d3890541f420f7230686dcbf562084dc879415b6a08698d3e96403041372bf2184f377be4b59ee2a88a1090e8e389da690fb4f64fbd714e527ef601f16aaa0418678abb9ada0052fe4ce8da39e4e3cc87d27ca8fe5c6725656638c31c618638c31c6189feb86f175cb7791987130391ea9df61c8bd851e223f19a3c537d86037a7c40774f0222545c0e8a34bf0d0076c7984392bfc8d7e36b032ba7a75297ee2709faf07d15461bd7c703998ab4b11c2f86e1430f70860a4b2654d8f943e5a83d9ad87635ff68824de9adfb1bfa65f30d18948deaf379cd92111680399c028b3774ee3548f328ad16487ba9cf05d91b3bf9b26f5f3e1d89779b2fbe892e759760f36935df1be7cbd7ba8e8a34bf1797ebb1c36a3b3411b9c9d67a48e248ed5024da4c1becf96b8deceb54b127b1e3558efbd771f1378f7f5c10ae9a784e59c732e4608f160bd22f685a096372bdf2bb25db5d42fd0a4bdb818e304cbbd14d1648e08b5d030fd5908ab95c93026c36b9e6c8c3d5a832ec7478fd6641f1abe4a29e527f06efa6075ec60b991d3fa5ba4e12e1ae507490fb602341a5eb02c88631d8c73779ae59c2be2edc06e3177c52207690f58a1fcbab04fd4a6ea4d2f0487bc9da7ef31e3a7a89f22ea135b472776d6a785e1abfb449f80c450b84292ab31b3d599a7b649a21a3af18b0d7160b997adeb7bdda78dad67f33e591e3f6d9a144fc5e82cb746a1fb50f9ca7d881c347d40083d589ef20f8cf0c8ae5cb2e091c5a3106662e52aefb18255dee3a2a760f9bc682dfe2d794ae410a7e4d93c9b5a53eea9d54211ca370d2bf9a0c1827ea3458f28f61bcc190e25d355c2e44d184d5f1732d9d1d1319930799969e494e96cb65eb7b3765f929cfc2b27a7cac2adae5025a9b8505231698aab1c0773ca744e5b9686dc97290036235f008ca6f3b56288d36b85f64a3ba5c91c9871f16c482439e322f7b5fb863872a49a4b9af46c54577044545ee43ec55928409533026bd5a874d6a8e12ccf3c0c40021e7c48cc5c159a84f895dd85b8efc661563c239808722e92b147e4c6352fbc320723c0084221d7b5e634f90880cdd0cb91e40a18f25625c92de3e10980edb8168e4bde6a8b2a4995d344b02f01d8b3295d9eebb2b559cf6eef76657b59d352646c8a8ce1cdbe52314560ad6ff274a20efa0fea27bf40d853b07bf2024ba150b74199b01dd44f6ec9603ba2f440fefcd2b0cb603b64fe5c064b02464161589ca200d88e983fb7ba06b6a34f99c2d483d40c7a83207f9e83c15e039b39d94067aaba306cc2663ef686b2e7be9cdd1f3297e7faa94b316649c5cbb50f50ef621f4088d79fc464216eb5458d4b22f1dc97adc668ac29a85308b82e02b0779a46a0a9ca11699b8f4c5d9ede17a759b766ae8c250173f52a07b6c8557ce109646cc77325b1b442085c6dd1a43e12550e12d8ab9999cf39ae1d254c72bfd2c9b1aecaa97220a95f104af2263b1a910e2d94b311771e1116bc164d9c000de64349a503db71738eef56a7494d9e5eeb3479a905d72147da99efce5bdcc97d29b003db7173ac0a883d1480f148924412d8cca3d975094e91a93bcda278f22089a74412980e25ae67e795c06624126bca2c9690482c89434f3db649884b1c99c55b8344229108712515f274844c6b7d194bbeb6f624129985c4a1164500460046e25202bbd15a5f4e234dea9afb42dc4c0903322e5aeb6cc9257520a9ad4a94b325adf50c818ddf322e322e321d6892279ce9f0b8444b6033f1f3667d7a395d6671258e44424353024ba2665c644ab225f8cd1b581200000310c090633cc545b426ca312f008be9c880160600012e115a02b3b67ad3750972e039923c0f6c0795e3df9d3cb8938969e46202b31a5051b5011efae7e80c3613692ee76ac064301eec82b1602aaac2d0092361606060602284e94700cf3ccc765efa91c1c67acfedee8e2c498e2f7be66ff46b18e5dc6a86f856071391004404003000210c22cb8270d391a38dcf351010426821c9950e217d0f52b7074283a61b90d40e705a9778b4cb65dcdfbaa1ff70efeb05f970a2df2af587bf17f164628b8cbf17a3122cf61bd0a4411e5d9269cde52d27c12fe393fdd380f70347ea43e14834ef074eeb43e1b4f7f2088fde5d2e0447cb1d3d3c62a2e5f73d5a7eff7ef11442782e2d1782e315b11f21d895832e7a4d796f166cc5bde85af1c353feb8783f80dea1f7ca3be2f2933f1e74c9723550de0e9ab427a48026197f990d983f77ae24e363a56b7622e587c35b44b98e4611d7e5bac7285ededccefb416e1c6acaccf76dcc229a46cb68b9536b6dce335878286fb48b8ea5311af4c300217d90ce1042bc0999e20bf807f37cf4b96f71c74108a1f6ea2a290f9ddcded55f6fb9a3cb72452a17884c45c15b05e1cd9ee00c6fc79b76b92da6c21b101948c10846040387fe80e63deb5b5d29cd5ba5336f79ab53cabc559a0873de2a0decdc18cd870818cdcbaf725990a1d673d198f9c1dbcb9f4d47b6288aa2286a9e2e01fa880995a35c022ad84112ca2520e426634fb90bddd2c40a430c70427290b1cb0ec110ec2741212146327688851ea2f026c426633a328637ebeec683aec8c1c014948ca258364f2cde2cde4422bcfd56c55b4c0cca8ea52b199e343562ed8077d98809f9f81e1247f75cb77530eed6c1bc5b59f7eb13c8cc8dc6c9b7175008e5eeee0e05876a05dddcdc38d1a4be6501ed17899e705e94c1be1b68b280cdf40948ea530b54efe6dd74f5fe2bd2a677c5bb7902f6c42e0bdbab56f63efcaa0abff79bcbd335c2bff770140282fc5eebf681fc5e73bc3fa09862071430d7e76aadf5159a4e5ef1861dd253b7f4273553fa10aecfb2bb365992c5b86277f166fde4534f6a60311c24665a7d02b932ad6ecd533f2a4c83507fa75ef10f8a25c4a1d6b29e5a58d125698c0992c4662836b11cd445a9caba282c074d3f07234653610aa3d1b64f96d80cfd4665f98a55b7aceba281580eadaa301aebbaaa276f619785f1a0820424b03cd785e5700e46ce5454f5a9ac675996656599bd28ebaab0998a890ba3a1b9ba757261183dc11ae0791e8fc7e3715d3dd5319afaea16cb61e58aadcf75d54a5a9e0bc3662613194643f39c01610d709064319acffb7c3e9f8febeaf96034d5e73f580e2b57d83ab9322a4f8be5b03e957e3cf53455904c61f87ec555900d42f8475fc503abdeb21e3653df7bdd0daf0b466c47ad568c71a4c309c9d4b70fc47a3446436114891a90dd23016b292a62d62dd9524a299fab1eeb37da3a0c85ac5b315bf259ee529a93ec9e93e0739bdd73dbe323319acfa4d88ef7264663690d654a319aac5ef53620bbe714d6c32353693cd4552fcd75eb15b6e3a26e5118cd55311a944cb1252eec1c8c85d184f275d58b613a5cb6f016f3c6c817f6b981a54e6f6118cdcb966561fd30072b0ff663385b028a3746bef06847152453789bef8f765441f2fc289e06fe5dc828e21df00f045148d516105ebefb647aca99aaeb59c1aa3af5783c1e8fc7635d48d09c9cbac3769cd0807ea2237f7e829bb8913f78b377f6c667f7c1cb0b6f29579427a5df2ce548f3a13be7a94b27762a197e5a7661f5adae94ce29658c103e0987e7de8d2e473c3c1e230b81af5ff0d3e91444ea5cf6ba359eb658599f565cb77a34a93fef045a6b3358eb5511326dba74b409a3b7eaede1a349bdc4feba2f4d6a1a5dda6c2c620c1b4d32b3e545b05a55c5db11ad751152a7760c83e568f7732d6dbaf013167ec91b0d2760ad22d126ca22263a16be42936beb756137518e22893941fdfd0694378af830d7daa494e6c06e2e8949e48386ca407abbae8adbc833831d4d9c6fb4135d3ac94e74e9e344976c7e02c6fa6aad48742d5278ab9352e2a2a85f4e74c9e344973027ba746527ba6439d1a5ea72a24b548ef43353bc71e019a97ef05e2720ce1df4bbd9628c317e4689a38389f2e1e8ab3125846c557e432cc4169240d620565a632cc97813f700f360b7500b3da52d4fb1a845f6154fe44a55e46accdad2f43224aad493f8d2c1c81bbae2b574895ed45a0b3dc5a19bafb02d96a42e630f75a9be9ddc5eb6e41dedd85ecef4be21768bf951ac514a9f1f95724280c66a39f89e7b6e7ab61f595e3e8af8d21ac5a3ce146f17c8128fe885648a6f1cbd0c33c5a31d549eca18d8ade61703bb499bfc6ec576f718e1bd76ce5defbde75cbff78ee5ac4cebd2bc28d12d246914a594524a29a594524ae9cc24561f9d20c8ef3e78e1e383d67b0f76377cf0f5e8bdff708ece70761582e5acb6bad49a9c73d259e79c734e0a53404579621f36aeb404ff6a9d73ce790a278d8dcd348477d812f12e470961e3dc4d489e3142f837f372746e7a03103c656907254d8ad7b1793b00b5cd3382df92302cdc5e9179a5682d9e88b4893636231f53c01495cff776301bd4720a1f71ce39e79c73ce39e79c3327c7478352547027c9995970dad468b44988cf8b5482f87c8340f3314f3c9a1762cd3bda31679c31ce2c04e82f3788eb2069c29e36b089a8cc0731ff9285391f757294d3c52841ee9a346a48f4d2251863658ea071d97ab4eef2e7fa7c0be517b297e8be226fc8b5ac7b5de5868e85bc54ae8f4e095d2f32a8092f16ddb244a23b4c45243a4c49393c516915ab52a9501595c7363d239014afa2a2729882b797559648c12a2a2a532545d430c3d17afce2baac5f96e86e2f8ba048051e9a5e5e30de625e82b80bde2ec42fbc89f0533010f53fe0ad36e90721b06b120d02bb26fd05bb26c938513976556e754318a39473525aeb108ce6e594bb2eb9aa7a548e91e009488aef1b488a541daa0846dc8960649f37b3694d664efc86a41c623432a7fc37a50118702452bcc468604ea1c180d3e23b074e8b9f29b7688a130e263e05b71107136fc95a6bc66ecdee61f20b4b29cd6fce99bb4a29219623858a31e658410833766196f3b2ba3b63b7f3c472a43c65629fb76a0f692d3e45e5588e26e26062688897208430e16f07e29b96e51017888beff4d1258aca21bcc9ccdb7449c58302a27813651025036f5a06ad7c5e97778e7cf4a0b4dcc151b3c8778ecba3cb3d2e4771798bcb472e6771f98aca1b22935d7288ee925d973f55f674cee8b2dc2d266f9d03b2d93ebfdbc7a5e4f2f847a44b7f14e10d884c9c837df9bacb751d6847d731ec1fcbb372196f6981f04e1df0d7b49818d055f0a6236f2a072293413f407719ee08814020102802197e8b405601815e708f263208040a6195837c64080281361d197ed391554e96a05c97f0ced021dea0065e4e9132e5a0a7dc1db73411561a987237e09241df3e3401b19fa864ad15b5492c99a211000000005314402030140c874462c17848a809abec0114800d92b0527a4e1849410e634621460c2020020000000000002005de42e67a7c0b47e83e8158904ef708917d934feed679bfedf6735903b3963c8117774d2631510b17168b564d4df4ad60b0b58e2a03284438e274a9bfd0f733f845803db5686a613e254269a3a1bfa67152c11563356ef59f41e91be9d318ff1feeeb85ad5e9d9211ae789ac34c2893c8747e99a9852220e598b8574c05c15c6021d1c9171dd0b60595ac9b4b552945b80fb8a279d448193483355b66ce0d6200bbb55c058a93f95766e9533d58ead7347bfed320422e46628c3a8fd13575563ca010a003933db1b39b25088a1cb19a28873b62b05b9e226f138fbb9d9c74fe8e957b044ade90c0c86c7c4c9c8b167933bc93fc866dcf88efaa78acff585f57338121cf3bdc5fb21e74b302fec3e5b9abdde3be15f84f43b29d5a16ae2a0afc34327797ab6294d23614c9b02ee4a50c5968416bc54816730848e8adfe6931a8f188d9f8c4278efbc16009041c55510c49bed62e78b279d04109292f36c9a213ebec4e60dfecce0e2c02349250a592cc0812125ae1a52479de0583ebb393d4a9c87736f21ac4e97556fc4fe5653316c3dcf940ae18d09df8afb1e886fce07640d6dcfcc2f091b61f9b91a6b6b1453d742ff66658a76d2de5126a2ddd5353aa3620d29a436720ada54643a2f74c004654e2f253006eb38a64630dcaceea76dd4eeb52e58b529940662ad89fc5daf760c9f583b98959895a40fa1114a379ef86fe6e9071c814d8cbdde5767356e013476fa1c0034beb038f4c1564cfd4d045d8d060e6e72a248ca3199bbec08ace839daf49f7a8ae6986967eb79741a12bda939847ff48f56118125937bc259055a53369186aec40bad408dc0997295f8c7a3e41cafc39f126d9be972dfbc34eab7b3d8f43d80985c790bcd9edbd91e570ba1f0aff8fea6ec29774a97e8ae41c984e8d243746f8245506c5da9acf051be1fbc7956986ebeb0d8d41c87f205e2105935d7769e6266372edbc4370f54e3cc7e81c7ed580ae899092609470015ce255be911e533c5763c8daef6ec6a3420638390e6218e1fd3e9b7331032a452b7705f1048711f6c49498a4722c190d8a62523280d4daa16689cdc2350dc5a04cf244662161bbac08dcb09715ed871876517fdab7caf3fb56d08f525f51c7aa02571f6d7ef3e029462a943087e7773ac90f90ef29b1dd5aa70855abcd9770e2cc9b930a0d07c0c2c393387b8614ffde35f1507ec91c55ed32ae03ce4dfc783938283839adeaa661184cd6c7b9eb78a6117d943cca5742c6d321e1f69846ef1dd1701f88745080e913276803e4a642a24baf0125a9fcba394aa50d45e6c06702648e220f487e128da1513585401bc37266d488c378a78cc72722c4c92b78fc28b692299adb691961a4ed906d93157f4f43706043dd2be006b0974236659ddaab5503c6275be5fabd3057aea501ab22d08ddc4ed74dc63fe11208bcb9484a8eba181337c68cd664b0b44b224bd76012c7dc9deb79f8d71b4fb5cb5f2ae42b4a44d4f3a0d71c30a34432900480ce1f133c43e7651686121adfdc7eaed28d5147aef4064fda5e642c2b6b16ffb5aa02ee3fa7dfe780f7dde291bce5e0f8fd780b295b3c3fac5592b39b248167ee43a753881be63051b2b310827e53c7d85bec2bf5682bc7709c569be88887a67aff341d38e5144644bfc4c7f183f31f1eea9e93d3b894cfa91801feb9937d7165536b80632e2501abb5c79a4109b8de61d2b180bb4e06c370e501b5bd7b3dc596e4305abf2288ce0c07d13d0484f90b88655f3c4d6d233b79833489cf51c33e4e2ddd20486b47c5783ae1a04a2bc46dfca0c6c219d0101d4ca87f69b17f172d98617a0cf49c0f50e24ec312c0965efbdc79df3ae1ee40187b0a8b166bc5c6142a2d61f0f98ce3742e682aa6624e2f245d321a267dc7b8de8273a591cff540179884d17a8e44992aeaf8c8d72310b30b4c798b0fe147f7edebe20fb3f38ca76c7deefedd14907d8f75ba2678afc58148cf676002c053a735f9c53b1c845cc800dc180c0fb056cf1fa951732278ce93e8badb8701c74a5e1f8a16529238755fee92825bee560a1cbf2f4b7a9d3177f610aee4a31f507f7a841470b3c8fde62b9a7470ad5126fd313cf94c45a0a4bdca0416e19a2038ecf2f23c3ec23a95fde39951b57d6caf8222dae2829da7c7f282cbda26d297979d337eb7ffc2953dcbe82e2da0f4c800e622a46341f7129db2835cb5f128a4301cd0b0bec181b52993b1da4b2bb33629f80c763ff0a1e03f1182c6a7f901bd391ce2b02c64ed7fca01722983b30c25dfe31730c7638660916e8ac685f5309fcf042c2d3c3d4602310ba11cb12d14692cbc94d7713e0bb8b35eca655a5ef86e7571fb091d765bff45efac65eebb3833884b537ea3731de63c140977f0a61fe6cbdf1552aa5a73b6522e14dfa83ee5b8b2d21969b22051dd8441a2628b5d5cc4b6e4985aeb5541f3b8a5b8fa05e8db331d1499525278a22b30d25db2a7ea638f20cae80f1b290096b2b25177b330ccaed4e36f19ea5714ad3981dab83f5cf30ea6d9946a1d4d26c3a887a5c23878e61533099900e71c3c4f1bfffb5081e2c1b0738bdeb0ed5f826400cc551124f3990e3ed0884253b47188b8092654030819404db297edaca2d552d6e05d11b28f6dbfa79a28b270b6a76d235e821a416bb4b6b05d58b636e4f968be2425e1a38ce9caba88302899aac182a74a7862ff26909c3b71b8b3938c30edd315f9b79464e40eb80983776866c8a0336cbed440e2b34962be1c71e1269f508c7acbd259921920a40562b912f9da576ceef3b7ed4c1461b467be873795dd235d9c21af08def2f8c3b39fd77a1b65897e4140b28ee7f7dee53408bb46d5cb06aa030ac12c76e774d950c38754d7cfd4b26c4d69991890d555bb1c790148d6fa83f3aa429d1a1d03c34cad7fcd5f75add71da57853d10b4697492ccf5c6b6fa860e878ee96d3f1f00534cfe1002786fd8816c4386424da8fd651a70b352d2ec311116bfd4c2178215399e5a2e174e6965bee34d3c8b4859d26aa193b4a061509a2a02778bb6b852ae913c365abe8030ecf90689f6ea2bc7cf8874d1b5ca74b854b03b7e38896179aa5867e2da20c8b54a494d4490b5d908575ccce4257be3d2dd5b284c54225d653af56a882fa2d9cd04447f1c3deda2da54956942a6dc3faea7af25b035ce8d32057921b4704e5f87f9ab39f781ff25f7fe52b43825c7d0052913222413efe426c0d578126fc21161da388b8246d3394d2eeb502f6d1937c6eb4bbf8249e9e9631b7bef1bca825266dc46ef81eeede901cbd4dfabc20e0bba8aea274c248dd7a1f53840142d55c76ae1d0ccf9737e33c8406bfadf29d00da691740ddee948fb820d8251fce2b33f3469cf55945e3f757f535b351a6ea7fa8ca6184857d71ba73bfa96474b10ae3b2254d77e879b66cafee0dd075dd9d1e459bd47847109f2f83d5a51cc9dbc87ed1ea48502924ab5c7c41121f08ebb7ee25f769ea6b07b014f747510b823812cdea44715dd2a17491e13028527796dce608359bd4865892455ca1de24509bf532a26716ea97aa591ebae4996a4574a1aaa4c126fecdf0bb7b0b6db71527becdece14990e6fc6687c7e2f5b1ed24e459c60f0dcacc0fcafd13084d0eb32cc5e7939c60ad6cc5b9719c002cf43814f78ea60b2ca111d4f866f9b2fa5aab69841b78564ec5c55aa84587e95cb5b83068526171ed820e1b93b99a6357706d37c0e5c40f07f9d90ca4402a4ee887ccda4eaaf9c59987877c52e1638a5c9fff460afa8fb32ddfef5602340eff60ed0b7a3e96d2c0c39e6cd2f056e896baa8b5b33df92cef9c3da2d8725a73df6cda7e77ce26ec291a1b4acce49d23536bf1327db30fdd48b7e3dea71e8b7961aa6dc0ab2f034c356d0c44d972baeec159a00314afc258de84df03710c3701ffc4c5a709ecf9e35f7a6dc9f920392671133e4989992b49c8d7c0f4e4105be79f59cf9eccdb9713d625515d41181c8a65899610c19fecfd945264ca00576f7b0a7acef64fb0a1e8a9592c7e47d6fb85fe851d3b829fe0462461674ad22e8247cac982cbbf882c1cc071741a1e46e51d26c76191b5f5c18f217f2b8e110f161cae9e286ec25a0b031e0f8aaa5f19d75096ea4e7da518397ad5e483b3c32a3192baf161953092c479ffed577e42c7933924d562865251dc6a236cf2abdb75b8bb05653b4ee5aea2aa5d2fb79b28ff5b9ff634f33f7006de41655fbcd9a9e82c600056dc4f54e9e7cb65cec5bd88cab713dfb26dd13a569a28d187ce27cd3f656e01dd3681b16447978ee923ee5e981b53e7d2cd53b478954e0050814970101848435ff1a0600c6da20b634b3e72b89fcb233464382331b936a9baba463ffd4551da914423a8dbd138426b46bfda17dbdfc01c94973a1cc52da48bb335b8d706a6563a449d911c4ae1b51475f35ee6a00b4c1285ec7331158ae4698fd261937156a5ec587d49dbbc971bd0e756125a2241a7278c2a5066ac40429f751ef30e9da2421988af8254ed07d68c50ec421e62560cbcd9b03c6b3de41d715789d584a51c907f24211629b9fc88acc83b4c809c267444527f684483dd591503556485b8a4bcfe720ceb2e5d67cc628bc6c75cb13e22e968cee092f94ab464a9d57633327586f22e5c0ea38cd22d5cf421bc7e2e92b3fd0b566fe45d938998d4d5e8f0f5beddffb6c141b291eb74c82b4e381a1e18f70f1c9de130d3248d6aeacf05f02d595097b6880ace9c97209fb007e2ec93c0234a8791c24682e59c96aa49c46b0185544d9019fd0f5cb5654cf1bd529cec69c873c6a4823221928717ed37372a0378fd4fb2b71ed958e735577670940f9246e58588e8ca9460cba81d403c6204373c3449e399571a60e863eeb455b780dae6e94a6093e3bdf807470cafa175cbca2366d14b7c7328389bd8d2226b14aa3b0b4b95411da9dd535275436c2c1bad927995a1f269f5df5fc79c8ff717d4b2a8c1fe2dc8615a9e1b268332283f4d029cd9214211f7aefda3ee90e678d069d16b0c51f6f07e349a3ea9199bb7bbae153970086539fd9a0fb1ef3803fe21f940a1c2aff3b61d2a95d66ffd9a5bc20e252dfe917d0dddaa1de57ec86952bd713483f58e295f28cd6dc2df73b1676a08e34e19800b7b2334a067b0bf47e5520320104fe8454b203489656e920b83765697a49a5c6a9b76121c6d83275e2b8270a7871ab3507806a8f8de0761643ffd4ff40c46a8af2c0d51f85196d0ccd99a05a3615b9a5f0e4fe2e4286e9422f771ddb16161f60e51e29181cd524f6bbdcf1456092674a63c477dc0e2777e91fb0d16c1d5d0fd7e88c5c6b2fd7e2606838f0da176c3fb51eb5a7f21bf8e0bbf864b04bf23422530e9d6fde10e4abe03b67afe23def6153cfc84502444b07adf7f531a6c1198d94d2b5513996bceabac3ef6f1fa91b565ea6c70d2882d451d72034e9803f22045485cdddd65c34b0cabe550c1b40b8cc7195facb7611ae7721d68c9111fe719574ee7fe46b09496e1bdd819706757fb9633cfedb7971f02104613a0154f833ad0f97542f3da229ea28074260dab314ecd86c3dbf8f9d3e4ebe5c09a30a143de94a2fbb8834f32295507a2ceb6071e1b0cd3c5b6f260594dc1ad557b4914aad6a3b76bdf57b5307b5f911d444774b2b19a0c1781c9622ea50ba72e4a638944278238e213257197ccccfa11f63088657ecff097ace820c3a5703118e33cdf3516ee2c4d3034d8ae63eb8386b456076b22faa69ea8d687d602228d5539e9096299d180024beeb0c112bb35d3d3c37235fcf917dd49613c2f8d80a346a2236433d39448feea93c21cb630b931395ee43feecdc770d60924e56cbf65f484ae5e499c8f1b1902ace53ce997d02bf00c3195e58e88764de578deba36e20a4878587f4cadc9683187171e7de60ff7d76988716d57661c50f942a46be9439eb2867d134ca331c2d876a16a80bedc1d6c9d608bda9ed1974207d3cb10a12dab5a54726d8bf27f9dfc5a83e27f46095cd600da738c3d455316ba6d9b78558ec1f1f85ec9efdd0fd0ea8a2eda05d52bf1298bd5d40c290dd16ead41bd29c818057a242e7817b41940bf6ffc08e2a015b670916a7ad91110b1fc8307357dec6a435def3f40dbe068a2f2153ce856e9d124ced224eb73c76de1fb672a4fa4975b19c19386c2572836be38155ca6bab6ac663293d48d804a351c51a1916c1009159ab0f3dd7c4ab6b37665887a2ba404065441d83364b7ea5ee80459e1dce4f63ddd546457a2a88131cd753a4a06a1033f9bd8eeee8b5e137141a9bbaafac28e493a8ea94f7566eb69ec8ab60c530ee06ebaa2a0c81439613894401a7b693ab1b433fd86639641c40122f925f6eb97d441191bd8759c3c1c5c0df51d26c9b34e9bc009929d5c0df3cf40f4fac0758124a319c34a77baf49fc88b196ff370a5f41e3bfc2b7cdd1e3863c2da8bed7804fb1a15e8583794ef0274ee4973e23d3381422db588afc1dfb6443aa156ea4824ea64d84fe4f4211bcebe6564754933ab18c5b7638941ebe2b9ad61696432f3721aa9b86c78a652702cb6de8fd6b451d6df670ae1328370756b8dae16e7e529625519ffd6c35ef12b5dd22c2bc7b90fd8223c7ecfb47019409064a77f911a70de1d965fa26f8a5aac21fd1effc2ba0597f8df9211f9c2a0a02b03e8700870b1862d1a81a0480d958cad5709296df4c1591b9f6a3468a1eafeba6b7c54ca8952158f4435e7eedb87a7aa15c4dafd60da575fe8b125441d441bacaafbe4a0fbb6aafb6ca8c0a16c2f64e4709fd748adbdc4f17305a29b6eb09e9dc534a4e6129b13d96be7882e760500b17914d7f9d55c776590666121e4d63b13516438453a34b66fba6fe81ecceb17210eef50cbcd78da0b1960b385e007456b8c068adcbcac266eb03eb6534795d38cb34b818c731498005a18c28265416c24943da9d727dba17f3bf50e69262e3a33a1196bd450d563cfe9de26f0c4bf96afec2ef81012b7a007017656e99e0bcb90992abe5a92f0141a4a38f4815ad94b25254ab8a9b52f7d9b29d093e43d6ec0122cba88e6ddf24f2d359f44391ebde6273c6d1093aa777c64601a8a43f7bb611ea02a5a2972993ba6c66fb2660b44cb6842d4b35ac87b880fd9dfd9dfc4d101f735dd9a43664d9b5f97433cc302e48cec7d0a7cd70dfc4c5d7808b587913e706fcb0f81f38fa957a61a6294720975a731f9c95ad3a718d403299e2e946432abe2a3513c259988a132193fa2428ba87126581d5b5307b363ba2888f91cc9c67940e9ee13a819d6ec344a0b5ae49e661bb9a02201018616ff4cb1a7211c758555f117de1a9766d8299866106b364aa27836047d0e4f294c7b9671c30e422f0f9cc0e76140b91d444a82566ca278b289a66160f0f8492445d8a0e4ce43ff1db30abc17dbbf239fd556f02192ca3d0d7fd66cf7a4690e68b550f10efae46826507b27e1a4b8eb88dc61737aa6784f00acb2df3fd951c94f7fac2d788ff8083446f15b404f4cdc78a483d641a217ea9e0623809a6b8bfe4cf863cb1200a356b14039e9556fd299d983af0583581a5654162423ecc266617d541f1768190ed118019105707dfeed67a55f6fee88b35c7484308a1c4f62eb4d1fcb95751bdf8772fdfec54227451b03d53bc4b69af55e00b047dfdaee4af45bdc1396f6d85326bf3d4cc4ca451d5c0809ec7199cac9ce6f228d0592898a2cc06110c705b551aeaaf027c6bf7e02ce940389e4a32093037dad065919c671c22987ca77d419dd500868a7de57a59c2fbe9273aaf69d28a7f5ef508a679cdfe4e2464f3607ee851f50cf04da6612dfcd8f6d9bba2f64e8a8f8b4c9283bb9d6adc16ce6195d6efea6ddfd6168368069524894cda656241891fe136cbf9f11d8adef2f1332408377337abc0746038ed66bfe24e6b27a35cac9d136adeb8cd0b51d9a356efbf6a9caf9b6967f393fe6b4a9435026dbb5feb4618ace665c087efe240ddeff12cea9d0b04f20fe616b507fe27cfbc7c08b6ce4ac3cd5b8bc884f986e4e0b2386107f5124f98c4548203f1d050a3df3b93caf594249a7d1feb36001387ed3edc8219ea17e658eb317f6953bffbf79756485f1164e474f52d95337e6f8f267c36dd9f38718bdea12d2a9ac782a7ba2f0c620ba53a3516bea6de5d897079b0bc4c1e1e1070f3dfaab42589f6e807c87879fbb915ecc66e76489c0b06c188aad37569e460a300343d1ebbee8cd3fcff5fb893c64121b90c8eb70ed2c7dbee57bcd65a6395068ede769af20b908a2797f38ca0f13ebbeab3a82fda0b8f4657115bfa4abc3197ec0c1019e860685f139ab8cdc5ef6072c7c3b232a4f8778f815ad26fe573a455dad8e89c4220f0c8dbd2ed89d38e9d87565c60b793631761253b3bfc6ae95140723b0b7930a6313faa19322121c615c49bad6240fab0611bade61279f011bb3709576097713fa62a027c360980017da5d1d251d8b428138af49c27f217a03878601bbf32cee682a8e682ddf502c05e2d4502a0d4bf3691637328988250e73a960a86c74470d582dc5bb9e6f9525c84683723e2a446e1590b862fe7cd648b4cd6707b39ddd234cad5392baa9350912310bbfe88e918b95b81defb4830ed22d84b54ff8ec842f8ff8fe045ff7f874866f97f8f45d0396891b10af02c813e29911c1a87897074ce05042eb070011cefacaf2bdb24003d6bf9343e39cda99062c4a44d3ad871aa61f2784de80154aed69b8cd662e7922fb5a23bb90d7efe8786d64b15963fe0e3382f6ab77ef8f0f3df9cbd60bd8fd46ea45f376a878067626cb0b5855ad7aae46fa418fc89251efa3318998114bca682047d8983fcb7daecc2d67c8974aacfe0236e7eac838c43da257cfb9552174fbd68014bce41606c717a74a42fe56dbe276d528604457244fb862e652cbb0c531695fc062a611fe89d155929d03c9029c8abc99d4e402f606380175d38870f5d58549b575d814aae3292e3c978fbba8b074fb6dd216ac7205485d738e1cc8dc43ac367e8cd95a8f2467921a0b65a950e4806011b619b9d6f9b659ea8e649fd6740ed85da69997636ba878bc12099c2c352e93457997436616574bc3d88e990663025a112078a4aa91a540c0912c964046ae5f9b941ed7930f547443966643b755599ee79772c3d9dea6307e0fb7c843e0b614756f6ad6ea06d297724fb991ffa8ed708c62fb1251afa37cc4c565312e3702d288b984f62548ab5ace2fce0ff939b43ddfd0050232b7fde6c041c622add22128b11d70822907618c333c42d56a896c0c842dd48901991bb3f2086cd6856733b6d8a716856d39b4e5a1cfd8de0121968247f4e4dca6bdeceb13eb76be676ecb52ccf24b5fdb36d187122e100bf157b4ac236668261f0673407090d97274390bef40c77d0384ece80728cf277ead0d05690fb552d11ec8f5590b3fbbc21f8e60467fdcd11b84665838b22c472468875747786504eb23b668640b47ba7ec7e5280abf78384be3c2ae1fac52f9e752fe136bf20b6e3514be7c457b1034033677260aaf7f2684e03f2d65cc59e73e5d164c9523ab87979ed6a22d471cce468fe002ff507a7c024c479fca6c2deea0ca315030b8c6cafb1e284e06452a0ee79071aca7479ce3eac739a3d51af798264e6f02f2ff27961c12786af326da10710a08bed1334fb2a67a065cb450a320dcb5f5d7ff36f92557b2ea56f43f697c2de9b56d92251f0fb5bec502ade268fe9968a50055c7217509720e2fc54c34894ddb06833bce0994bbfb9e96b70e7875e1e299a7694b69611a8c63758b1dfa53aaab25ac6ea5d2bbc2e3c3bbaeee92a4e89eb39a71baeed0611038b9335975b9f21ac8ce1c719ce230f7e44f18f1225a1b29d28f0a24e8562f469ac519cbc003862a728c46fcf34316bc88b00eaa5b0859d60e510b72de0ebaac2e44f7933f11dde963843911a011bbf14f9d2b875d7b64f8edc311b7c7896c23afab85c6941674f52c980a20f12ff869fd976a8e2cfe5a4cd2a0493c20f1d6d4133cc58c05bdb938dff394203e530a153720574d3006847413af662e7f98075b49a1f3b79b33babc73fac7bd7279c3c41cfe2cda9c9f114731dcd164bfac121b5619f64231982f4c82af2b5a97ef5f6cbe644a9242d6e5ced730ad1d156c3fd0fed4a42c6862be86e7d2d598b832620dd76b7f3d4e7431efd3c20f6847213aa951e881c205865533186ed5cb0e4490db996876b622fd149f8f6cdc58e2558b3c77ef5973be5a112dd860de4c4be599933fe205090f0993fc3a2e842dacc66e15a78dd1132fec65c874102ff95bf1dbb9b119027923d0fb1de851878267ae6f4ef867129ba8a0f5c884f1da63202736b3950470510a9a4d8c01a041ab70a693d353600c82ef0af9c0b812e6b67635c6461c80139d6be0beb2b926a8aafec129ca9ef6efcfd61937ef9eb926a178e1649d250d41f9b174b2a9c921d030d3beac02681d68e6cacd797d2c0bdda28de12b973f37fe1466b621a6e1e2c4bddeaa2571bf0fd5b0460ee4f23e56debf645e9525a434f0eba4f15a6210ddd1f3f8ba8e29ac300877d3cd31134a57291332ef5ab9a4b7907a03c726d9a75c6ccb8ddd5fc5923123f36b48bf80d2562e5a4a94f34bbd8e6c431d82de5e82bb652db6febc641b8df83c9662a4a898298dc8ae71bb4d47f0cca78adce95fa4a0f32de5be40f8ab15c0a6dc4939dfdd6a011b8dc761c338dbaf38b16c4fbace37c55b7202eed932fac6f70a911899e3310f3bbeed832ccd58bfa9407393d01373045a16ff0a443c31cedfa8486e22704cef0870c97cc59d6a5ee26497e490cd4ef51edd53b1c9d1925b76f571c7179c3a7e6c0bea65b7ba48b7ac9d54c95cae7a57524d89e22df352a6c72519acda9b74b4b4dea30baa842d1ef22503d148906632fb043a9c3a62b3231d42ebf651b15b7fb3c1586f9923bd7a4174ae1e314489cea754b93d29f9a630369af412a674d0fc4ceb41ecfa7e32257c66b43bb03c6be27d257eb77fb55cd8b184854a7dc034dc4f5ca307f024d7fedbdf9b809c3578f389e809b3a36c710fe33c941cf9a73af18bdcec44348bed9d8e09d5d711bd81d769575c0cb9ba57a8b1649c2c84d332f21d5b329f04a88c09808625ea3cb7fd42a81d3fa1b575c1adbdc953b0c1d73d1112fb46669e60ee2ca1411c10e0ab2ccb120a978ad2e32568f013f98b99eff46586666ac72ac34c25ed26f7377bcfbdbe70cfa384cd94545a91858b750808acd54f66223269a6e7f5e4019c08e23e5b198f8a9a6e6f8ea86767e2d197c2ea2404b19c1a4bb2f7a5553ffe5d8005be8fdf7a563c93df2c5c2fa459e01c7f147b31f356a754f7a76d6c3dc579efc6c12876a6a27619ff411a85cc606621b9e5beaea1fcc5a326a2fff340f21dd0328d6f351dd068480855b5a74435abb9e170a018a766d58437863708cd3252d15db81a0a05cd185d39ad2ba8f4944cbf5bfba9233fbf210f502e0099db72e25c1e30ba6665c804acbb05f80fd957eb1c2dddaac2ea608431a6c954426028e9f95c5fdce2183516114595b40cc91d7beff665340d6867286067284de1cc17e74bf450f296ce836870762777533479e092e34d74587acb70f5c343b0c52af71c8f18f1e23cca3d8a43db53137904517dc9a9b906abb657b30cad8a156bab71edf1c80aadfeb93e120485e112ace2b0cffdfc1a1fda89a5e278f07fc9fc629f6e28acbcbcf9ce11edd437a96a040babd28565f58d0b4a4910dc0a5fa4d957df409ffdb49ae7028723a041c24519e3449f07d1878e6e37fab2eae3a1b71f1510a76e4fd55c74aba300691aece74eb5bd946cb60b79419bc7644da2bbdc0de4b1d13ff668dc3b62df2e7bf05163da9dc354019f395c180d26ec2828cab14d2fda07d671e0a9776b581b920bb2b74e76dabb6e684ca1480758571def4eb2b073d308d4d2cb6309d4f2caac5c32e2782a8009ce5f84d8b5e6b6bed2a43e979091488a17de411fef8c3c10e26bd32628a63428ad65aa1d77a810cf7a616112ee5bd8f7862648211177df321c5c9c97f23007e79a78ee09e93256c82758839d9c12f5b8ad837bf3255a04371b1cd2ac81b9f6270d4752ff0b9a43529d1a9a01179376b34c3b5ab143c64d853e1dab076950b1b6d077d10efb35272e48136193454a789d168e9b32b9ff6244e4c328e4a0d137aa13166caaf0219274664d202d39327ac7e559c1f1beafb9d9c14c51282515eae02f04fd3f4a5d73f8f1818f360ce84a1d7f7b273c817e0ae4119d5ebfdb91f43aa9b989517331ec2f9c6629714e1472fc1e7532df0110ab47c016924e29a792f47bf825491ab065c59c8459fbc6ca6c25a595fde7a7bd951d460fc23832e99dfe6b6182b81a584a2ea9f181015b3a0b5395e1a258b7a971ff31b94a529e2111156644192ab4dd25f912989dc9058a3d823debd4cf2a9a1ae427cdb34385379b81c08b2399f097ad6700a2d0ef86409673155df71bce3ff3df9f4425550a475b4cc469ca26189e7d7c34d11c470f1d5329cfb5aae7126b957b4c5362a253827252a81326074a44c75364ef3c1ca74a9e63d62b1cbd17ff2e41c7c6fe5bd7d260875832e340310b701806139b3f40af130eec79619816c916050f963eb15bd6ffe64ce019e54867ac4bdda23390bb580e4c16e669c7b2767f62466dcda23350c539961e47b1c38a77f28ca0186c83be148f0f6900916c1755b87081863c3a3b1790c08d41ca4fece18553bd59897c03a0d34db9251821fb6fee5bee2a0ee3d72979fa5a609d6f761a10b28e303d5760b275a13eec109dee3d1645df1fe88df484a8523e1666a4d10c7facb8d263e464968b233be5314139e09821d43ffa5d073ffc9fbc029a7ffd6f1e30fe0a9c42c273ba4c4e58c58a2f1b8e35c2e24ffb7580a02a3d8ae88d03779d2558bfb6fc0a3fcdeb8ae392d6f2867746eb38179f56b522e23115837d829a6ef133207b41a5840a1d66890c816c726bd520dce61dd5bc21620919050efaa1da03bb026e1aa78c91e76f123b1e7b93cff9c3d3cf395d42d147d07e81d67b9c67b251e0b35e84365f184cb5bcbcd308edbb566caf0ca46ad02070bafdd9e4ce507e7bd707a7935ccd91a066adc9277d4c8637f81886e6ca83d49f00e06d7144102f1bcdb81277d538fd12b6b49ef31156a5f46c0696658b5fcb56ca7e7652a4a1e86780d51d50118e869265bc939a2b78c953c87b2e9768ca2a34a0e67e089df86daac80b1ae05e5c3c692e812810262d5f2613c7a9b74756229b24e2f62c1aebd28ad63e2cc74ec997de76f9da8c4c59879a017d296c7bd395a9b528bcda66375d3fc0f781217fcd04b22ad90b7b2393b8bcf830999898c4524912e8c0081184d2331beef277ead69b88bb8e2927dcc372fc1891728ca3fc94acf1e3e9aa5b8e68540a17777703828d51ea11cdedc6866142225450a2de6316fa1b49ea0b303270466ae83bba491028b53c596c9f8e2d06baed8afc3e953f32274e5e68fba01c007b8a4fbffdd404d6a5995319d3e74c2ff98fe9853f107be177d53242c6df2ddd6c4df5eed255c019a0e16d6162d64cbc96048b59ed071295c5e25d033538025329081ec2e3c05e949f9325d8fcfa5550b09259996422b1a53f01e23e4a7c2d515104245459b759fcf28112222f1c0ba6d472d54208f22d2ea8f2e8b051b631fc44b8817a3b26031d1866cae2a04cb919cc235e0dbc5c26808a8462b583bce366d007ffdf8520495f16e9c42620e4b30e108aa4a8de7aebc2e84a84b9f1c89dc19002acb0708b1d4736b2b71421eeb72f8310a21a0d679b4b07fb385a4f8d1a7338f3d1d4e8dc0a0e0b8ef14e463ccb26dbd70542f78d1a059378717211ecfbf341eaaa8e421185c38912aac6df5c175ceea5c34b1421220d9426da0c094383bab999d25db2190593954b65043672c1bce83edce4e2ce90994b2610ddc101e17c6d8ac1ab00b68e578829c4c0f29cd66f393d004b1ddb1c04a9500370186cf16cbd738582701b87e54ea655ce8d93a8199709e6e28d804dc549a25525dbb2f557e6ee173c58deb73b3be9680fb440ab3a27e8e9b8b94b203494072b6b11de5475cf3299e516869804eb2d71f3082bcc790fbfdf3261b778a3e89830b4f8e7bef452f0fbdff303cba291fb929617c83f4bd895e45a637a1973e6777932b3f5184f42938d26635c57383ada8e95da42d90953a2aeb272d51c75bc01560498414a36e69a9dac3e6166d0758fe1a0167ed44a0a5d8862c4823351105d0ed16750fd79af9ea120b9aff60a187bc679d7976f196cb9c31c94795c8a104d72ab5644d43429ab6eae573a300e6864614dade20b1e0a0cb3ef7c62ae7480ed77936de8b8835b760531c5e10f93469cf350ff99d5df7d8224e17e46c533ebcc50008a19d5e2788f9350da92769f9df3972e86fe2513bf691e9f3c93a34c4ce133124b0816c74b3ed4d467a6cf6282f992e096c85f69415fd56ebc87c4afddff87f8a2b5ced6930eecf98332f559ec1a488609271f22bad2492eff66c76914c976900f6a98741a76c506ece28a8ad9d604cb69e5b9d6db3c215887e5642fa379da3a4b22a8aef3d0e024c61109bb8e26203352d597ca95aeae56b8c32a423185234f31ea78811b1c0293dfe4193278fc1816523a0ecc2ab1967cc7aedf4e4932ed73089f49ee008145679c94549f6014b98630ef3384a8acaf5ddf8b5524c4ff0b7868263078d74d6661224cc1cbe2fd934b274180be48cc3502e5a19c48fb478aa2bcc71d93e6a71d3cead8f62a988d24a4512a2283d46ec0d5821bc6851f4a76218055d64e9b709f96fa099d2ee6a542ef7b3635ee8664101b2db9e7646af72c20a4e950ed39590f835be838ef07e204409041a48b46f093d0f375681dc9199856d2cca49096ebfed51f01012ce1ab4c8c81d40a0269ec607016ffa7735b7faa0bd3d7c1e13af5383f81485e8ab60f1b77bf23534930ddd110183d31121f0ae35aecd7c8aa74decf3ac5c37942c4d1fdd3edfd44e34b6393b6eea151637641405543a0e3277fbe73a69230db7cf58c9e2ae8b7b260bc7982d851eec9e85bbc70b662ca51a5d3266461e64d341523868564d1ce540a6c562d4f68923b51a9f883c7fbc5a5db5bec869fedacbac904a6b5b6d00d86488c396c2d9a0d671afb240edbd018e2cf9c927022a2a128fb558fb1799025a0a63aa7364b3acd5703d830b466c1a9f38565216f7c08735e5140f4eb8aae59d2f8dc82815845889ee5b2426e3d6589caeba356539727ed93de37ce627b40b4fcdaf0fc3b443029a4d1d7ee515422473530cebf3aa8f13f1ce135d189b072654112b32cd847165c9823e6cb2d3f2d1b7a3d4daea4c8dc9f25c1368e6a5eea67940f1cf19c8d3d8c10b5460be153cb36934dcb3381f71cbef4db7e150c58fbfd82283a7bdd41cc2b01708b3aae2004fe105017edeed6608f44c158c1bc8d74f506108c1e8a3875600506516558173d76ecdb1a49a758c0dec6737ece212670252f1a9fa7af918a2cd13a56bedfd7e1fd09797000d7b18a05e809a4483c84d1b12d7948b289831c1cca199682cd31c2ab146623fb7260a3d0010fbe3b6a16cea51f6f944045cd623f88a2d31e5f456574ccdc10b1f93d3c06ed4be9bc5fdbace48057f4d52d2c5e29e2b4456a18555988c1835d4536cdadadcfe2b3759caa98ba3f21a5ea3c360a9f9ef5d14cde1b941405ed1c9a4744058bec22a54290ed8f4a0597a8ba1ee33c6d298b5ec0dbc97d6010d8b7abb8ef9fc5c840974924347dcf122146c6de92b89bd8972633098088f44ba09f8fa3766ddb3aad651850bda3bb43849580a3abcb1b3ad08231e3ddb0a326d26735027e9cee7e7a494805a3f0d910a51d6d4c0c4ac4abbbce700035c9108ececd7d50d5e68029e4d341452fbcc6fd5b508157d495a85995eeae4a9d855530f49fb240fd2a5c6a2d20ab0f392fa5045aa0a3ada59e102295456c2133a1e965a6f42ec1b6b6113187beb8141560685e32a4a14721521aa0bdbc41388f472133752a0fc3eed64efa45d0f826ced5051811222d8aba2cbe2ece1310803eec9c0113e8082e2b6745770f625f250bd32cf78f0dbb2e361792786cde3387975e2834c9ffe6858464c73e4863f720a4df662c64e942898b4b7d2dc5bd0c725bc1872d2909541ef068b5be72bfd31d1686db9e72579fc51d9c11136e433aefcfa8c171d68379473cb11273ef2c9a54ed91cdc9e18670e0674c65200a8fd23f1701d5255318bd35b986e5eb341e8f054688b0af3b995682acdec5c2d6e1aad440013f0522f0a536f35176cd4ce4380f8727f1d4e53c2ada5bcbcf19584221cdcac776fd37203f8ebf27e889fe4d00202c67790f0837f1b04e9e25dbfd759f3ac0fd6dd53f876e31bf98507168f2d80463bd8dcaf112468ee3e940def6914353654a508cf5c2ed726e13ad43a5c0ea758529bdc485f688179fb3ac3c7843d3a390e7f634f7387f70c66911356a1a139ef466bd3ad5ea0d671fa79c44929b67981b10fa8221c2fd6f3ddcf60bc150812816db89f216c0f90ddd19f514634e4363d99e367409077dc9113414eadeb25727a5ce54e15eaef1ab7dd7acceb5931f38c8880d13fc167ede486121f1f72c6f8a800906dc62b0f90687fb50de121b5ade2c565e7e7641062273ee8deba999d68ada7c6fca580419660fb8ff5351f8ca3663f44cc5c4012dbea565ce8bef3c01598eeba0ff80342a9d5f767bf827cb55de2f131ecd74d39a6ef8680125aa97d2aa554140bddfd03c4dacdccc32a7f5dd38c98b29b2200c79e59b9b2705ab56023f853a0c47a856c53311bb9606688e81edbadd0ab96db91201634d6335f796d4b3b794c9f9e7b48522e27dac02cb2b62f8119dd42410b8d075e879f9309927d9ac4d18e273047ae82175586b48607af41aadeab3391b6f41ddca2b05073a622c324be4d1755a8de68849dbec322e1643d5cf71092a974d312c18ffaefea150d31a12f23921bde12325998999cc8703a58147fb33d4dc58921277ddfc509a359feb770a431aa3406629a22cbf10bdc2ff049f06a505a73c884306cb0c793147e2be386060235c001b4cdd3e30355170802857643494be1ef061f2193fa9db8b02f58e414003aa724d5f37f469ad8f8e04ba63c6008e0605cd464309a00e920052ed9d6f496dc40223d30551a99350d29a43f3571bbdb817b73374fe78f3b7a4ced8bc2413ee42d5497125f4e5803e71c59e582a31628c11c2bdd0673364e2fa6f7d566a997a217a4dceaba9c8bbe3e6aa2ad6d5633534c0b0a404ebfe808a4a95bf861c121088483cd95fd08026bb8490b94463da05e517123f3bc094620e465ce25d60e6c7d2f1a537c5f8a618ea08aa687ba1ce7ba47334505019135922b5e264ce6d6a3fbdd91d6913bcdf4a18d3ab06d6e1f55e9379e2fa0c41c384b8a847fdec5a94ce0c58fca4a9c136c891ee1a642371c096ddfd882420fa0284fcc7b6b220f7c60ad6f4f5dee1b13c765a491419709f22aa0f7ab1a4421402979b4d82f4f6624c2c41f102434cce044220e368ac95a7712e1aadab0c2f170a61e6aca860fa144fc0a72e61f3f90ba703b5697e3d84f90d5801207b691427571427fa9928a7f211a889de3b5242f7146a5495f1ff4fe4f178eae75523299a466fac6b54f0715da99466d8fee950b5e19ffe819fe25131f2059f6d459ad9f4dfe3f478f7fe2c05a9504cdeb212de9793b45cb3d28dcb1e95269666029c86763ca5cc6f51ab9e35fb805f2a56d8f849662c2d027419a57cd79708d4c4a918934165f0bd07d450c39a24c95436640d589aa4f2c604036f4418ca104fb8b5b4d5a0d43217c6adf106e0ce8261dd3d663e3efde98abc93808f9fde7a8bbb1c257649cfa27bee6ae45b386ad45d44fdd5757ae29b600d2d42ddf3d269b747565fc7aadb5b7341e73d8a847af88f783849de7b5f0e846d15dd7a1b2d434abfd43c2a7b9d876f68a95238386d3eee077c4ff774094f0f2b6fed3613461615afe853a5cabe3f5432b0e07b9aac361e3ddae7c9a702e4d62cefe2a8418678aa2a2a86b0fa0adb8925b16b0686385538163cad22beef50dc562a2423a08a91f3805e12eb30ae42eb7900be85500aba0abd8834765e60778cf7d4750aa914d9094c647b8fb29790568a397233a45e77d7525b93b3053069ea29225e5caef88ef36506184646b4485bdcd2650167b5687d7e6600ec2154f5f3ef7c91e7a8f5352167fbef48e17c23947a884111ac9b9441553762341af6626632248c756370898e867020ab6b011cacb55ba6f4e06db9f015709dd42783eb748767dacd708f4ed0d8eec8473266b73ef2a38ecca2b43f922debd0f388237081009ac945cc4e1c2ec5dc5ce9f2679d4e5a4afdd0cb640fcf537b867db36dce1c1a7e40c0697a7da05c3fd2147c269a517e1efeb3b62216c2581875aafae7ec57652c251b5846507e1aff69c3ddcff58040cd62a09014d9ed0b9dc9831a2905f34b12fd19f1b4d890cf48672b21628f3537e0a4da8f9aff532c3a6dc76030dcb902070c361e0e17855028a5263c7a3edc749404213c888325e60e0199445649552416a74013c70beb6cf1c8d060bf84efbd0321fbdca1702980df7496923b49f8bb21866ee8a93462380ab11239c2e6ce5b0b46c2f0855fb5a8502f6f62b6b160814bc9ff8f749a933ce69d84ef1ea4411d3bee3ce26093853844dde374b260cc86bc6e5c7f2f5818a696c5bd167b012cfec26e7be76c425136eb67d59b28431537f04dc4e81da5dbb42618b8549ed06ec5479d49c03fdc0427657e3f7f6c54d1da5ee40bbaf7a463301ad8f8094d7c9a1bcb385b46f86ebc28455533dd75786a455fd6f09028bdb21df3fc102d2132b204a2b4211295ca3d7500870ccf05981ccbc9aaf31dd62bbb8f4e788b4aaf9c45794da568dc5a5d9779fbced6b05fd8756c6149a1267907b08346e138dc70c9320f96cf9c69a57e849f05ec6e92529f3a93daf7072ec95c9c961c493c07d7cf258650f7ed8750b44da3bb2893f79cd638755e1284ab25c17de4de55d75f868358793f6183016001d0b7a997926d2f3056a97fb003ec44977a1fe9cbeff3a58c2a1f409316a2e6f8f31ba9319454832608282d29aa16e5b95f920e93193f9a11fac5cdfd85bf2cde58e35c43dfb635bd14fa806906d31f784b0dcc78bd3c5845b859c079ee8fd2b26f0cd335af6b862f1da90443c2f688f7390bd1153fffdc9b1ac93eede0b93fd0130fcff725c124c7a1fa58a5eba9623d2794afed51ab85f1be7dacb608c75eb29467d929faafb118604c78579384878fb9b26b0b5c7d262cc1cb378406067c06fd96d7006b8fa41f3fe52ba8a2911d160ed0ecc42b11e4d72cbdd101798c266b6087f6765430f31286a46fd645efecf98443b23e963c78ca09b79ca6e8528cc80d1aaa4c424820fd3cb5270ff4a64bed59dfa2c005ce7f66d9a232c2b8881e4d2d62e50f5b9f6303412117cd8da98ac763d8ad5db96c357c3b501a611f1e19b0ce30faeef86f7bd80005c43fb82a65b522ce576fc38e534dcb1dd9f6211c300e982e015bdd8863c233125b51b950614532318694df49ef8c8ed85d73370b30497f9e4f5b4e9a7a89224e213c2e31f8eef047332ec6a60ead87fb4222c9312ee3b93a4d064d45b77376f770d26aa5f2145ddfad953a4cd8e185c287028824c79a9dbeff8f4b82c4e24ce52edb0c9395dab8a42fd385d7f78fd8a167c20cc1f856045147658707e7c0699b9452726770ba076a1b203b64056abf83fcfc572713bf2c190af23b661959ed2cc2ab6b8fcc9229e5a99b354a5f72053f1f98025983a3ed8a2d526f8c0afc3008291845b557964f0e0d358c7517471581cb0e7b2882691274ea38e19bd0a8f34d1d719b3719ed86ec3b6d04a285aec0ebd420813adca866b3e7514534d363c2238a95a3b90228e10a20453a8ff6efa0ad9f2a7d6b93b0ac0a351f751ac51d8eb6b5c5febb85d7cfe3ae0051ea69712e3c6523408cf88e4479a2a73e91e1f4ccacb0053c75217e4b69d3e8f164203880c5a1942caa600925465fdc0d764c04011b32283176c45044c5edafa74a0ab0f3c794459244dd7ddabc9fcae38b59870875b66c3bc2afcf6e6332380d35ab4c2048f83565b06c78dd4172cdfa0e592066f2c7a46f3f2d98e14ebf6d4361602b56d11d3c9190f8a213d03b357b9d5afa1811975a424041b85b6bd2d216de8659570408b9739d1968d8613b0095afa13b99061ee2a753f6e9c39a407accb29a6a37dc0af5210ff719da7d0b63af69c68669beb2f1c989c37cc3c9e3e274136504e2ccb10deab7f4151a349856aa32353906cb40a062b50a70596f6a0e1df05e554eb6d4ea5d4f2d7b029ac945364abde6d7899bb5bf6bb43e24abb643c8a6b57846dbf76571026518c7268863893e905513db713fb11e7e732ac4116aea193189924b6dd401d8e6e318f795415d6ae78828eff0dee937861d54cb747315c917eea8a623fea14d969dc8cb600826c350495eb945b346555cb71b671ac25b90f0dd840276c6ee3fe793883799414eeff36bbef04128b1af07582209f996ef6006f03fbcec32b15ed8f81a97ef5ef75d8d8528bae00414ffd8cfda3f632d63ec4ea76ccb4afdb4164d12301affb1b7b369f3adda11d7f951408210434885c4659f20fc50690dadf619424cb1274f5abc6b1f374c6b54e0d06492645aab59704b6ab97535bd282a320622ebbc3c1df2d9c185ed9e0814450a53bddb1101751b5e50e0620d6da3db3578fe63e35623df745f5c392cdf2cc51a75f26841632d8d3d663e48b0054fdd282503b2154728fa7ab29a2dd6072ca981a9bcd57643f64e89f78a61c08b1b798f18dce966ceb302b03ae05f00aa95256b68bfd8b6995e5758b85335c68a9dc4be945979c4c53a5c8ec86547b10a3bf632e2583b0fff165c139e2e84a028a2b4b6df6bd70ba7503c861cc616cacc784220008e51810691f4e869d77f083681a0165021b53c009743eef8266817caa4db06c070714030304073304176575b15f9ea6b3a093322217b35b4af28e8fd4082f884b94eb53273e17904db33a51f5def794023218962128ee226c0c4bc731caeb6218f7f65e032c755190641d0da6cce375aade6e663ca176a6ef20fd762d66b09468af0fa36a22127446f1922e21388d4367d861ea2be2e5055f37a34638571630202e1f2652a40f1b54cc9943a1a18fb2971cfbbe5a72013e916044bcb751abf4e9da71d31b3265a3506564c1bbbad44e856db1762543cb583c7096cd76b90d067888f143e1aa629f22b48044ea24ca9946b22b83d187e925f12d051a9b74a414e2ac39a4367227d500ef60ef064c0846b3a34e754c67b122000bcb91fabb9add6a8797e6873f9c2d724046c835a28daa8b49223e24639b392d6848599cc5d33fa08072059a3de75de31f3630d93c43060230a54aa295a5a1a0336e260155641d2b166505532bf9bc49ccee8c79686b73f25d38de8bbaaaceec5756eb846d5d3748b1ca2fafe434dcd4d145a304b432046f670fed853c8e8eab40232024da5ac034fa6f132c6d01be23313fba047fe55816ec46c63dce11caa24640e3b31044100de51c8c3021126f5bf2f3d0344fafb0039c6cf397a167a83d131feaaa8f17a2a1aa828f82e8161ee41da5e50ba7b90919679993eaa82279d91b0632ec36677a2a1ae206dd4fc8998477fecd4f01f28f33efabfc58a86201ea0586100f83855b7cadf43100ced27c34e43278d767476cf25d9be8d0a66b3fa472e7c205a99063b2e4effddcd2088748409259bcb385d27873605afc19e9dfcce3759eed132ff35f1b248518b69dc4f804bcf8c8bfd24ddd764900097c54f19893881e9938015193df37ef7eee35fd18b02db8355e9d6f051186168aa64e32deabebc22826a812027352ae04e81044f9acac51f9b98a7b111dfd3f6a6c4dcf93184918f03807de73c37a604f932f9a673253a3c6f109e6fe7949d5941bc9312f1cd03787aaf1ef8899ef7410a0169ea3c04e2d943428cf218f06e2fc3debbfe9d0979ed36460497296241246526b8afb3d9caf2a3636bf5330722a60c3574fbed903899f35e14b326747beb728e2f8eca4d397d418600e0e9b90cac4b187f78c66092603f2f29bf05846103da163b601b03afdf1888bf5c97e59749dbe14aaf20f58338688192171f951a8c0738f29f0f4557ec6c74650981673596e4fec38f982014ee08a9e1fdc75331e9b239f51600ea240cff82f8fde5ce48b83dae65be1fef39a96cbd14fed44f70ee5d169186bbb7254632463f14bb5d76609c27207a690d63b5880d6835718978a49ea7350fe3e3c80763e29701b6d5051b4d89da108ed45bf86549fe53623d2cf829f689d351c7e40beb911ecf06af09ffd1e17364c89fa05bfdc019797a64616c8df271f1d0a2bac8467af1b2cca06ebb911e46adccff2f56e795f53529050edb600859d7c10e3a34f6053f21725df61ca4845a4a971289e0cc599a6900ba311a0ff30cb202d0a355dfc87a6c3729d0c6aed9b07fac0a4209d9c0d7a94102e5a5b0f3c3d5f06a81877284aec030fbbf0560a9e11ecb74a4d95eebc899842b8da6ebaae7cc5d94e7f5e32f18a93044268682f03142de3b918430082a4bc2305d13ca387dcfd34f1a0eb74bc4cd380cd7db70c90e462c7add605e8b39643bbe288b65e0326afc82eafdda2fffe46339bcaab2bc4fc8b80971ac25809a600a72325fa9f2a61d4654a86150f6cb1475cc2275bf43ffa81b887f27f38022faff32bdc9298613e5e78fd3f25d128555ccbdfca7b6ad61047a66f6c93d94fdaeabb9df3453ebb35d8f0a7a99f3dcba970d4b96d683ceb7340c1be3fe7e4e150049cbad654883d9d4d22550462a35216e9b0d28dc000934b9f1ccd8eeb2e67e01759e4a5dd3f12be6d316aa8d7278d49fa19a984b9003f0b42bbe4a6471c630a461f55db964feed1908c89575510a95b7c8578a005812145c561c741e5d52f4dce4b5ac939911c35e755adf6fbc811c3c26ec8816c3aa7edfabb70c5282f74aaa5b4cbb2313ac8c9a9ba2eff000f1db7139af0d3bcd19495b405f656e9ab6a2eace62657fb132f767d8b33547ed41ac390011e9e5956c99017f10a7f3ff7932edb520ad95f5bbbc50285bb4fb740fb6cbe0487d7cd2a7f3b8925ffd56a1259845fe9db620257276f0c10ad9d29f49f7e67daa58909d0a09fc529affacd1c0e70f03c9b503a87c09b92d9c476dc05b322cb8738ad1e6e610e03094db78fba5395616591f511fae7e2d6844d771fca020d131242af708d7594e9e67c19bd5944629d2eb1363ee1ee87d4a3187e7d38d58d73a5ad11950162ddedeecbd5dc79f69e9084319e5a6231d81fdca09bc91a74a71a72252cea4c2e776f7f87cfb6deda43fb7b5671ee79f0e070750c16e7b405a61acc939047ee4794cfe3a0b2f3f9c607507610402cad8fd59b88a83a83d9ab6ee1889b15d09b240ceda5b04a700c955fc21406251d1fd01f8c804bb47b44b6eddd84028b8e0bfb746f20b55573fa7f0ec340a975e4ad378485fe727fe63126c11f6d756927b18d752a8852071b5f8bd7989e627262092e2c6e2935ed5eee77222fa8a5e64c5003103cafd6af12ec523173c08838a9984117d72830534db85d9fd5f2951e058910bc366e8cf9b08d4c6132cad7a004d5606f1da5042fbf6bbd791ed97f56ef81e9ed1403e99e6fc7f30761055a23aa6d341c2b37d7c52da7898f217f27267fbfcd3ea04d557663cf6dac724def660cb4a2fe79fbf1277959fb4f3cecd6ed3d76829e9ecdd23b54a7da82f755ee2e7f4f7677c3b02145cda0a835f7b98016b17700b98b5ac5a084066f115faebe6fc4b812ad10b707f2bd920cbc7595084264a28212978cc6f2e7b93640efd9cc6bf1bd971a7c238697fd1a1e062fdd08f970429e5853c41babb86afea130c363ca1d9802d96c057164b0da40559cabbf23e2e4dcb9258160bcb00d979ae6b7b2eb79e86bb255a607621c2e305b7c4d3a255013f49f278e5258765f31c38ca247bf52f3c0b25a4fa9a2e1034e8cf9ac4ec6604022c14e793f653bb83a0404242e41dd3d3b6475b07358f0512033bbc5dca0ac4df486d16630b2981c02f2526684d9de16696ff0f04a68d66b1c64418a028ca8a2d815034e7e53da6bd6a4d13afcd1f7e4f08b84e26fc1f488f2b3f3d9773225dd5317ddb0392b9b82fd8f890ad55de39f587bc594170cd3dc355c2a3d2629d8ef17ad481b88081da25358a9ee0427f34f2d1734561efb48131da8870613963c67aaf078e25b48dc8e6b43a72e21ab63e8b071d78d5fa90edc16b2967c943f74c256fb8e6acfa15350a48e14f2dc7598e03a781530ea993277fadf2552266b00eca91784e393cf33309e0d398709882a4b9126efc01f21cb14a597744822e0f762ed0838b2164114069ea4e08e680a430306dc8fc1c426f6a9f7838b3e6bd74284305c212f9094c665c1afee2baf481f4e94b42ebfd9be36d8f320b41bc4b7c81e69cefaeba9776d7c51b028ae87602caf4cf6d417cc085ccdc5c29c12563177242a4d1dae8cb2e24db05d3ad49279ab607ca5728410fbf258ae487d621d3940fc012cd3b9948f339e9e9193c8a34bbe7fd85d5a82bccfdccf53840b715618bca9abd097277f208802ad7819ec2553e2be938b9c0df9534c37d270815b85681e292b42faed5f1c0017efedcf47a0c59d12bc6d2ea6491e6a69dcb9c31f3d3e567f77cebc1b57761c60549727b20d8051b23d1ee48e9bea600dc4af9e84fd6d769c64232ea7e20274b9d120f5bf7f0b14d506eeb5aff2c3f4a46a53b2d065ceee746531c4db8e235690c9c65f814da6d54245af8a96d8be62153dfa11e77a750e0733d54e9a69835fb17b806a1cb08a9bdd8b779747eb58adbba087963c0b6a114f7b59525106e07d751eea4de2345cce8de38c26977d6318e93460c6c262d2784fe18e1847bc80d7d8a9f8c5b2e370c09da6aa8fd2c2233e0dd67963eb6770406e9effd71dcd2a5c5c18398754c58d67252f0b9f8418217138215d17d6ec23af174b9dac9facea4b06151996574b5f3253eb198a52f859b29d9570ca2fa12bf7e725c615059f88a3cd45d09d713afcf7fb273e6fe6af92c7c26e8f2a9d9f6847ac2dfd3bb8a012a2da59681b38463ca7e3f0e6a408b1f13a73425456530124e0fe038bbe62f668a341d6d826377ad499868c76a5a2f8bcfdd5d5e4cd9bec684a6d3036b02626d02a5c6aa70af4c4d76425e9eb5e3c01bcfb81342b7e2df02170b3f8251e8fe218005950158fc4604102319a6de43a617b0608100831dbf2c64f5d9cf2d67518023caba4b90f9662067295d295b8806d176d1b2ce7d52d9da8448c3a9aff70f6974e9b7650b0459355ab24da85cc2862127a0800da875f19733c71021f67868379d5ca572d2fc77511c569f1d0405b6430eaf23d849b9a9caa0514a7a5228e7dd174ba7b65fb6bdb53db132c513302b08f87cc39908b6909480e4dd3c6f6a5c72928d8552493319d2568d8fe239c41f49cd1ce0258651659cdcacef856af2f5df24a07cc754a0381e5ece39fcde5a447bf21024cc0462cc63e5c72ae74486bfe61bd4c40d0b1d2c34d3c21810a444416154c19dc2181dc1eb336734b9dbd770d4011e0652b2d2a112930e5e6614e7d33a9ca1bb983649cdad8fc0993fd09b3d7ad9feed3e3d63ef4eba63c76e4c01b18b9820451a323dbeeb4a17e34fffbb8dce58742ffdb160379a4ab0c7506d0162c9e9275d0c102e31989250ece21f9cdffa35b956f5178351fd5524b6516df8b55f07c7c391356c32080471f74d8110ab0604de7c5b00c44c813f785ffcf8302da0ccebb7053ccc37f0c9fdc59f0ed302940200ae732196d4a6ea10ac425b062d73ad6e7945cc23499ab7e11f16ead0709f8eb1ed0e5db1ae1b9268dbb1ecf22dec8025445fbe0f1f0330802148395d89e21981fe6c988893e39cc739fe72f917eb7106c720b496f2727a09db37d889600789f8a8a5517b8d1897cee077646e70248ca357403c7f4ef99bcee9865e1ceb0f695f3ac8e83ab87825dfc8d2b938e2848e3b910f9e8f65611dc01b082f11b071f5713c0e037ff184bad53ab4d388015e703807558dcb3f9347fd0fd6743c0b2f79e1ab8270ac20e37ed73ebcd016ba2ef48d03c997e48c94250af83df609044188d295f06c4e57a315cfba7f2f7183d1d7a93f7569eda0c6bb0cfc715dd382f5786e91798e26be80caa4ad4d5302994726b61d7bd8e3bf88e3d955f9a75f41a97eb83201d25e9a0927054b227d5061b37391db9a13c4bcf9a84c7549ed7508f33a2ab35a527b15c2b875aad38d58d7ea8a811acea1d597d625ef69937ed74d539f4931d005cb888b977e6e1825e96fedc1ffec92a85ddb6456685c0938895581b19efb9b8ac1ee7a0a06baf018325b80573fc110396529b99158edf88084996f8e3577d93d65bcaf84d27adf36519d2579f9ba03a7ae1ed7afed07054f8b9e37926d821f37be8f9c9467df8a008160b44906b50a690855a4898bd1ca0217c5e1262be50b6689450c755b286210cb7c11e3e4c08b1800cc77017793711e71d2644992284e76bf34fe023353e75801473c602078fb03809c5292129c8a5718c68bf8f84699cd7a161f6b106dcd0272a3c5d201229f7ef55afab50dd96dabadf6d3586980b7413a4346455c8f5d1eec02476c4bea1630c1401714e9bb1c64eadba48490d18676c82c8a335e6de072d0ff627b1032529479993613e4e6790cb3f178cf333638f9d6f2d3899345fed1906ab4ce1f8cac252dba0f14ebf7742c6238539c59712ff6e417232ea6c709aa0208f1cee2f1453a12f3ae05927c3d2e144620137c3c2a4e0a695514cd20dd4c8f188ff9e74743ae04e0df788b681b6a1aa9a182762a4ce04a89ab9bd81f782c1c36ee907238545a4eb906dd55e932e666de8e3c7e976244cd4b1bcefc4860f903aca783a6411b54e1df5907db08c17390afb020eef68a2c4561c16df4c87b02f5f4cba6036d4328f8c327e0d7abddcfb1205a48365632ee46f863d783214fba796bc4681061166b96904d23307596073fe01ea65a4fae12c4943685b89eec22099f048cef136737176182367c301279d2b1842062f9e21521a33048c1d76b177ccdbf8047d17421aa13d2194db20dfb0ce3a53dc6a2b9015c299a2f8da96b66c3bef9a2fc4dd25f174899ec1142d950c318a08e3502c3b2b6dc0df62ad1c423eb6de0928d95c84b13be13ad521fa8c6218d3e8871636cb5ab4d555af4858ce3648c044919555165edba69a7417288117b35c5b3d09289afc05b6e734a03d4147b5bd04c2422e629f27e033b38145c9de5131fc5f8d31286e78144194ba0542d3b06040e23a15d5708523783e7485a59b5295e809c0ec2cff9952a1a3407778d39306e28ffca029acc7b7b8cbb620e4b04ca3a9476c81370798347559697e69c171d3c9746e0e2ec88c2307977f856f83141127ee6fe387730c7d3807dcbb2ae4f32c29e81d08f0fda3860a00f2a566a6d2bf2ce6f587f1141aafe8377805853b0972463a9de19fe11269b0ac2754aa58055d4db0f4b215dffb1d7f7a4efa2569367a0b62218221c8c1e86c9322b69c426ca6bf481e1774510a7dddfa04dba791557fab8535da9bba33f1bbc370e613a57b5f87c63bc71045c7d471a94a3928e04b3b0b2c396b63fd0cc52faa9d7cf0d167d265d6a4e1955a0e68ae7b555aaae683eb6e3c89d555340cad34026d2e4b1f749e269fffa7f4338e41f15f626f2de3c298ef71bc0482a9fc40c2c45f6062114f48025d9f8bd48d2bc9904a95e6103cc06f8190210fd7fd0fd1f533c497aa93c0d118c72ec7f2e75fae257a4e53265f1a67fa80f97a920cb120a99f3c775d754c428063baf18ca55da5048eb00526253fda5b8ae69df24ffd399def19a398c842f043cfa48a180f58c8acb20619ec132d65a6275e1a0a32f36f866c0f74b81ba5bd70085e04fc8b16c1768ec5c391fc3f0cd1d67308d7d1d3470805696b9330a09d4be8d2e9c3c7309b93b0cbaf9a2b22631ec0aa19661eb98cd646fc10d3d0a932db1ee672290cece5f01357d1bc53eb22247ac6893e0ee28d49b22f5dd94af7711bffff505525cb8d093fda6bfaaef8edd2a4684d19968e6df1776b902fe1df1913404981eb94faa8d4dec1a0176a67b047a5e0f287381467d9180f138851bc027637b81082004e177b9cf87c42a7ca0ec01eb258a0249c0092a108b583e944b76f765604df80cb838ecf67f509dcfc608f3e05903e5cb57fa0406a0944b4241e4a4425bd912543715a2aaf587c07d5ac2e0d86a939340d0ce2dba802eeecbfa16159c24c83508c2b7de6b5e014f57a006a7273117acacbaf26396ad066d43e88c130671799f923104e8988c52c4574ecce38b9f15a5cfd727042b332a01546e26d62e651b41d8775741f52be3b87ed00816f325f121caabca393e174b0704da54bfa10f69971ab181cd34da7b54129cfe0f7a46c86cb04e5f37dd137c029eeab327ff376db14ace0a8c1b02c0b8e2232ff60f3dcc9649d59240ba4f7c0115f632dc87bdb8d61f59138228846fc758600c4765942414528b40771ec3b5590285153c0f325007544f499859291ee18d0b5996aaa984cb1adf2fda27d43342705b145a5e220bca47fecf987d1842375e98e42e6aa29b7040f369074e0f918689d30411ddd220be5689569088afa96f9238643b8ffb1f2a2b821d65344d063c1cc5498f7e996dfca0319df2e9b0a6c4aa21a4f275d951ac40799c8bfb647f448fada102bbd8bb1bfec4e3f1685224bbfd5eeb0309ef08a27bc91dcdc1c3a91929e8e2bcf238e9567311f0db3f34cc67be997fd639f5c4526b5665f80799a798509b37afcc3fbc1f99b9f921d446fc34840ec4e97256ddc5ea78cb339c3dc75eb05075d4112cb9860712186ecc5625a0a7d117eb6ed33396dd8d9e6df087b77bddcaef0971f9a07613e7deb4b54c0d24e7deef9c49fe9160920ab56211f61e251ca4f28f22ab3ef93d5ba829552ffde6b24fc3bba8e24273d636332663929397946e54fcb4eca41d511f708a25c1d7a67f9fa12efbb458c566f5d49176d38e4eb15e41241d5dee7e645de98ae19d001fecd6ad1e93490675c12452dabd3dc06e15939ac641bd4454e9c9dde4be38e4cc3efb29ddb670b9f20737636726ab67bf5ebc3452fd27d12dfc98eeeb2ddd8a3a51aacc8e1423f8314e521468ad006da256f430356ae6eadfedeb6c0c3f0e0b4dd6d1091ba1a38399b88474bb7de9d272bc42772f66fb7cfd0779bef789f1874756bde06253880a29c5afb0b359b40ff6c02ce7e8110ccb4dfa3cf44ac662ee26334d2a0781eee729b69db5c733434882eec9815d2c8ca252a103a36bb6caa1ceed78618bc6b45fca091067a80e068c628b2d4f615d420f12960d82902884aec9fa735e06176f1325b76a67496c04d2f47b2cc5502f14b689282aa0cf406cac309823ba17ccd9ff09ee223e143494d32b7c464a83ae8444fbfb92b31da20f0d246f593e81f831cad3a723743a51e17e9744cd442873a80d5335b4f7bbf3e0fe9f5423205b3938c19d0a08a343d255c239c01df59fba7f32a7f8596ac9664657d84fa2bd2744a19d2301dab6d8079bf5d9faec1e956aacf698af226fd1c2f018f2df22be10031cbd97bfa276a4977bf88f6295622b777cce64718c7ccc2e243680093e1a1fc23abfe412c7d9b0dfacae1c167fceda077ac5f2add68c3aaccca1c16761f719647ea730840bdf6aaef9ff1a3b611587ee1ae655fa3191f9194a415799670d69be3fbaa4ac0879145f94fb46a91bc7ee0e121b2bdf35e9fe04fa86a6ef8d7065bc327104a76d9d87187b8d4c985b6e2ea6a0b05ea1fde74376b13bae7dee2c9eb15d6f3ac5ea482ece2f6fa4385475720116b75ef2ebd60ac572b1554645bdcaa6ea3a11a97d1fca8011063b1136109d27aa3a3893c9701957f3bc85dcafcd694098ff2dd2218adbde2e11c8eb8ef850b6d98870de480739f9e60a59c1bbd53f8eb2f13d28436fd5b05fad05a0569ce6c80a03d9a332d626fd21095d24ce3900b89a8c318c9962e876291c0cf3ebf24d642fd3032fb0f28ae434d3512cae046569a90382aab6d78986bf26331d76db2e312ca2177558378fbb72dd6bd9e68cb9bcd30a46a738893f390ad8869500a6785d3d97f10ab478b0ae1521d058265f4110dc66e053e0f6365b8a161c9ba998f8c6c3e0e022795da79cc9a07182a3168b5c2c2242e5e3cee41bf2bcf1eeee2c43b53e1b415d29429158c9fc1d6e6321807208ada164fa28277494af7464234c6d75f079ce1c1add846f57904ccda5d782d971b4e47380771c2231ef8bfe81287c911ed60ef784476318e045260708f177ce1f8f5d114a4d060ee6841c5804cff0a0a412981468146aa49e28081bfc8c86f093a7fa2aa356fc843bcb97416412e10f787ab3715230d5991f0c592a81d9343a18f1697812a7be56daf9a5fe7e04cee01567b69c1d6ad8197cdd19069eb9f959c223c9fc948ad1fe073ac2ec96d5b3108b28d556e1effe812073d572a47121ada15159369f817bdaaa2f2a6be0ff866f28b1d61a162a65a1f32e2f32429470e72d2c213e477ec579e1bb1928ad148375983b9f934e0db359eb9642cb864f211442b4e24546d9e67496c2e32580a3af4503f10d421e841a4c00f1dda552dde00f8ef50486bb5094475b73df2efee6645cf8d88127121cf5669b17820fff65a6c132f52ed246d4b5554a72a3531dfe22a7b5894f3df402a3a6d018e5a08411b435467a5e8c065362e44cd985da587c9cc7404cd823e079cd6389a5f1458e6219c86a91f658e152da06d0ce2361b40c1c181d69d4e9db01531a0957210315ce4e157a746f24afb298b232835f7d36108278672a5741b305dd05201c86d4881ebaec1e98fca97d844f69109d004f97c03571c38b545a6587a1efc4095e80244ff9ea299759a33153f0a24b985d93dcc829d21e625cd4ad213e0023b3b52b9e374212d30c738374addba2d3b17a8e41d42107550cf9638e48709a4e9e70545c63342ec6e336646cfe7cb13512db7675a5aeb4b8b63d15724564926f20be06df3fc57594918d9dd2870b15a42cbd76aa03928e4a53d630f0ea2308023172c21c79aa684a2e163f224839fa05b210183c201856964ea6705c40331e27c6e33b647f06929b6de5c8eceb38b624310b471120189fb4ac8e6564846c2a0fd48a907c65b4538973027c529a8d19efe2f043ce7181e9b978a63af97ca5c0f42fab868876a862b29f4a14efcd29a2f0250a329ec3dd00c63d4916d3591895e816f18e56d861bb7536758c5564630873ade367421508950d01c79c971989c0f3ffdfe2f7b11963c867372c123541506fe2da525ce873af7ab624f65837815b56a798eac94c9568ad9509867a2253354a63b58b05c55849158bcc953d09a22ccebf9c7c74d43f672a03181ea862ba41dad460184654ac2ddcb7467e52696d50467bc5498769e2dbe7206861212a7148ed6910bf44ee06f615e4893a129d6925ecb021f7b5eee403322e802136447faf4875c609ea90fbede9b723f30d9b0cd4f51dd40b67105d67556cbfeb38bd859c01d8d9f3921d0bb8661bf2570cd1e283dd77012eca00e8c106701fe16a7ad047d2bdf8e4256807138ea34ad5d34531c3cd938a804300303f00dfbe4312bb52021e066d9f4329970a7f7099614974da2327e464d4157ff185c34ad349958e0b28e6c784581ca7cccfa9139e8c754aad53ce789510aa6f35cc582852aff4fcd3e4b512a7bfb5e93ff1c267524a3750fdfbbbd52c83d604348cada700ccd83b3fa4c3bf2eddb0e54739aa5bea2fceac4dd4c58243719e5d694484928a9d030ffb9bccf3a4f011730f8e26428ab806b83b8a67e6591521541410f2d6430aa904b5d1825a98e3ff69f14121a80519cd857ad762b0df0ae9d39f7702cedf416ea034fc2a352ad0661a3c85e1f73c9470c611f56b9441a899c57086ed9bfa295bb207865be084d96a24f6c383b9badfd627050d21f20062c752d8545d4aae67da3a6a68ff8bc25209fc38ce00f6b7401f221c0e827d219ea85e35c09189131a36aef4dd0f4f3af59a8fe1914dfbdc3a26c7c9fe7d3dd8ca8164cb612ce9ca9f37f0071388626b75e0437c1394d1816d5580d381b62912d49ac9552b38efb2ae325f67c5a38fd960046cbaf7e786d54c2e781b7410c35872fd801df31230b0ca736650d0a9a078d3e9241f799c5a0668a39f7246845602924491bfe621254fb6089f663caf99c1fc71e2c5da29a6d1fe0137610c83ddabcaae45789f4e38ade0f0768a8d079da0bc41907069883771d94e83f1e6233255a53557208c5b4b99de88c551baff55fc273937fb10dda1f96e0c5c1b99411bc9226d1e73eacb494a2b1845886c2c6ce67b8a82abe5b8fd0c803e5d6bea79086fd00b10a2a38b140d386e78c2949b7e9b517478b012f9497bb2e45e75b2025eb26432c5045f9a7227fcdf887377589f81024d6747c2d459222684273db920e0be935a1d6b1562c1c5813a3ab0d079f639b514659b0eecfd731c5c3513d1cfbbd57aa6a482acb191cad1868e90f5384b5137ce1515f9618a66baed6973dbf9587caaf4bd9981e29382ec5d5634e904608207102a6bd26af02706c2f02f8ce4e945af3b3bc5c8a972a940501372caad1b6563092c16b0086ff0f38a5d24a1795c3930aa2c8d81626c8b22629d0453e2c37f8eb612f88d3a2269f14bff3df627629032e5925b4b274d42442af0360b93d3486203152599ec11788b84f973976c139f2b361ae6ef7068e86f9484debdcf5b369a02e138b7bc2eacf408824594ecc3599e1b13e5e1f6632c5438ab74e881e499cf1ad60e7d9df14ace3b49d501b8fd6fc23329ce26bd388b85a0964088dbbf5180826bfd96c0bdcaedfe26abce19dc3e60892e274659a5680980014b39675ee715b40dc45722a3b2673be53b3a21141b291e55548e7696b5a8ea458f3d42276c03a766889b4c6924615cab5d81662d4b137ad4adb5951144aee51356b3ac9aa855ae72c01b88aa038d10128ca41a42842d05d0a8e57d9a00f05f7cc6335a374f0d30aa2387cf49fc01d60f35f786b8224b7938c00e37d14196a2c7446e343afa915ed1143381371b797ae134a4c74c636f52639ab3edb45be6620a2a9427ee4ca057be7650c9ab04131fe2f2da3668466a6e10bc4c43805d77bdde0d4c2397dc46e2264380c01b097f98e0defe5a74ad9cfdb3f4ce67a4c11e4ecf47a2f631d486891a59e741301a41a97ba929e2813a34c6614f85ec803022e2bbf10e3306f8ce551ababe5505ee65387c19aebf2e737d92bbd2d9426430badb7048a6329c099e846e68dbacc2ddc9be615798ca09191b02265182ebc2f2999fd42ce803a1fc7e6fb70641281785d70e4ef88d34ccdda5c29dab15784084bf92d2053b90cb001c351cf8969db219ba42528653623381b81a644924a62f13f0d8441008330e6d8da43679a3f1ec4b6085c334c5742831236fc9a0982cb2c11d08b1d391d82b8bb58cc664a877d15f49902e92d0f8eb216c9e94975049ef4aee336439a48c2eca7f67f8485bb06126ed37c48c92be3b4642fe17df94d244bc0d2bba9d6737f581d85094586d9ed192ab4ab412ec444c348e272fef8f75274370bf680d40bb2aa1a363db5e951469cfb71efd4ef13ee1588642c40dc1102846eb770edb4243b373188fe47af3214f004df5c072a970e5d47066e85996f0b58a3bc82772a28014359ed98cdc0a690734c1512e07c3931cf78c85a8dc42ff6557ef5fe66441beb6291ed6419fd73a4afa2c1c77cc890e5e3c68f3c32155beb4673f17902b05ef0df3f004eb90f7c96dc16bddbb442cf4fa8eea99a07545b95e63b7593e53fc22e7a1ebd80ee49823d57befa41c6031a6b868813976fbee217f3c0993c3e03b7679394166e43dbff4c393441de053bc0148dfeeeb78a33f1e97c77004b7fdcdec2942ec298c51b9e6e692d9142cb94b37013a2f1be2b180dff6c387bcc9ff37e172952531a4e4f91330a6434d662a2982a8900e70495ed68c74b8b85d5f93c666f8bd00174b956373904b147c0685feeaa5e0f56b28abdc23595e08149b0970277551dcd0949980a4de2d79ea201b18ca0dcb811ed735210b138eeb4bbacbfbed85741cad739d6e452796dd475f2cb9393de8bda6af1b7d59d671a3500ba14f91c7760be817cf8f993df970fd19c3fa16e975207102c4a422e4c985243698992c72666ab7e7c8673e640c612b4a5c6e9206d757afa0aa9b409466ff83d21dadf7dcddeb6cd4ac283f617523482225be93a8da2177f9070cb49e267dec08e910eec10989dbf53caaa3f508c42a13b52ed9c43cec9b3d43cc753b12686e8697e976592e2e2699bc2a672939be779092b4f74d192097c80bdf7436785338499dae45fc34e37b6891c0ac49ded3c87f61bf27d5bc393d523cf15be4c247a296662a3d38c80a53184db1dcc47e17c5f59860d2e0379804c9b372961cea8777c867a951a5df0f04a983215e2e3ed089a2417269fe93791c4fe905a4ef6e285dec3862cacc8553e05cfe9e7ade840958f5e8c9ee0e7989c6a4f8888914fecab827f14e4162031bccd5f052ece1ae80ad1d9060b1d772ad775b9f285da946ce8f4fdad74a41774e45df5d241df906ccfbf387e43bf0df54d6bee69e04e734dd65737ceab34e35ddcaa90b2cdb3f494605ed3f52ce2df15cbc9940ee88a9e587adba1c255a19cee56f5cda04958a17e8520405ecd44008b2db946ac4a56b0840f008855694c6bc0393c5c6f406bd63e726ba62f5e8bc937b28fb13fbc7c949d858dde754cb25734d40609dc8ad953f9d331443b9982fa2a6e6569941ecc044bca10f3ffca38c510536f2d423bfef295757c20131704afd09fde4ad17f472b6791167d56a9b2e7e9311466871daf9747d377f7614da42cca6c34be2aa3210b9de4426d21ec5015ab86f394aee4c30ec3110f05ed02fc61913a9decf67f773969b2ef8cdaa745604c2b4f46494e0ce7c0c1549604d8fd87243f758b969baa2fcfcea2c61522924bede926a1a7157d4fa8c2ab5c92d8232810adea9c5827e4a48e0d3d45c96a0a31b822439b68baba8b7aff27282f1f8aaf98ea5c2572e4977b6de4b22c0400e091f3ac825723e246913f7c0bfc38455fca663ae013afd3a89a1ebc6d2fed703d7be8348fb433c0fd4ad492cc15e7ed63a1c9247ad7982615b5abc294ce2e3fa89dfe5d21a60bc23b00c04560b200b22e6721000e6029313ddc893a82ff4a56b3d63128dbbd1f70ed97fe8aeb15e049793568624f899fb5fc3f19b90e43e3a63d77dbac4e2a679db9e4e62f676096c60fc74c4a0ba95ee587043ff58921b1513b48739d5a084b41c3fa511ba3315472ead26d4a598db5ec254c1c56f891041ed686be6db19505e1642123e1c543aa1ccb0a3072336279722373566ae8b102dd9b3b946a3510fa62f3b73c187c491cceb19119595d2aa0abf2ae8364cb102d19fa65580cdd84038edcb12127b6d1d0e373e8e25825da46c4c9cc4072a338b05ba0651823c6202dde7cc2a7f4f631bf716d2b08ee576188ee5a7cf0d3579308a8df7f95227cf430eec510ccfa7edc53ed73e39a360dd06f64905cf120b666927b2dfcef46f58fec98cd3bc31857e61d54862c4a356b95613a8fcd1f5f44dfc8d4118f966a52a67e88c9f64b09019949fa81b5ada2a8868f8a9daa804a9a5474a90014ea6a826cb1642dc2a8f14964b37aa463124950a482afe7a6471ceb9a58594f9929e241c99474bd4ccc70aee7392a842ed51f7f37323b255a4dad7d08cabe84c75a9e61514e33a3a538df92457c1276814fd93400d8f9995ab76841bcec0620d2dbad3cba94f127bbe2216397f8558eb06a3c419f316697df7e950942212fa730204294be3102d310a974f8abb0c1cd947d0809885af40c23d391bb5e282b8968f6e36195480ab9ecb3c07a2ec7f3a3d2f940c4f9d0dfa2092ecc3cdc57dada4a6c298ee1fbd7e3245c17a69c8a2302e921723b126d4f70f221cf2d8c1a86151cb05cc01583abeb3ed2cc0b395308ed182edeb497a1053a4cdac7014d94f21b0e770347319778abfc63fb753db5bb8b389a7e293bfe740bcbc7569afda83cc7b79bc64168166fa35cb467d613b6db5247704c82dfaf954f01180fbf4bc0bcdc05f26546b71834f53d07ad9f172e4013908b41651d9a6b5c4d3cd5e1bcec0c0e3bd20a6cf9e63eebde18038881a0370b8edf519c63e1ab21e5624b0b8e556572f1194ec20ee5e1c11b8d6e04cd0813d0bdb517bccd3b134519cd79f208959d7ec5309a5b5014c0afa8a0e5aa44814d49028823fa133822a543ada274bb86fba1083b5be33a3ad39f119fd749bb94b4e45420a09e0e5c6d035480362d08008ec6f34ea2bea8eb8292a1ee822e0ffb6350350a16b723d7cdb556ab642e54e2b1690570a0c8585c46a4a70a43696290cab22bbec8fd8cdf49ce880a0a93375eb4566918e8c6253baeda5e1e211c0c1eb69161374b3ee9fc6b5fe965bd405e1a5f863b889ba34bbd495d226236d54347495bb27c44c8e6a6fea6d06ff3088e6bb29a574f8124dd21645e3e1d7ed9c01daa439131edaf66328c4dacfb69cf63851983f4a53db211d53c19d46fa1797968b1c6d6cf6c376ef07b8d1f6211434f030a8943eaaa15dfb6ff6ba1493b116a5e8d07f1f38e28a74157c876a6a0c43da0d121fab647253791209eea5717f9666e4019b7a0c9cdfa31e36a057d05d32401b4d81ecaba6fbca116f97e71c084da716a0ce811abf06581828590842713ad76cf55c37a49441371844e26e888e83064cde96f51b097b49eccd678b96b03ad65106cc9240c3cd69039f71a883b2516cbe56c10a89d8e30d7090b22b5fa0a8fb1387e36ed8dbedbce91044dc99630745a77544a3ffd8600cd51da1d27c25587c31d23acca9fc66a998048d60b41fef82b0b040a9bd7101f9b81019d0d90632178e0f872ec26dcc71604c58c12ec6859b68e2009a9d380fb76950b7f1d03921531eaa99992867b2ead6044de1b57417fcd86703a7cfc807df2f307219a17b41bdee733dd8971eef9bdeed9bdeee433deffef8d50745a4ce28d1d21fd2a17951020392b7d529303ca7bb73333cbe3849e48b46ad7baef89b8e0dff6def2df97fc4bbb43d1827e529739e21022e6d43e2479bdbeae6a6d8af073775b76a429779e2f1b69b39e4cc6dbf5bfb89b6453f587917b289b0135b5f94d91821bff8877bec595dd049487a124a5aba082d4ab895760ad6d97ef16e2987d919997b087623a617a6d91fe8991531e691bf0a35c18edf81704c28ee854b4966bc74eb63f84b53ea9e12df6d27f04f3bbbc76eb313916329ab355f2f90e496bab81d8a132b65bde140d94eedee85ca48e6a87a54f75315b5d654455184dd61891b8de6acea09aa0e48f2ed2d154159f8f5c8402beb1990a992f2236d6c777c9bba8d37db1474af9f98f1562a2510680a7cffdfc608daf4214dff9dd80f49081f7b15390a0469d566991a2c1723194ad6a022e675d61483cdcb99318728ff915f3372e5df0e1581eec0a990539cccbc15bf4c90e8b99a1727e27a3887858bc03d7336ee892fef132459d4fceb78d6e03199589ad9a388892d6276958838259f66abb6924b0b3797db0b7ef8d1dead285021e4cc3a3c47b7bee3918657d441ba091a7b82fe3d5c4990e2fab0aa274738a63861cac5f546262ac3f4fe03d4415ccfe9714bb2ca6f178a4219a6a5476ebf8c7e1326a67636ad100dc40bf924e4ce721a7b89646d7dd268714eff1d584c58dda4f9b098b48f9e3ada04e741c126ea87dcf753a6cb12b7734514a78458dcbf2b56c0c52f501ffac4aa543fa838dbc22ede64d77b2c4d134369cbed8c0eefeeab98ea0abbc8948bf988066a4efc7c7d1c12352b2df90d1979cdf4bf554ab0d46e994e0bf6e0f29d246c0c95b6687ad146865422f4126a7b70581b8b1df6c269c48f47d4253ebdb6ff916d02603f2bb2c084a9ef7e41c538da4d29461a2b500815f918a09100ddc664cea1fe533c196bea9e25927079cd543af1bffae005b26f789974d94cc200da0bb3465c27f7a9122fdef61b876d7af874f2f43117493ba3bceb85a11d64e29edbfc0c989e5415469081c79ab827a621832775abea3d5b67c98adca450d543a75e1fc51cecc8ad5b2abc5ac646448e456fc8195111e66a08aa202c6b0eb535990817762406862652c412a10b27d23aa1b823c4f6893b1bdb374177a0f87df363fbae7b8aed23964263fb575cb3bf1b376a23da2a2a60ad2947aa6d1ff0ca6e5ed690eb633c886c635ba83e33d75511d9679c23a3430f747c39716d1cff8a6283af9ed12e7f887ee89f4c74664c866bfcd00f4a6dae80d36f3af76cf22f9436dc848cbe149346ba55046f910248226f58b91a4f15739255d14463e38890f8ae2a85010d21a5f4fa3d7b77c272be327fd397ab6930e33437e31f987518ac1abeaf839818c83d6da707d1daa9316bc19e60f8236f7f06ec6146ab6c25e463f7620ce2e1b33b8c4c0ea22131082ea542174a6916fd844892084171a62521d606616e8940240c9d46c1808a4fcad98167c4159afbbe805a3b7e73cbbd14e31d133f37ece9d2527e1627789208bf7b8dd265e1947d7722b7287279b247b3e49ef0f056b8e1d156afefb4898e46660bc2b47714b0ac311c561d703020225ba67e091add6f67c585cb739ef84330ed8ff341465ceb213154f4bbb74c2fdd108b5a61f0f14b28d8b7f7ab19d63abe9b1983687ffddf88d65fdad112f0da81e2a0805ae8c329d874e86c22a6e8a2e6bbcf68fa9bd7a1c3c2935a3ceab2fb91f5fd5c0396da3c69a79d5d828bc20344672bd155d491553b72c79befe5c60551ccce079a3244132092c5d77277b25527a8841da24ac98ebeec5fa2b9e1d4f10dbf1f158c2217e40eedfc8ee7d1e5a14b5a42f4ddb20f7f33a61109fc84815ad4922afe260b6d9d595f6ff77a04127598c871cc40327b2e1956c0547df1ded18050ec30850ee6f802aedb1555945c7db185524d8e9a5fd9f468b293455e822fcf32b012574121ced80aa350854625fe20d526c3624a3c5eef22905828d39a4d7bd3d7fa9afdaeee7d24c2e4bbe36308baf53be0a749a6b78dfc82c2d45c8b3140219b614e572ab09d6e2e6bfa14f567eece948827e3b1a779948900708a9e7a585432843e4dcfcffbc619bb29d7f717cf8669c7043b00afbf07f7d617ef38bc87c1cef7d658916c8d1ae0939cf73911690273a97c0dfc9526fa1a577fe39380fefbf4a239ca10ea76599e7265aa831a640372eda7258b0e1b446a17566e07a564cf8344270410731f12b7be22fa5782bbd8faa34070de05eeb53f80523b6f489caa93852b5726e10b7cff6fd057440219925450377faf6d84b6c416ad5c31401b3503ea6603d76a0ba754a38b85f437b690932a24f31140cabdfad6231a0399b5a6a2011a6f6c50d71a768a967671a734aee22391c3667433fe781bbdbb9ebad9e0dc07e7b6360894cb248801bb8ae0786f2ec1b8e7774346ad3b8461c7f241a051e9142dd40afc557c104174f6d65f82473f29a4e98cfc94819a072ff594ca3328c2e45d895a30ad3fbd936a0d6ab03e1471303f4e3080b2298769b5c838302f378a0c5f6560f0ec4010348a4c2a54df566b1761d17565c3ebf55d49945351a6ec343eb0481f1f79ce1b096d8b3e98f6bc8f8a3e34a2b442f37ed140b204f431a6c4fc455149114a326114df56e90b9f30d2f773905ce5789236a43b95f2235fa985cff9474898a04c493fa5da3adcee05d5b2ae8c55afa5996311557ce452a777d7c5543b619819788f7f1aa9e2fce0513f03b3e5f7dc47c11caab7cbca159c631c654bac08e965e9166611cc504714fcbbc38b894f09326b0619de044158dfc9fa33c776ecf2ee8ba5824518ea2398e13a1cbbf2b45e4bdb9fc604bce375127d5993567d9613c5c29a6ea9f9d0e50616cb39c165304c4e6e949a75abd2a09d113c33a427ae485ea972d65cf96e9359c17a9abf627b202828c835fbfc824809061788962b8f07ed810a7c7f4e7aa09075032bbbd7b46748a9dd901623d6685795c6a18bba1e9f96fd2fdb97dd6491db340cfdd525d4c9accea51ad61882eb5f3470bbad39efceb76e18ddbc872591265816e3f380bcf0e3998d9ff9fa7f74404c39db821de9ff986f0ad6863909c73bfdc8f829ad1ec4016bd430d09044f93a680e23fdd230e7da599993ea5ee6830ead6c47bf05edc83ba83cc163117a60b5845d85daeed0f1a2b4ae808e46a311baa5416b83d0dc884df29e5865f6b3afff83df2462ca75f5e58bbb642216239ea6ed352992931fcd65250479bcbbe002f793c26035515e4ba6b6ba00d1fce6be96872d71c5f279e40b451649498e30477bd7dfdce3a6d51b1ee65a25e40b5f2b86581c5db8b8cb4d15dce6f42acd0026d727674fb59020547a83666f8b75a082263e7f1f2e94899b6d7100691cf91d63ac29748b45d27533c5d4b5f1b0dbdeaab8765d35388be7de60751ec52b520ed0c1debaeef2e582aa8e1646e6bcf9d2ad8595d27654ba2f8c9a4c3b87e6f933f6721d29f3c20bf690eb95060b298cef9d5be1eda6496f1bca136b796332486e60e2dde906a2f1be1d352c2664779b2617bf0110219b2407c031bab83f2bad52982725a493bd97bda408345d153039b9a4df2f723408994274e1f1235a129725acf5db9d30b9a4e80071ff623d79cd3c9288425ee8e11ce59fcd1a09c5c00d218d799cdae38872d56082f40bc55c44031645509648561e3587d3a7ce2a58875b5065660dbf1d431260f8b3b4c2f9fa032fe8db5e41dee8c355f9d859fa10ce3223af5cf50ea4606b4f14196c3d5a8a805324541d301e96c420d724785635549706b77543b593654a69ca0a0b36d14fba7f0fdaeb1e5181e0cf58f56df4255024796920a5ae3b569f09884f5abf87c117af0c2996d92601a41f19518a3e12f9307125960015b9e9f4f40a6a588f5fe16f48220c54dd28fa08f4eec97dca7289d2a933ca327c8200cbd915ca30eb5baf34247f976c263a009ffc3ca1e72ad3671d614b8f72aa03b39e650643f00b768394611b0b22cbc61d50d46a23c1cfa0bbcbbecc21b24c43299013c045c446efe312e8f7d284b5be020d3b1a6b74ed427f6a669f72c8c9d01c3c0949d56c7c04a34f8743a4e06ed8c9f58918ab8ca458df5ad47db51ac6743bb5d425114d8f07dd070d165c453aef434bfc6f2a744d44df73c0603bf1fea082141017593f08ed978c8ce6529ccef9c5888b2a8945a7b326f6e523865daba62c8194d117dc58e19fde0d8da17930006e6b64f7d374321ab32603c8803a2db8b2757e9cae074e1317628e63175ffff298f846fb563cc7f6bf1c6e627a8086463ffa8ec63237915605f9bdf470c99b7aaa19eca078677839c5c7848f9e4797db86df89684b9d1a590d6cf0f128e5ca3a02f7112604dcd20d4b98855300c0c5c5cd77db9f8bffb7b89e99d2eff434de3cc10522b4a459fe185a639883fca069e34acb47a1de02c809c52c5d024859b053508fdd18e13037457b26fe080acd6f725bfee83c71a6d4cd19450f3062187ad18c72ba8c4f8665a2c6a2a0af94cb6c9ae1cb44a0847abae3ad8c8d461ba8245e9b46353881fdd825b2945391991aa7766208ada188051d967f4db909e235ff675392bbc90188a252e65ecaa23ce2dceee8fa74b3984fc2dfa26e880dfe5901d62d983d9f3fedffc88773ed999ea0e4a06da674aa6a790efff09e6682cc351a4ab3785f0a65fc06052a649476528d3e198f325feb3eefa7db1a48ed779eb0858ae95279c29b0ea494e7d0312e22b431d98b1f99d4172b112f9edf5af9936fa7886eda4657d21c5735888308f0e371a86c7dbd97103a79b20340d840780d3a44b288a549be4ac30072117a46f0f4aa42a18070a1d18ccb89813f6e09c97649b4458fb0540bc6de0b57703496a9c5b1c88570b87c9cbd5d6a454dcd050fc28d1940c7cce34255b54118eafca875220466309bcdbad2fbe2f2bf5165f5b812b8a3cefb87293d764870990fc470c3aeb352aaa27ff00115bc884a41f28cefc401d99e42e76ed22b98695eac06498f0ff38efc442db5278e52e5fc5bd828bab30da5c8242a79417ab3d6be9adea788ebebfbc67909dcd70d393d16c04d97553012e43b5ba942f33317098636ac255fe7751de53d1fa0d140cab2a2eee93b0d8ad6be771a483ca6dfa284ee434fb39949958d7e207e9a111752d2c57f80bd6ed54ef5c257a9cf3c95d32ddc6fa5c6a4c38b3d9ce70c2db38b7e0a286725a3a3bda4996111ec54fdb738437fd192a1f0fe848a3d19e95c23289f6b0fd1cc22cf5092331979f08b3cf4cc6d8cb66d296d69c697f65bce4495705b79781ab081a889ae4d8eab6eb3edb414cf8d6dfe7b385684f68fed7b08a1c272724a150e695d866ab5ff09eefd4515c0356956990bf00702f7ea0c57a8469e4dd687b21787d9d542bdf8ded9ca1d53ff9692ee50ff65094bcecc98d88bd24e63400e60f0591b5b979b1dfdc8a4973e12db8888f5bcb1ba49434c3cbf0a8c09553fd8716de5264f91db6110bef9830632c7679e55a02005ad692eb463cee93bd79e32ec8819b99387105d23f822dc4f1cd77781cf4e3566c28ee33b74b22d93c389e255dff94765f8d633d4013d9584a4597affb173d38ffb7e9c9920e3107a7878a8b96d719ae7a102304229332f8b1dcc5fc3d5a4225994aa54ed7ab657138cbeadda4c13067666d429d04fc46985ccfa3b256813d20ed1541d02481972217dc592fd1c3ef6deb1753afda3d5553e09348f93d0f92d53b2c879845b6ef9a0d458cdabec0bdcd24af62d3012b7fbb508c46a23fecadedfa72c86615559f8986955193002d8a2c12fc95113d8fbe879e3329b1e5275903b9eb99acdc733c95efdb825dab3bf8c0f011ba91eb67ee22b938ba44e8b4e6844c79ff0047600497cab91879cf0ba0241451f1a5a25f94e0112e5bfe99448e14c5e97f9cf1bba6844e568b14244f855e112c8f599d002ba4ba458adf4972bdcfa2e3619985d8cae04fa7d1265108b39fd30a6148924561b97b669d3112e002246965e060721fd1839b0c6a327adb2b5eb823413d698dac7fb6bcadc0e379204d2c8a1a8e303d53565b6a60b546d82ec615a931611b65236f8dc80c4ee11eb79b475e7ae02945204fa9283b6542020554d073820c327868610af10bcb17c04b0391a4dc24bf32c619571948e2569e58fe0243f0335ff7862c58d0878835ecad2476edbbaac25139befd5e8f87cbb330c74a8a814d48424d97befbdf796524a2965ea0a5c0bf10bd9c7ed4f9ad4a341df303069b07fdcfebca6e2376d2d2861dffb3567b65ccf67987bebae73378fdc6cb1e0c518638c31c618638c31ae434bc05ea7df919b768ee0127e1ddfbbe16d734c80f963b38d5467e147e79b2b34f7e59f5db1f55c6cfa422ae951f64aa4991dfaa7475d177a472959c9f00b1df49ceb5c7cec99ebb899098ea5e761883d3aa29a669d4a569a7130c94a4b94446d4e7d655a2571305ff9083327d3f211c2d03731f4effb21c8fec61e98a6833a010a4ed8d8a9e7853eb32b98161686996661a531eb58d7acbe72cf0ae62b0b6192d429a5630907dace766973beb4b40dfaa77f9ba3e9ca34ebea11c27cd949d274f2b6f3355fe18b7af7815ca651da1d6ddd74979ee36d8ef4ae35e684517d521a73ca94259bce9813a64ce14dd43fd29b8e73b6804e43c7433308c398486948cad125b9c39e4d41c7e1f9e6df633f9528dfb6504725421330d8d86b18a339ac24246c7518fee8e327ab0b83978a4197aa6d6956110358e9a7aa2f95a9e2659860bd9aaa9015c6aa2e8b43b5062ee84e895b6bbd74da4ae9156dad980a1fa74734094daf5b6badb5d65a70bc36c8a5150cf682652083f5edf3ba6f4f1f9a51b02a56d9130fdd21ebaab04b645d38b27f97c8c2708f7501dd210b1b11a99c6a7487ae35a23f2febe824cc96ea5faec18848ca4f8ea281a61be527948ac5727d519c736f8dfe2afe9c8b1ff79c831fa26aa81c99447e30f3a2f088ecaaa3e4a2735f72d2973e36891b4d4c3bb473a2836369621a6ff4382bc9893eb78f0d3aa924f9cabf0c821ffaf7813b44e776949ce49c1e4d4ce34d6924710e72cff9e8b4e2a51517555e2aad784d154b57b5b2e2a59187cadde2f8e3ee9550c54f28e75c857363398eb392efb7920f02018e25ddeede72ff7204bb057eab257a486a3907bae8a1b75c454b248da58a168a73906b8d28ae827b1450ab50e1a19f702177e25ca681c9e93639388e9c8e263728071de5e0587a5bf3d0ce32a19ca59d857216cbb58f0d967cd691441a4b6e8760e82c5f63c3f2c7dd2c5fb96832390d75a09cc51a4beda60ab2bed45ed62dfa67775893834ff2f04bbd43278965e8266e7a1eda4b4a6eba01475dc951a934f2d08ef2528f2c3f798d3d79bdfad2c73ef9288ca95011ed1f76a3bca6de946c947f3987b1948f888c8aa0bca6a67e345432aa953807fac9bfd38f8ccc40a37c3454c9ea2819ea6b6c4d055fef920fb2b1ccc2b0cf68686464541b4f9caceb343722525f737b51c8409718a8c449a038099306a44008011e9621ef2b8160d785fe7dfe758f67ba0b7b6040fcc8b1c67625a79dd7d48f2b8db7ebc61f57878d3bdce10e9d83e38f11c8dd77e3d007f6e18ce81c030c545f9c17e411a9af0c03fd39177a682a017784748272f08c326126739ddf0d749fb9532acdd5e0e020f16233443b8e092c12baf3a80caf881f7a93bed0c511184644541bf4214574fe43cfd825fd3cb3171b96faa95855d003595ec4e00a64f9fa5487dde60e7ea647eddb0a6f3018e2e9c225a62fd91ede681c82cfc31b9c029afef42aa0e94de30a68fad2de3eb0af9de4b522d8d7b73125bcb1fbfac7029a9e845556807a9334b0d275dbca0a099a7eb4c76e92780c2749626161096fae0b34bdf885e2c974e115e18daf086f340bd0f42199c99c3732674ab6004d0feebce3f2bc9820f675927b0f335bb0c73ecf3b302477b95c2e40d37f9bc5f7a14a1661ec2be101725bc762db586c1b65416966d1029adedbf649b305bbf7a5e9829dbe6aba60e77e355bb08fa7cda40225f0f31694e6aec5d622bcb9a6ef362997f0d01df8a18bec22e7cd45785377ced4850b083e6992d8bf2f9924f6f0554c12bbf82793c4dd4f17c6303ba60bf6edbb4962ef3e9c247692065ac2ed032d38042de1cd7d014dbf5911f010dec080a6590237729cbdd8b08c74ba30c92506343dde9987c5e9f3905e6271f0799e67a66d48dff4f3e020a16d587cf662439a86f09e345db0932c2fe14d2569e025bcd1d4f477c3509a615e80a6b79bf440f4ce8e738cc638312caba40ccfea0be36f34943dfc78867130e6fc1b47fb6ec0fb2fbce1587620bf375b7cb811c8fe7cf36f8b09635dc6b3ec26379d5fef4e77cb74e395096f5090e9e9e61e88de74f3d203d1bb730fcf497ae3ac2fc20bf4026dbdbc40bbaa30809a2ed8618437757bfebd26459525b305fb8b3096fd45f895f0d89744ba61c8817dbbce03fbe23c43405fc028814101717394181d0db98f0648800a1b0d555775315319259fce81a6227e705b77decb84b1f8259a2e350545d9c267866bcac03dd4a5ba8f75556f4484ba0011a1d448755507b922bb7ac518771be71cc63e6e678c7f03fae63649ea9b0b75d2db39324d92a626ee696d7a3d212216fcf2ce1d37cc71618eba6ddea67bb58fcf85a2923f3ff772f55ef0bd5a258d0ccd967c3bcf0c44d05084b3e2e35d5d6edd96a4235f7bad7b767bd6bf3047ad1b744a49d014ba98776c7bf30b72e2870fe60c825f681d343d100e480ff6e921e51cfd5ecdc86c01ead230f38eb706bc432ebbfeb96968802f16863364e438a423e1a88d0b459f1e89c55fd74d3efd06007ea82fedd385183dcf8a3b49eaa4e726499de5c1e9a28e9f35497a9a2e4a9d8eb79246887f27d9e123c26eb336678b6f18c3ceb3c23ff24d50235b31de1debba3a57e7d26e4fcf9cdc04a79ea4f5fca7a99aa475d2b326693ddb0c3edd194f7273eaf81bcb10ececd64b5bf60d34457171a73dc73f5af4ef5a22452ae909edea1e10f9d326bb2026b2bc07e40555b2057a56d223e1ec527768bfeeef6766810eb29b0c0fc803f280807ec8a9ca3bd3c53361a45727472cb32c5ee6246cd2cb4c845dc7695d322a4446319e2bc0d65615e812fbf880584665d05d7109280cfb500b63f18a9db0abe3255018cb3822e2af3b229247447216b3cfa0016a0bc319d2f9694f95ab742c7f65454537c7ba66959c9d63b74ec39bedeb72edb6bb7ddd66f7f6f56e9aebb534a8a535eaa550d0ddbdf7fac67db3ce0ff44f06f49e6705b782f48fe5a9cd8ab7424141b636344462f9694312bdd00341d0ab3c70e919ddeee6786b6def414b64893e2ad20103f1ecd2dbc2ae40958ca2b5b9512caa53a86229821b24fcc6b912c6b63cd39df59cb1c5750c73fcfd6e7e01593ef4a1d1807e7ef06579d006dbfaa25fe63eab6373ac2ba76767e7b713dc9cf1e6799d5328e8cf3f1990e53dfc3ca47fe4d7229e4a44646756663d74ee56920bdd726e1d0c2d67bfce3d6feb36ec395f7c3796ef368ee5390ea5a394e4a1e821c9bdf0066fd1bf6fbea8a2b3fc6633eb2b0c49a317c6c4f156321c27cb678bef38c39bbb2fcb111cddbe63ad0d415367f93961bc16f85541977987e5b18dc78d35c8baaa1d79587c863c15a80e5997101355087b405e14f50b6798c3a240832018d2802c7f1d4409c350a43b1b8582f6fc9301b2bcb5a17f8fe0c659bf9837662434f59aba79d7e56d148b5a612c8727a2288e66bd8e12e6b07be3c189a64d9aeb180c780412431cbfef28169d40c7ecc85a51cc6eafb55103ce4943da78636beba6966e704e1abd7b54da35cb63da79dea1150aa0fa53e996afdda33dbd54cdbc0491f80a6e611e06f280ac4aa5b585d9d1ed1ade90f658b91bd2bedbcd3bb33ca79aaecffdc34037bfefe36d37f998a481f086d47dc521e0c21bdc3ddde44f1b96cf4b747967a7ab74debd5d6b034e3a69ae2637c741c26d8674d4a7b782b6637dbcdcf6f4190bcb3bd6557df0943122f281df394ca5c1384cc8537bb88d9f87f45c755876786a4cce7504fbe96cc11e2303c684d865b62aabb3a2cdcae43d22f2855dfdc5c5d130c017217618787fcf8193dc46baf9d89bdfe8dd51183042ecafbf1bc884524bed9d01424552452abfaa1d4192c4446682e3f26291f2471851447164544685bc8474361bff47e8c305b8fdb6539e012a4f010854b28edf23c44025abbf0fdefa1be71e008ef2391c7f0c979f9b885f1da9ae4a84cbac6a2f646a78668490090120830387ccf8d65f3c0d87f1369c88ffe1afaf71d86f5f79ec336fe32c7fe2347fc3657c0f3ee36bf8cbc75ce5b3a73ccbb7c75be6797af470ae47ac87a23ab812ae53936292f5bd7429c9191b57b52574a932b4ab972a45548a4c9ad932d2c3a60e45bbc695701d296a3ffad81a51466e1c6dfca8f1abdaea08de4e58dbea88cb52ade230c2d8e89f09b8fddd33df1a9a2dd5df372c8c80465e7de55b3daf6fe5046917cc3c12c9c5852483f416a4973021e24d885497c9e5e557b515ccaf66fcaae8c5af924c98cc66557e7504c60b70c6932ee379709a6f71fc317ce65d38ec5bf8eb593811af0187f12e7ff12b7c7bbb659ec787ab3c4f0f1fe29d03ef45bcf75857f50dbcd378669c8d0fe77c784a0f8f19c26188f07f20dc7e197986701f798870940da8be54862a16e64b050d93dc822c4c65665d30612a534c5755a959575529aa6455a9c324ab0b49baab54415b505952b44b0f12dad53f1fdfe2e15b2ddf32d20a42c3507d556ff1add96ca9cee25bb299ca14350b9b53a50ea0575729025d5c48d25da5d27aaacc9ad8a1972545fb47036f0285e8a5ffaab63ab232b674585688788ec510afd25ac7c256b5554d6755d359d57476f5d78aa8c793afd09d07980bb83ddd780606c4ab2329bf228a318eabdaaad6e262a6858b5cc1b2a2923233aa6652a8929993696666f3ffb9fde76efd44f91396dbccb2265f9ae498ec98f4b4d83033d2da41ab0826329322ad179861451405095260832d044511446445b4aaad6a50fcfcace82065a53d90428675645506958cfd0dae079b1c1a356cd4ec9e174ce8c68719326862784536f330b3635db30903805fb7399326c6afd7a1f992f1eb63be5ef8f51f1396c36fc8c406c3c5af7313860316f3ebaaf97ae1388ec3f13843388f3d8c23c1d9dc702ea7a7871c9b9cdd53d363a3a7460f8d9e220bc333ebaa76c658b65432c652a569c652c7188b388e87b1d4e5f7ed949808d0ae9b4f9585b4a892453486ed9489b1f96ea28c26abaff234741a9ac944267a4ab167eba25d3d0bb55aa5aad4accf871b7bd3d98e26eba14d5a2df7fa54506aa79295450b162c5a7849b6f0d25bb4f052b7f09273712a3a11cdd3108ea7b41eebea99295a6ac857f48a5ed12b7a45af9068ddf5f1bc0f0c3beffbc050eca93c7567b55aad5624900f2e97eb64e403c1501c7924992ed2412c2c28140a855a31646414fb0986a138d2af3fc1de44848384b6b9e138bcee549fba5377fec7ba80acabb6b478cceb224da481c694714c494949511185c422de890d53a67aec4f307faacd96ea377e4e98f224c53e0dd54e43b52449f48a5ed12b7a45afe8d3108d761aea6ce74293c96838b24a6a920eaa648d79d28c24d3422b60b1bc9e9c9c9c9ca08835f18828ceac2ba792d5394e6b95ca7776cbcb5a23095957f59b27055957e53827d17cc2c6eed5b93c4d42e78ea2ce3ec9ac10f6ec14b960abb075b55aad56263321e1cbe3e0501aa5511aa5515a46d341b2ce76313a93f9d30cd3529e725a0ad3525eea9253e924013ac9c7ba7e2a29a30df5c040ed878f11e8c2b9dcf898c3f0dcf01c4f6aa2de7831926834929c39852f2f3aec8661613c0d633cf85792aa916830cf1373ee79706c3ea79dd9a2634d44255369e2d92ebd9e64258ee3532e9faa55529489b34aca52495249eaab4cd56157af9eaac34976a24d1a8b72ed28140a85f292dcda57a24c078932dba56aa9daae5e620239181700fca9c7ba708c224e75d598d65a6b6a6493fcc59772fef372f321d1483e9594194f325a17d3d164349a905d3d15e6606d71564998e7c1e12f2fca2a59fdc6cf381b1cbef9783e75f3d9d571c82c8c44a3c968329f58890aa5820a9a7a0ae49e27161b494db88c241a4dc51237bff1d3a6fbb97908632d5f62257675175f7a8bd3d06908d7a02409edea2b4419890495884a8a28ab7478120dbb60576f7d491a9a484e64a4144c232b12adac74d82589866497245a2db24b128d48c969a82464573791686575c32e492589568988a2ec9e764922d1ee2e45990f8da6da82d8d40174e19b2ab45a5be9ce20d8284df69fbb4e2b829aaf575aede975da3a3ac0e9a456a45eb1b5155f8ce98b7398439e1782f085a11dec6067470925401f7feccb5dbfd6b7ed5e93cf7978cef9e739c771e58f7d1d8f18e8017ab45ee1da7967c6396d1ae6acb3524a079f9a4f6d7f541445277c4a3e4e94944a3e4e9494f80c4be306acb5254d501927a75fc9cb999dbaa27f2f3b7572e2746ac2099fdaa9c48992e7c692a2504d388172ea8342d5504e94949438e1dc567a8aa3b840168afa537a62979c36317a497d7699f2ce4b282f7989bd3496d46753b77a2cb5934625d7f665976676e9e4a5120fffeeb2a486a19b48a4702c5f769912474ff4bb49238f91934ad358be6c8a1a7f543cfea85be34d9d3a41658c48de8d3c44c79bd49568d1a913d4a5c479e92d9747ae45afb11f90bb79888e7d863844c7fa474eb5c9691823b9fe51a964f29aaa6725450f1f7cb092a28beef1a8bca4f249eac8ce55cf5557e7e2971a3b494fcfbbbf4ef8ec9a8aaa2316e80fdcdb87b770c7ac241ef3f5ce62ecdedfeac23ec31d3e36f6fce3fab8ba02012cf9ac511a55d57a1cc771deab3409eec7e23c3439126e53f2f03e4b754f43422ffd10ce3fce491b69e436bce15c8fa32fc54087612c04b990bbe1461bce3ac5d849c5e2c6eb8531effbfce67d6b3ea2fb39496e9c7387aeb848087d8252aa027ac45812c70dd85d3af90c4b4e72f16ef1fb44f1a928522abaf8b40c4263b4f4df9f4e6e9dce4e27a727aff48953e94bba4b4ec3d857a29f53306ba761cce4f9a98f9ca7c4b5d7d4918f35959cf466e729f151899b9e7375f433deb949589d2a83313b9d511973d3910aa9a49091d36fa4634985e0d05ea2249283632d7b9470a0f5d803dcda4739787c7eb71e799874c94b6379f1ce4e55406590c691f3f81c6f3d1acb6f3452155017cfe9db9c73a679e692c719127a8db5eefddcdd8641aa0221da54c319d8c094021de5252a2f2139c7691578f29ce4dc7bfe7df9637fda479f0f4136e7240e04d9e2d881d6e6469390f3907371e4464e1ccb1f24277d3af41190d6f676909c1b45936ff421c8f64613ef73ef73911b71ceb90ebffc7c822e824efa924472edd6f3481ec88d3c4017471e2ae7bc07b847ad52a54ea89292d2c94ba792f64f9f4aa512c9f55722b93e954aa4afc471a6d2683299b4d65aeb6c0312c93fd2e8cb1f2a3cbc2afe25fa486bd0475ff60037f56d1b4b2fa4a10f4142f14b3efacf8a9f8b9f8ffc13bda435e9eb9e24e7a17de424ed2307c5b107f8a307b881d0541559801d888f6e9b3ab7e91c6dfbfa388360da81ebd3edad4cd07a816f64f124adcf10ec4a8e66b5b4896a71919e4e9dd620fbb4989bd6656b9d7d7797e1867868ce3aabc3e874dd1c3ad0d4ab4a6dc33757e839b4bfd19e5ea98cecab71d639b4adc576889a215bebf855db5b4a029a1e73685b9c73a02de72525c1aeeedc1741b3c51baea10ecf0b41f00247c41c9a62747daa94decf4d311870904e099baea81d3b9f13e8e9259e6dea375b6cafbd76f472365a87e424320ef20d68a6dd32cef8daed35b05f2b1502ad4fe8594fa71315eeacb556fbaaae02ce418ecb15cba6fe71c2ef19545f32946dadb5d6da0ba46da5db6dc23755b60be2cce2f49c1bbeb60d1a63d5377a5b57de58f6938ab56d75abd9b1e711048283a0af4ea53caec5d76e790329ada91cced9e71d7a802bd0d5379f143b75ae6e1b0778f4e841fd0e51a1bab3ad4a16b050db74d816284710dac6a87d7dd2a16e230522b797947372d25cb745d0dd0105f1d965f53cb1b39c9b3426379c873850fb3a47a3089a2d163b5763b3df61979c898eec9c73230fecd9e7def288471ecfe71eeb0ead43fb82e0b723413f3f5b8ef93e6f510ebbbacc25d2f409ea437d42eb2fbe9cc7395d398f34e36ce30beb30c21c77d77e72a61b110cebafef2deda1edbab3f3b20ee3f0cfcf48465d544f735233d0bf592745c786ee4847d421996959579b2d3f23d94f097675d84c18b33ef3f1a47c7b1e957b29a72aef547e6922122ae3fe5097ea594462840feb82035187bf23ea8e24b93f5446c7069c4e8a7c71601fc4e0b61c23c6fc44ec2d2f192606857a0c40e00d433132209d42a8b68539ad30a110e307a6e69a30fc03bb35ca000408ed5a53d53b9a609cd2806dc7d972bdab75b5293aa2cd86e9aa38ba5a4d13a092d585e88ecc168f05d355bb36784f4c8184da90211fa872a067e09d4065872e0eb52534f6bc7d3239335957eb6a5d8d5a7cefad8d5a706b2321d485fef7e207007830ba6d1fcf6e92d7f38793bcbe3d6992d76f1be66a5faf25d32584ca98d365f3cd85ec3a3ae015de3807bb51f3333313c630ed8e203b0c7431143378c64c38f339861d34f5123fbe350bc33f62918c108788a492554ce2b091ccbe98442c129188486416767faaab7aa34c5ba711c64424f6c5aa52c9ea4df8d068740d5a44221689495657c5b5aec2da4b13be5c192c19a14af576c66946ddf879508e725da711de703b6328806ad09a06bd46c8ad41dbf03ace06f61bf3428fb41882fad35ec7e92d6bacb5d6fa5a5da98c889de616cf3955da832cd0d9260533b881ceb6cd16f9402bf485382c111e7ee52e6fe22f9f1de6b1bb9e735ba4be2a1a264949305f76e84685d95a2322b337e7c8a461e1d67f54580cb7eea3be36d2cb2985156edd457d8dafd68affd599718b5271ebaafa4a71eb7bca20dd8e29ff526f8bf040a72c91d116a9e4e30c6199799c133771156be528f72c1a98d8f57581306dcdc22eedea58d825dd552aad69b55d75dee5857b977f15c4e172fc30fee57f719377f1d5f3e0b4fd5d22c0bcfa077b9c217609989becce0ecc744ad9f2eb2d8dc7f6d81efb630aad8ff539b14a1c585333845de3286f93b03a3a791bb24126f02453aef225934c79caab9864aa9bae149d648a34c9d4c924535e5325e1cd6f947f1f8ac5a5502947853115fea5c220c8202727a5128120643653a918f5581c2b7b1546d020cf91111359579d5969fd2a49caa3bcf52dbee255f8f82d5ce555ee9f729477e1acb7beb13ccfcb57fe22f95b345bb85b875dfdd221857facab7acbb872e1e22b123b562222ac635d2a1ee7549248f537c9635989d792bb44241f38697d5bb168a7139611dda2c745982ed5671eef5857a531d193c41176c7ba44e212a61ea0b440aa51bc355146ea6bf3282a0964838276755799b94810504e5a2c234343f548ad7d0e523dba87b115fea176308b7cc57889ea4eb529256632d7be454720b2f138367cc6440735fce47966c699dbd4b0e15f0d1596ca0a6b5301d2a091031a2a3366243163858583cdbac1ce282ab0b1e658577dcdf88a90a7ceea4ea585b5c7063364910b0911914b080eca904103192d1ea4a1f119d0b8c03f28e0c63d4d4297fc7b30061be5e73f5e796c64687c4c0407fdcc8c584885c452ecea980dd3a5f2bcfcd24824da36bd9ed220eb9aa12c964ae54e922f2f3ce8188126a1b57f2be0c6f23c33afa0d12daaaeeae35f24b270afc03f97c805cf5e62660c0c8cca0ba20002ca76b32f30a17f5466cac4c4ace440f1f30313f362be90910963db46b794ad24774a9e09b273cb78e3b694bf482a5954491c2ae315428c0c8c09e3c58b6f05b8a53c8ee26745c6b944330bc92bc82bc8b2241967d2704fc8bc78cd17fe8101238c61287014d88ef8e773812cd8f9f10f50145054b28949a3e55c6bb82f511166c65b34d47530421cdd4e19f14c932ba04cbc684f0aa80c6021cda13934099d2582309b8981128315e37381fe73c77894ccf3729a4bf4030d02788498791a2668421d76b3c82c324d18db64c8a049c480dcbbfc041b035d24d5553d65bc4438c9aefe2ab3905dbd4e0c056bd2e01b355886769f11c6b2aba8b8cf30a6e235f5830171b6a3fc0cd63833c39d7076b3fe17c98c4c03ff6ce4cfbde2795ecef23c305f799e1947790c452531112c04c3c797733fc30363c4402bd6887f7cc441462a0c46914ad619a72d98cd696d7db739d86bebb23cafffc13f9708ffd00871b8ec5b34e42524c9172f4af0a2868c8c6312c8d88889717c91c4f8cae416b1c64b74772b852a39995e2a37f9b955b057d655292f8ca1300e6ea2085cc347b010eb87b5cb4b4481e02a492d112b648b709ba5331d9b14cc40446778e3bc5b7489be4bf4e5519097fa80167c53a55c91edba4d4ab7b14651c901fcbb0fef7ef32d07c0739ee3513cc663af447a70a0d3810d14c1812196982f1daf01d09cf498f121c70d0062b076ca2034c59115b77e7f260dccedcb2d0909ceb66ec47cc170eb474c18116e4b7a704060544989c5f40033e345109128a2c86325d243c6715a9cc75ede7b70713f91e13fb88de2037816ffc16d5c857a7675924e7d6d4095c4c17c6d5898e48e856d41659830ce05d355b999751da92f2e09d214f3c52931c9eac358ba9039c692f49bb17400907662907682483bdc1224249f500f8e97bdf9f759f07f001483df067e18fc403a852e5d27e52fe02a8f017f790b78cc57c05d9e020ef313f0192f016ff170d8974ec487c0617c06fcc5478046074f0f1e222c40819a142e9b47998110ac6a17f8d5110bfc8aa802bf4242815f154d400211a0b365f350dc32115f7b78ea4edda96158694cd06464c0ed79c879b3200e088c2a29317f9a647698674d32fb8cf7d992f524b36a9239b36e7eb3fc0b350eccd261ecc43f910508e6916b87e1a65d88c82d8adc8b8617f11ef00d3c10ef400fb121fe032b50b00bd1c8d21811c556498cf000901dbb4161fc20fcc5d730a2075ff91f8af81faee359cef2d669fec4657c8e01f1ed5970cc5b28580c626834eac9d19e472269adb5d6a5201322261a3b566343ec61553bb1accf09bdfd0ad12754539fd027643f21fb09d96f55db3e05f6520266c68bd40ad531ae0a632ffe513dc3671883f14f057edb8f3be569125aa432e3458511524d5ec6552da7e6d424c2aaa3c3623d9296cbac6ba846d42aaa24cc8c172dda6c7982668d941552222213d07d8524c461bfb52a0a71b8f6cbb8223a829e953c01537e01aef20cf0975780c77c02dce511e0307f009ff106f0992780affc031cf60e70221e040ee31be02fbe000a40c003be4573c0b776500d78132110bc4991140316b0225a215915ad92c80af026384ec804783a5bb27b1843f1eff1eb51f6f62828631904b7c21877e2ac8d7ffe0a06c80f20e30f1f431efa635d3f5428a892b44687546c3c521f1e4a53f140d5ce9b0a0bdc1eb572026e5fb2594a8a4a0ff22015f0c5f7e0307edb38ec6ff8caffe0332f848e1780b3fc0ea7f900b88c0fc267d070976779cc89ab7c8da7bc0d1fe23de01c78237c7bd3092b881d352936f01f70204f6d3af0948756263ef0b487ee30b133691581f0188ef239fc7df0ff0138909f364018f19ee302d81180208478d9349b5d3b4284a46895446682b3aabdf898c3789b1b0efb1f7ce5adcf3c10aee37738cbf7e0341f84cbf8139ff1341ce66bb88b0d7f7996abfcf694aff1ed4b7ee2b8879a14de42c15c6d9bac6a2ba255916c16e2c0db715c16c4b792b48a8880f856ed876fedcc161dcf5b28f8b48d254542952c92da352c1b398989cc04e769e3b88234da979d924282e30720881d3ef34200f1c38d771c00217604010449baab545a6f9c625dbc45188bf116618ebb5ffc9b2dc091bbd4a4f88c83a2a0e0f984a6f9169216918e123a52d454662a339599ca0c06115c8b1796af84b1ec2b610e16e0f842f88bafe130dec689f80ff8eb8170d8c77ce583f099dfe1453c10dfc0fff00e7c0fd7f127cef22ca7f9ec32debaccf7e0dc0360c6db7098af7197e73ce6b7bf3c0d1fe28d700ebc077c7b71d3707f96eb14d5e1871befd883546642282d4711a24891318c61d2ccd6501040b8ce0fa3ebdc185de747d7f1118fdb68f3011a9be857487e5554c924b11ef0c66dab16caafd8305d8eb07e4574f22b2446fcaac8036f2203f226b31f6f92d3e34d7456ac96e12d618cf39630c7dd32e1f6961350665036abc2cb92a295142b36ac8ec8f815110d8b8e21de44663233c931d1e9c09becb4c88cab1a48f3ad243ade71be559b2d756c1159579531b68a80709b65745925552ad7992d9507da13be956fd15a3ba79212ef743a9d4e27545b0b1b2e4e504e4e50505050ba1a2c2c8d56abd56ab54a7452b5129dbcc48e4a85e8b3f231f1619166618b27fde4a050a9542a9552d178d1c7ba54a8f03ccff3bcd5fe524ecd976635939ac99c30139f2e3cb89e2651c9ea3a2a747215c016365c984ca7d3e9742a6151e3a9cc88158da739ab90ceb6ff5cfce9a792556b4df2120b91583c29c96ca9ceed947088f92900811e18a8fdf0e17a06dcf8810042b058a2c2708654e776b8ecadf0c63a972d4a18db4eac6d85377674419233adf0e54587dd2d2dad3046fae7aa9d1ace345ad870018620188661283a0d576e42c5049f536dd264afdec81b79236fe48d3c3b9266a24f56edf875d9c8343ccff3bc8f858bf1e4d3a2e4c7d1b278f189d9529ddbb1b01a5e1d0b95a311f7c05535657baa42b2cf4363241d21d572126a2c73736418c32b58565452c6f716ca09cb64a5429542959c4c25921e95e080542b43af5604ae1ecaa0ea24b17599a683ee04f5cc4cdb34e80941a3c94db9596b7118dbb6bcd9bc6dd6da2025c7711cceb876ad8c2a80367c53a5b47706fbe25b6f252da594528adb706b507dd97a6d0e0edda9e40a726691d784131506187ab69d8d081b2d43cedde8cf467f72cfddb937efdc9b77eee5f1f0cdf8be982f2f560db21e0f2b210c7bfa159a34d7679e913f9e8f07e4616192d5bf206f080bf3e5a161dbc2981038a07b5e41d0bd200bf36a5e51253d34cc9757879a174451305d2e9830d5bd9a57545f54e6d561bea80926599b54a6a432a034a830aabf58d857033aeb5cd07d4a749f6c7633ccc92e95814e5d3061a057a72800f346c1cfdb5e94b02b45d954466554866f7e54845d6bbde3286b1957bb29dc22a27c87cd966108ffec8a31ae1e900b66a2fa720c873b6e5cc8337313c4406dce59e573afb5f7de7befbd4fcc28ecad17df99207cd4c2bc3a149cb5f77e37dfbb85bd6d26583c7e325ade72eeb6e59c73bed7d6ba6d9df781a1c86ddbb68921f8791d37da9e25e8d232b1e16bb1bd9636b79c849f9f273cc1094ec839df9b2f38da4637bba71d09b719b9e771dc10cf473f24fb0d1d671741bfe14d2ec540679fdbf32f8c79de0d6ff27867c57e3b2e77b9bb77cbf8861527613ab7e74867cbbdf65eaa8137bc014fd2daeab5cee9b40e413443eec64729b5b43ab6d4a9754ac780297da1e34b0c4f9acdadf5eb7534dc8b0dd27c8abf49ab98e7bef525867fd21ac45f87f3966d187bb130d5d7e9823d7fa09e2dd85516469a2eec1473fee56d04b7d2f994d2a75b0d2c7697baa913a3d3db1f8d55f5e3b073396fde03bdd51766593bf6b03863217c53a5c42a55ceb8b6a8635ae9a5597ce76cc10e45adf5ab5bb8486aad352b61669f99933067ad60c84e6424e426ecdd18e3cbc3e26b71deac1d92dd3ab67eb175ebd8ded891aee13ab56ef1d437e89b2ab44da7ff50b93a22f9f419359c109c564d275fe80b89e43535ebe89242b1a914a02829f9e9c4cd1f8d72aa53275fad1c643977e29e8bde72947794cd39a7f4f1e98f4c9c23a88cceb20f1db8fb7c1b4b931da0739bd3b134f9c61b706e1f9b7ae72a4a377034f9c69b3cce4a724e1d775e123d8bbe7db96d8e1d95b3e89be7b18403bd8d3f2eca270fd1f3c8e39dfb71f736feb85bc4efa5d3fb89a75a2828d4536349f7091027ee7d275f8e60dfd0f7bccd71e751df3ce5756399f24e94e58d2c4fb19c7bd6977aec54a71ca71cbb0936713af200e968727372eaa70df2007db5e3e42b5fad560eba2e7d6c9ac7cec7e7e8f83516ff8fbb3d107a53f7e734d471f295cff0e6f3d55882fe957587cdf43beadd865d857fcf0374130d731e79807e723cfeb81b1c5726ab9f5b45c95525f7c2580a7553b24ffe6d5b182bf193d7d492a7464c5fd26da247eaabb4dbe49c976a272fa9116ae444b44d7a9b3ec8a644164683689091faaad557e9c838931c512a79475492937d1d48a18882ca8ea04b2a851d545f4484a400723ca3eb9193d9914b7124dc66f4cdc191e2501997f243361f7f08c6d843f7c6f97d29069b6f1dde301e4f1bb6f8888cf1c867181b8d26379d73de8d2c20775f4a44a331d0477065b779e71ff7476070ee0c52a2fa2a3a815ba638f565c4068fd820f8754f659b9733498e232a59c5d0a9839ff8dd701efad78537174f12f4ae0b75d82238100c39ce7f705bf43a0211f42f8c851c47c3b10cd2028e338c7de3dd4ef788faba7753597d755e9de2749ddb178e8658a5865ec935b8a864f5ee535925695039939433c93c839679f526464357e8065da07b84fb83ea42710333e7e94bebf0b7f90fbdb5771e5a0a4349e16792aa6df342934e513901eda3702e11f374dddc02ed9fb667da5e6973f77a23689f94730a58da2a56f12679398e6329a1a6eb3aca745d1e8a360b9197bad0c31ec69b8771f6c08fca0011335bae73dec5f8f77d2468ffdb20c7a532b0add3166e9217ac607873b72dcd96eb52a262ba5cffde64b65c1605b6e53c1187e18dc719917318de5c16a0fd0e55eae7a68b074e97ebe18b221631de448cb3187e09b59aae3b4ed77516fb6e6ebabc385dae83afb75178e35d97eba3f0a68e5c78ba05b43c2807c3ad7b1ed5387bb1418d75ba6e89c443a742a150a77dafab50a51211e18dcbe701c9d914dedc6ddfe213cc4784cd6079529ebb9a274b79a8f22d3522d9d5672f36aab1435197eaf70ba9921476162f6f2391854c56e24c7a2bc9240fec427184f2296424e4d26eed5e9a8884bae04ba9177e5713bb24b85e8c71b771c8ed1c76bfd5beed7a3f9a265a0f95717faebd10187d57d42109bf23c25b077a39ef922d894d47e72eb143a78bb5de0c47e681244062822e09974b8ff7b5db74b958771deec2d829bb577abbbd0c96be6e3a8bf8b139cab161ba48315bf2d795906ecfa5f5e06c043dc3c9a07782ae0805327c37e9e73eade8ae7f2c54dbed3e505b2e447998249ab29872cb85a83f4d9775ef55266959d36549e7eeedec95c1cdc9c1567baad46889accbd66cedcaac2d2aba93dc406dff34c9cdb9574d72f3f05993dc20303b0f981e6f212859a7a207113ba223de1572677796afe6ec8fd6fedd2ec9d7157535d54f9b5c048dfaaed611d1b810db6b7dbadc7c7fda1041774788ee1036d2d5d5ba5a579ba1f00faec284a954e3fd29311876f5126b61d7715ad7cf9787dc9ffbc3c4fd1182a6dd9f1b050abb346a61282776f56b040a438d22129988e42e41441204110976170b2e639445bd0d02021a12a2b62b8fd89b53b2cbdd86b79cbb2de74cebde32c66e59ac8054e3152e0a050de69c6b2af63ccfd3da3f17307c9e949852f987b23d3bd6b5637d6895a4855e53bff0d35a77313d4ead7b26e71c776b1cfee1fcc3dfdadf5b282a59edcf143fb4fb6cb0ab776ed8d5bb5a258f10e10c04cf83781d5ec93d1a1c37ec54a35ec4977e18c39fab450b88fad0ce64d625b33a36c7bf857ad08605fe69318b162d5f6d68a88888a8e537965d803c68ce73f0537d05faf911b2aea04a06814e690ba8c1cfebb88db3d9516e760151df6d2f20eab9ebf75e8eb2bedfdbbed64ef2ba4b7873f7c8bf971953c5cf6b2afecf51bfd9d8d1f8b9a03edb7cd87616e5d8a7fdfac49dc24e183bce30764f4018d05641a31edb84cf837271acdd87471e944e7d83deb66dc326d020185e16681286e2b6e530f46f0644bdb5f92c75911b63e19879685b974751a70b575677fec140d4579bef37300259772bac3bf28395b65dcde2188681e98a8bd942c2bbdabb37bcb5f0b0a13010feb94736c73f2a5595412da7db320f2e5b56cd147544b3d6d56adb771b63ec3141942ad76d37effc78765ded72fefb7ad9116deed66e7c778ef78755bfef8dbece4d1bd46328aaab7a877faa903ac37e77e7f2658b519c2a85834439e09f4b13917cdf77fe72efd3a297979087123db7edf3a81cd3211838680a93ad082a4c0c08135a8fd98002a821a8942b590ba3d62944230000004000b315000018100a860463d1701ec6c1207314001379a05e624e9989c32c865190630821630808000000080060208900e8b725a57873c4c4886bba342115882659c47e2b26a328d7d90b29f989ea108343f931cc501291af150cc0c84cab2d261f704554a2178a54fd7e6450140962b6de94822266484574c29de4b54480c6cbf0e73d33a791577467d362d920daf114c7ef3fa78da125a60ca46e1a4c57e43d4fda75c79fe1cfea9bea52d8c117e5d142c77aae7d026088bc4109c4a7ed98906156a9e2639f277f26c46cf7d1a19f2055c6c085b24567c944c082ce016ebfa44b8c5253343219db62ae0163f266c872a9c604e976438f2217dec424588e854591cfd1f44739a119ac64f431a2155477a0531272dbc545df989dbe411363f714506a0d7c82fa8a20379c560ba40488dd75d4be622095224eb3cdfedda39b5b3ed5d6afdaf4d7fddbfe48df74384a8575391e45dfbe2a03dcad81633d56268749e45097a624608238a38acbedd018c75af687007e98da47bf520528d1ab77859c7106b2cd60433657f7dffdfe8dc60dbd87bce63ef0e673ff0dc8eeeb9951dbefadd96a0aad0c1bb77da05a3f824cb56f9bc90f1889c032a94460e42a1d1a982ac5908b9f47c1e4c756db4fce4f3836a4ce87dcbff9d1b14d7892f5a74a9eb918caae06394fd349eed36faa10a8ed24d39aa393bf494442e59a977518c51f388cb9a24b651f97c1640a350d010aa9a6492387ea82b1323fa9f7c35dc078e626e446391cc683c9d7af195c572d1f9e5af696c4f1f1551e867e7a21fe65eb252601462fecee1b50e642949626c2a1d5660a1db01ac264a973449d82f05a3e6c8ce03abd5af8e526cefedd9ad7fdb8d9df673d472b25c69ebf29fed3ba3781cf7d8c40df072f250cb7fd60eddfdf332817ca15bc7729ac0a0f112466852da5ceda9f42a206a6a6920b34d84a603558e94ab777b1ed4561f24e3eee2ced7b344c5e6dcf67ac09c415dc4bbf9ff7d643ca392ca1eea1bfb4d73c09f0b69d8d8e31c327715806fe6d4761d9de8e88184486ac1d8288190eeb5f808655ec1b9a8c8891823601ee9dcba51a7bac525351ccfacded5d3ba044bc884dd63a07064a9042e08ffbe120db37571263f2ba370346779846648b6d146e4d5f1d0e57ea691012bdee728d341c93bd3181610096439fd0b37eff99f8fd375170f9f38bc93e50a43a8ed9f6a18e7d0c807d659140b5a92350ae4d4ac623d1bf455c21c00ff8d402bccb8b3398ed73a01d1d394995e20b3ef5309a26ab35dfc8a8cd4cb702374b517c2fb039bffbe8a2a4f405074c8d750ac7a3e2fa796b9fe9a68ac7625dd503be4aab37ad50e322a2b73cd7cbd58c1401c6fdcf5f7a6c5f726ca919be670036d1f66dec59a9434f8b1155b2705713cfad4a0cef9a2cb6c6472808604a160fcd94367acfe2d6f55e8f6b834e08af15f53a11d690065099573266538a59ac73c7d7807f5b0af3404a06794c9fafbf7aa088ca749e11a04b8d6853c8643afe54f32eebb0869440a026cc26de8f856009fd50b50236557be33031b59d8e49d18b3d580d3fc5116aa1c61ee32746c62adddafeaa23f177f82e0bb99c35149a977257481801974a83f98b3a1a036c9101144404c11a51b8e0ce0bfb80e043bae5eec5df57235aa3bc768cced4c9f3d7fe124f498f0d7b3985aed42208c6ba66827d35cc08f081c5beb8584d76ca8e8cb4264787798bfc3b53873da0183c624d4047d1594158c39a39f3996457f98c7b5a566b1faca1853d3e1f7ff66d69502687f50b9eb7869f3802e0058d7d1fe258dc2973192f9cca9e7ff3653917a3e6ca36daa96a29a12572ba24d5ab9cf1c61209d41cb3300d4ca81c64c2d4c2b401803adbd45ecec2672043c7f536291db4182cbc3fdb2067c08ce179541914213038ebdb484225d8b568e0a6cf5d48fc628ced01e93680a94fb664d1228af2a11a8b5271362fe7dbe5496bc76b143c014aa9098210866ab7923f594b7b5ed1f8b0871887940ffd9bb523d11425867ca610f0ddc1cbc04ed5ab713e941c16df325c07ec8833d5fec2e2b2a3c052946015551b5e728b39a8582b7c58ce4827b28ec0e74773f628a0882b441fba320cce17cb0863bed03f599c5fc628da6e19ea023347d4095d0991f8c39d7542b907a5af95047d33bb64cffb1394c361512ab3c7afcdbdb0045db74c34cf6c6d6c9f2d6eee6db8ef9e5927838dd33944bec08f473bbff8c15cf6ebc6779867907cf09c1952b5690c2cfa39e0bf6fd64b5b4084cd55096bf8e202d5e86ec0364cd451821906ac6b6e3bb2970e36f51a35109d57207d5ac26d30def55e51569d6366ed4cbcd12501af1c4d2e88d029114f8b3fa3ee0ec8486b549440a417a0679bbb4e9864f1a1164bf41a55759ed051c7f2d8c1d18f65531232b2e960b4d29ca12cee7431980fd898cd85ccc8dbb76e68000494c50395c416176f32e3db3eb8f964fb2f9ba26bfeb4c6de0beb7f82c025a0e855275bf00eea2f88b6f5d9b4c5ac4eac83513411c403841a78c79fb4837be2ea447e7b5f50245690bb9eda8d672392d46f387b0551fc1c3782283e073748aa6c51da5f6acb29bf04670e3010dba9a69dd27a2924db0d6836731f71fd9f9e420e8aa9cb29362ef3bd4b93d34bb9e6b2e344a28b58347c39c02ea846162f134f1ce3583c13e18797231e1f6d4eb5b09d198fcd322a503e1afc06ae0689309953b978f564c49c1d3929441e4a447900d7f929adde0912b189a1e14094d30337ec2344b1ad6762046d325733a7b8ec3aa217581c739c39b423e591b557780e0668ceeea29af392fea211cd354765227a14b5d87954e6437ad41caf3e5e7d90bc9ba8f9c5d7057df8887aa4fe5d3432166aab4da54745b21e0583e885c7d4a3f0077b54168349503422d261e8e02a10f366148d0ab447495ece9939c0e11861f01160e112d2915fa84044e57d7491290136bb6a3b82757377a05fdecc97ab468566cba26495dab83ced143d6a2940621a4904ee5489e6f78d4776722cb5a35be41e0c64297113b1910e07c8f2843ade8ac5a25fc6ad1a1d848a9bac8ba6bb33b9f9f1d1ce17904a6bc76161825f0f20b1d4ac513184db52922bf48f44310a6e48c53c0173cd908322a9ee28ce5406b361a42b1b9527059f918dc75594ce192217fae4a59a97ea806801b89dc34bb5f92391703d842c15e8a08bbc9747ffec9b50ab698fec8edee091f00191dc07ecd1a793a0de93f7ddac2a7a252166373cef456b62158e8550f6f046eae243a9820329652f5438c09b7f28b4960de964c0ff56129499a47d38c72d566ed860612f6ed2523907e9af4b78b30a9a401ef33692e2c11ec3cc553fe2ab5ed92bbe4755c63ca0db33acecffea3b814321896b466336ca7c007cfe32362ac2cb6c5f7c39f16d00d25184847cdee79d6c606935ce239609c36c6916bbaf4c1b5e33cafd8a026db801672f92a9541b252a4a771689b03ed7b1d1a2616c888aaa45134e844c9e68ce828472610ea55a24a504430ed4bf45aba44379bb1c668a747a6ab49ce847e12a2671337032e876ae0a659d42731121b19a04a7f54875a025e18d9ccb38e93f8581e3210096176969b945f40128e4917a5c7a116ea1e9e30f4fcd181a3b5f7404d252e595ec3c8c7d5549ef024900236f369dbfb04e1fdbb0a60184c1a8be70f96f073c7bbf3af643cb859ccffae897c3f1192297a7d52bc4c85b69a91a0b48eb00ec5656f1c4fd7eeb08f8264feb4538ce220640dcbe68b620d318b12875ba58ef6772bac05b7d22a5e4f600f605725ede62df00db6494a923bf0c923191fc9a89aa8e4c0a88522f4fe44bdf35512e4fd1a7261a1f447a29d8f3dd72f5f59f68c075489543601aad20a1c7b851797b368895784e2b7c532e0c4db7d4a8242359419dac3577d804aa5f698fa18c7c7b1fc7553a197a6fa838b689f426123d1b011fe8702093248cb2d498a44acf98648d8dfc432693c8280f5a6a0e2970ed7d330984b69e0ac621f1e9a821d2ec12b502d47befa8076be5e2a7664cb03f65fda0aaabe94c20fa72b349f0fd01d619de03044a9947b64d820b8bfbae482a48827763a0c3973646c44d5e7d207662247424f5272753a807eadd47ca81ecf3b0b7d24790a36218f4a46009fe2135cf592105f84d32ba1d9f8d27a94f3c7024aac1e91a8d450389f0085cf5fe49dcbcd66ae74113dcdf6515108b206714394e586ccc594e7ac6ae9d65e6c54aee6acf332204e99983f21265fa87b042b33675c0c04b4e776cfb56d7f2cec04b6d577eb764a8608e3f5510b9654d625c71c9440c222675c1080afd09f97a542bbca80f7b4a9f9b9cf1c86fa0410ed44b7d6f6fd9fb2ed35a9f25c3541a82f9396f7170e62d52ca5baa04fd40d558374e2296520093bb6654d396c394b778d623578ee7d5cd7fa85388fc1e862abdce7e2a5cd8043e0070b464521473f6d67ebc50420ae91acfd217642be1c63cd212251d1072995538f4bc0e033ee305b2875bba2ab0307576a7db44a4fce5b2720ed0292dd9e836e0a8ed172cc026328467c13374534d4d066710b8c912f9484b964e3d6dba0512843c8b6f827dc7de454e34426f169596ccf7325a2880a6828377758d8a1b89aa27b8ca85960cc94ee20bd837e11c0b7c6fa4367866d42572e1dc17b5b7c600c8237607df58f2f12664c10ec8b2b3aaa077d2222685bcdcf2654a61fe7a91a8fdc4cdd640040122a8a435f612c00583188e2002f7a5d5417c9a72bafbc49aa413b627a036c35f9e2508c69aae2b0c44c834d5a826a6e9f9500523142095a95bb4a1254da8bfdf85d308243b4cc88f80b78b84964ce5fbb40f4f9348c6ea75f484940d455ab2ad123288a4cb9b88dfda689bf8d23778454ba672c8084dcbf8a42de34e2645327b4cc06499d79241cd13d7012f5d2e73b06a475515ecaeb93839558dfe08f6679c32c1cf8e5b6e282f01935fb72e1b961e5b329794e51777bd6c05a1449d45cf265a618525d5347e87936c0bd897c6c325140363eecb8251895f249881e2a935e3b36c66fdc8c6ec203c3ea2969c7c04a183905edf01b0cc81d82860361065e9c2e9c20bf293b013f498843de9319651a4721973e0032ac7c10c4483648786190820e6c86f9df930b67f0850e8d85586ac407444b02fa1c035964133ff9655ffdbc85b33ccb1bb5178501a8153a559ab24b4bbc15ae59e010c072e36cf62f226b370055335678cb8ce320da2715a0e9e349cb200853547e94edc267b89a6d7df86ec20eee350c6590246f8e5854901b901dc46161eafe93498c5b3f88f11a784da105f7a1990b823fd467a3e1e79f9fadd7586c9e4ef3cd18eee4827daca13d16a0db82db72d3c1463240556b0a1ad29a36531ee790e05ce7ef67c969468705777437f67794b021584418d46b1862f45ab676df15507049d2489bef261608504431294702c6b0244b6a5219df4d5aad45b1ac2e00dbc5bd743818152c88cc0f0a251ec1ecc12dd76a28ab9acce590848fcb3763079b3682a104ced29da98f098eab39629894613217a2b83bce2b3d67d3f082f00c7304a7b7f661c404bb8146021495531e0e403373ba22204d0a4bcc837dd544884b7562f1441b25693a4c0483ff0c8dd26e6cac74e0ecd26f58207b99062c8b2492f0210dacd5f976be0c429baa08a9dcb4448f4e9e74becf57eda7e45654515b4851717b6fc847753c1d65d87294c05127bc4fd4dee5a3a5ca5901bc72c057cea6f50b109f5c5edd1dfb8995b418cf8b62f56940154249ee52e928565ed18e5805549f1284945c73c05fce2959daa823cf09b7cb6281d8025e89e9f63184a280561163e58e19a226876d54e8786218e364c5399f543d9de66e30ec6096c51844b64bd25ec2173c20f1c6148546193bd5c4b612e56832978a92b112d0d2698474c810d75066bbc22466f4e1b02c64d046d4018baf3c4b97e13a1b063e1b52fc6974e04601140dbf68084c304e70bdc5c34487761d0fe60c53dd164c97abdcb42978c1c4b118ffe0d87d52538d9b3660eb1b20d3ff439a207f42c6d6944017fa42513ed0aed2fcb2833b27ac36ff105acf38afb61bd2ee55bc86f6bd01a3db7bde50cfc95b1a7c5d869f65293431ea5a4ee400c374414547caa085a1a73ea1ed58fd425c29addc1b2d0851eab7eeff217d04418cc7f8b5f04bb5a0aa8bb83604e2918e2a20b78b103f50fd23838d084894d2872c812898a61553e3bbfcc305fe32db49d5b326525c231ce687e8fbd9352aee08c5869b63a7b64028cd71aea8c692a2a866224e3466f30863a51485322853c31c89a7f8f65360fd9a38510da9139f1c3a2c3aef2c1ab292a5d11a65e48755eb6061e049f04f2dd630fdb7d658936a576f1fce6502dc1ccb0a072b4077139cc3ebd8e99ec8b951587c65aa109c46bc8b62db878913308eeeac5f8899a886e5a37c904fe2e75110e88ec9b4cb88a2c429a86c8f5587c7d5a7f10ae92b1ed45c7a6f0ac85f492a44ee508593647b8c634cd2a01fbf314b330ab9315be92a858cf041e1db8938802836f9022290361404407a77b08096ae73837712515b36dbb756e2bb8b5f5fc45460e0f0cf0dd647d998ebf5e5535c19984fd76b32d2479eaebc102c471e98bc894b4e9260615a0481941fabecc6ab72601a943fd60855b995218c3c63622ad309c387f08e8e7aea9cf3861337242c534ee50a631fa675b8e7d97f6e21e8b31a3c51728234ca7ea715c2a2dd15199b4909f6503be11d8fef899740f6942c3383465c620c2f4e463cb256104957fd80837da23185e60216a492aef66fcb237659f006b1f5bc16c85d75e9ff4b457a0b7d8052f94b6d0d7bdd4894d67f03898b9116ee67060de33a253ef247ec41c18dec12cafd763146e961f35f070418e4e958068743cfe32e1a65b3ea73a40ff5f50e05cf71240067cf81c8e532d7f7f7c93e21297ce1171cf4a83d30415dbfcfab7709c42da6fa995c2cca900425a2534a1df31408129149785e581f54addecc34c7478bc5cdeadb8b18e15670a308ab05550fafdaa00efc4ec097b5052b1bf8109f6da3bc0f051b41991a06e372ce758aff33aba382fac0eac225aba9f51a43534652766c26f18f9c9fc0971395b884eff561d9300055a637a6226cebac2a1384398970c44c792a7e7bb789b935955237041ac79b1ff6206d1be026cd70088fed4d57edc9de316d72c21011e49d6685fc68c475848587fc15960f83c0a85d2c971db7d841d87e18778522eb54e223310e40e2fd957700e8451d33a72928a188899790ee1127f6351661cf44eed6c4d6c381aba1907ee9a351b3e1595dbacceac4e2a05a976c7a85658bf7bd514c668a66850d7c94eb2b612acf5fba4763192172daed2ed52c0c6127507f40ca9b4c6c49836c04fbc48c11b14f9ed75276d3d8f76d5c404cab925f55720a6781cba17f7ef6414169151b37fe0c35960b42d101daad27c2e0db599de84e08cc6cfc505f9ccc5144a460f53e476302447cd4e585ee6646af6eb5ac6895b8cf1baab6719a12f5bc10890dff2f54ccdc2e3c97016f50a80c84ccdf8b13efc20d5ec835fbdfbc1cedfc28df6b2dacc9a47975270d555a36396e01ec6a0302c03686b26f19f9120b4a9e80bdcbddd2724227e2d54cf2d4258bc84f21e38e9148a4139d6e3ee9cd83e961c225767b410524d9565d53529354c33ed56eeb8460450158c0f1493839d6714136d190130021f03ba3aa198a352bfb23563afbd085291162fdab1a11e08df1746c1642fb0d46a196af98e3a738cf87cd6f4131638daad1a7c0313698301c1d56405710655cd1dd7c03ce908d70e92cdf3c180f36606df3180131c1ec5dd24da022f232931be2c54b8f89a786efe31effcba28620c5350e84dca343240469ca0b1191c8466921c54cec04e249bd5c204f27b8490c10867116fe5d2c2429c32daac66db2c3fc1a53cc767d32994a0f2b9e65311ea9b3aecd36b6b72f2c5e59e28614e3c5cbdb0dc235d83baf7733d400d9dbfd147f7aecbc4fe24cba699e12bbb85be18826ee672d203b20c34eb80cc10ba129f50f9ca6a1d47ee2bc1e408981599f7074da29b3dcbe111e15801bf741a724524f7a39b4d7a455fef1d3c9244f9d0cd2e45b78dc35ff430ba99d5cb034c63de22d0bae3caf8675d582516850674338f332b172c6e4291335ebf0ae256b3e1de6c6ab724fcecb7c5760e0704ceabcb30f8481b4753de0b7ba1ed5eb74e3bb6bb44eeaec0ae1579b1a371684093b05e7691862005eac25486aadf6cf4e8738a3d9faa978bc884eb37832f6d49ff1d3c008713d9d32e966cb9d1fd823d4fb754fc8b2c4c20ed0bd2efc6e10ac0bc94c4f019064839477667d664ae68cb70adbffbbc37a541559bcd5ce7f4b68f9cb142ed368930586377b4e70ee345766751978533968deb94851e5be81ff3264340e6396f9fbf6e5bc75540936abdf914dc0ea020f5b3f0d657159bdd3926df657539d03489f02d512b0f5c8f60d7998d0cb745bd7f096d5ecca93714e347851f98eb1d7c76b683da190b6fc69b485a7d794523b08dcece36d812656d6a67f21d0bf917628430a815e9000008f9c78539cc3055f80d4960b34e446e7c5037d9d4b2d930b891dc3d7bee0610299eb6cb2452233fc2963cef233547b5b4a28514bd7091d6153526efb118eb4ee0a9f4daeb9c1f1bc6858582fb92ccc8118b1567044e48d0083b1cc75e8d24cbcf687aa3afd6cbda9d6e177c4a374353a455ef5638030469de2d1ff332674aa6cfe2e5dee6b575b13ed6eb5f4c054789be7385c604dfade01ce6e040e18b5fc4488fcadb362cbefc89ad37aeb24a13b9a850f5e3b802406ebb43e0c4ccf0369584e047ab38bedc9b2c1355c1f1990251f1dc9ed662cdca6bdaad361a2f48169e008affcf8a982c1e88550bae36a51f6b8464884a61090ee78b21c5495d9621380dd25415e114693149eca6c1e5d1d79123cfa365935f6211ec4966545757c9d150fbae64c0b85887119c783856df1445ab01b149db8a3f4c3b2be75bd0708dd7c6acdc3a14449e5549e40569db5280bb35ca1de35aa2388f47205a2c03c021e95249e8fb61bdcdfc1a87412ac1642dba4b2c86ea7cfd622cb84ed302980d9ee061a925a6c54bfa047ca4929b7654f12c665e4d4a4db7852ef7322b1c4b253c0d5cc6af8a0d5a1833d0540851f35dae762a5f6e610c155e4756ee4f59c6589075b905265cab56f94ede6fffba691eeca41fc8c792e1c35a94a7a8101bbae9341c66740b8cf8a6a84fd7031ae16840c8df5d3ea64b746a55a2ef9ac9761bb2e3fa855ae0428ccc27c66a40e20b9bb5781aac7e67d9b75ce4f4a2d9f95680f9b421c563f05a3cf39eb6a1e3bb26dfd30a94eb7636820b4ea1d4873a36d370de375fe65991f4afb517ef1967d37b1aa92523f92de1e9a1fae00e810a691eb55cac8d9e2fd33f0e85db53fe4fe3348f92c0831505d3e8c82092bb9f9cba5ab0498759449be6f1a77dbbf8f163c524f36455677fd7bb04643732ebc0dd934129c50320feb4ef53ee026deb15ecbde252c89c78c479264a1f07e97ad16989bb14cc3b8b0faf7df44890a04a79e1870f4ee111787787e51c5000b10b12dd6ff12d5438072a17b66d771812bdbaeb7b7c4011f24dd2992451f42c4cdd4bb20bc3f72d08ac528068b7a1a0eac9743cbef6519223064a5deaf2789acaac87d3887db22112c3fef9d2008d14285e01ab192934551998dc1ff6be36322a93bbf53c7cda54c01b60eff77194a921bc3d0682a6d215938243469f0508a3ba0f455c7f79f58756ca948702cb9ca5b7f2dc5814e679c3fe020599e7872fa20e58db2950811a6ea29e8d27a3dfcbe0c595a69dd9f39251b3d4d94b8393b4c51d138474579af28000a84fa935851da890023b389a9091a94b8da7c13e5867f8cc69ed781aeea6339f76c88feab8c7d35e82bff19d20110a3d30c6d3e6ae50369b734e399ee6d5da1f41c4f0e5b02f49a3616df18108da3f71722c5d1ab874d9c388f5c564d8865fe03a354f93020fd62908728dd4a5ff338f7fd316a489de0b447eb6d1be547a9a82284373113febdd7f4a94fb31678e58359b1b2daa86bca7ad899da22266cf3dfd52ffdf5869930962b7d7947a8c86d211346778d8c06743b5952bfdf9e403080995ef69be8b1c7d0437f9cfc3dab4936732a9453eadd2b84455e0d45db953355a867d8fc247c52d747ad13ce7fb219fb63dfc2bf104e530047c47e1f8eec9b6e3c3a68360d67146889f96c5dd5bcaf94460bb40425681d581cafbfe5ee87cf7828917e1c758870f632df3be9b8d5479e2b1c7f20b392286c0eabc0419363bfae6647718d2ffac0c4ffbd336b8bcd79ee8bd8c8692a2d98f11a048613e97c54e1ade3a5a26b04a574bf3da0e5c335c1c6a60e3c62b0e49ce88de4f6c995c1903ce1f648d691cb0798fba49ebe16a95f0854b7f940366a2764699b84d9e878d65f10d67282fad98152559a36956ad7802f0e093a3d454d90dfe0108b41d54bc8183c66a14e3d7d486d71155c1272f2c8781d2a5525292156f6093809794ab44fa49cc087ad0c8d4fb5ad857a1daf09d6f8a0ae35a7a6e6fd28307e84ffbdbb0d6dfd205b85efa5b60804d152ed09ad54aa7576fa7c89beef36e16094d1a5324c4632c34a7fd49413084501bebd73d829abe3270b0c85c03327261314abbca0e23b6f1ba96a37db5e80575731405f9473ff0d86da4be005dd7e7283600b4c8dd48b76c62672e0663a3d8ac7e26867b150d36f086c9438d3e3e6cf9b2aeb997616a5eb013e4856c7577800395d9821f13abd075c0624491b1b07a9da32e8c4ec73680eea6a3861cb2a2c31e013ec6ff245764eac7bd588c2c2115614d8c7c344454dff32b44342202ccfd1d8a9e9cc5c99aadc2edda91d36027165c16697e1918139eed1e92dec63b29c5fa9bff0785b5a0736b057e98b75e5050103a54a50bff03bb74cf79c6ec81a37189be82f4431f3f11021862fdc8543f1b6066bf54564915bcc2c811e8a43de8a82e9def5937c28892c66d46c990470f31eb86391ac40fe7e83b706095c83ed1257899a66e6d1908991725a32b6db123b559d719b7cc60dcc01d39b6c8755f57c63178e1fadacef0e22843d4bf1eb2364fdd9e7150d04bd5ed4b8e4f3a7d8515400f778d0d638c4f0c5e88668afb23b49bd723531f031d3cc3564665b70ca005a20dbab6c7bf179a6b18db95b7ca377705c3054417b085ccad37cdf29174aa3bd64f246db42554654051a3fdf6fa62b9105b284226d15862e4f2a273b96a40556f372599badcf1a9fb35a876eda84f1d9edb1818999dda03fe1863cd0796dd78db445e584dad7287971ef865bcfd8526bd36e8b4ac93174c8842e0cdebd8a77fd5947607d6c305f3a58019e81aace9326a58c5fea9eff5d35e89faf239f7dd334bbf5e29119e321017bac64c7c8cbefd26bb2a45e13d8f6fb8345408dc266a62b4225588bd2fc0e9e68faae163caf00fe2b8e478108bc262f4d4567300d8a3c35063af53af1826b7c187790dd5856fa702de9d40c0e073b0286ab10fa2bc7b7c5f826bae9a66403dfc67bda185d591c340a8a2ff5536a6d4aab9db075bf316013a73ffb824f9f2574f1094df292162059a7b16b8308e929ce0b307509a008eba572b106159e5c5edb61449cf4d88838e2a13b561fb46ac8d3145d92e0584cf1f02a26091c5de12cfaf989a96944e2c748b902727d80de3959c24c75e9b35258abc139ca45d3c574d7bb8db6df9b6290936d94a6f85fc15d39fb49977c4ba5edeb40b0d0dcd1f549cdee1488f516b05c8c33686f43d32e8571df83257450237cc532e5806d31b0423b5c440f0cb25a30099d96b59f02253825278619d4e0a5243f238af7347d9f936d3044e6aa1a02076b11d8f636198ab95aa09a68cc950e6b9bd511f2645b3de8e887bc32d8550b31e42e38c18ed52ea1c54ffc663fee564d668fffd9fe206f609e061488e20c3467a6fbd012f3385c6a2cd77d5c271e97d2b6b8aa7e85551d1b3aeb9de2e864b07c62e5362ba0bb4d6aea729aaeb3a897537005ab489ee2922ca6dddfc59b3b4fbb49b17542bdd64d74a6303fa99fe2f6c99163038ee1d94ee36c769ea45de5d759ef1f096c9fa8a4f745617bfa821260437121be264d26a65594dab5fe3c27a5f4d4644655f2ed89e8dc7ae2788ab9c13523587172451cfe2fae9ab1d040be18a251c043f7428e38286c2fa0ba2f36e54bf7b17aff9af64acd75e219cea46d1b59e5f8c3b70870873e69ccb70ed0699874da707139d8bedd1dbd700c04f85734483acb5c1f94110af9824698663bd1a880ea5f49b67949c91d80bd74c20f483c5f99b9d4a6d1a08337a6b833c9ca87b8909e4ea03a286e2d0fa53da0f1228ae98b2818e585f3e8570dcf67939ad57a94d04f5000e1a44cf3013633924df6e908f3b0c1166384c411eee50362c2e94fd691a8a9518be9afeea32b4d9d67fe1e33fd859d5937e82769cceae5613ca4f74d9f7763fa271e199b8fa736b68a687a41286fa54d0904f6157844f6666879271a1ad536eb6a8009f8c177b3252682c2e2b4ce9269f8ca7ae41573b2fc0448e6bd4520e144c48eec75ac28595835d24a768ffb1fcdbc491502df0cefe1091ca460ab40d5e891d3406685f8e39d1eebdf109cc88501015e9eff8066f6737a4c27f26c700273ba8610fcb6940db0169367b0f714656dadfef632172ea8c94b3ad7dab1ceb1364cc4e108ea8efdb5da343deffba442324386195cac755d82a7b1af71f8e9b6de1469da42aa5f161cacddba08c4bc5f675b5a48b1f01896875fc538d73f61d0cbc718942e462b4b04e2837e8b857db74db0846cf491ed16aac27a8c832ff5d060daaeaf1b60e2c4f4c887c66781634e9f840eadf2fe07b7248a39008fd13ebbb4f7ebbc4eb6771e75e51912fe31718807ce14bf0e1d5fc32d0b44afc1195fb003e3cae43cba5eb7082826ec708be1231952547bd9713e7fa4a141a360fd28164097d9475be19830f148439eea23cff0fb87af390884896d021e8bca12774859b612c34ef7715f0c8a0ab7295190587129842081c202c6dfbd707a15c2c622293123431d1e82b1899c487c70a23b4f5b82b51e29889d23cd9ca3cee6c57c32a39d0716844720dccbd5c709198bedae4e26659430087bc1a84e9a8d24d98a7b199644a145137f5a08913bb9632e637a29b450983ddf675f428566664664d8587f7cdf454fa17003694584430cae9d11fb8763b09a5353ec15420debd4fbdd865bbf32a9a8dfc5484514ad46d012904024a474baf5b953b46d98d716ad3251908d2af136bb73d8b7ce69398ad4cd46e6ec03d1c1f468e2f53e95233d4408b0446861690a27a5916f81033f110ec86e46129f836d22406a25d38841a685c445f0355f3b414b35fd24b968e059b25db076c3b42298989268cc48e3c434cd96f83253630d5eee09c5d1ff02f673b5339d00b6a1b1f775e5cd2862394f9d77d98c946933409db976d07e35374a4f84cfd9611c3ddf26c0c87b1cac136b2fc42982b67918868e3162289bfcec0b9103fc5a0b3d5e21bd017e4020b077a03541831e478c1a491788e54395d665086fdf99cd2980f07e66813161ff19b2db84fb3dd5434d4954ba6fd6a01d48497317bb1e5997a2f39b128a22dddae46fbb10087a996eb7a84d60c9deafa8539c3a5b889d661882125c8a7bbc8036adff147677e095577b963018f53d1e334d8ed2391d83684a0730f460efba2b37d9bf7a2e46b643e26eb9779a3a5929bccbc5903627cfc9a8d45436e3438871b90a2bbe205df96f65a5ef820852c3ef31de9cdabb7ed12b0a81ff2f2812e41aea340811d082e7ba086f87ee74e9cd34cb905b720b267486b6a1d33fe9300a9c566d2b1ab409e05ad014c7e64af3fbde8d614fbadded916fad91c93f649816fac91d0d8b0488c7d381986a570b476b17588fdd12f9c8ae50180416db19b6cd7407520c209e40e5209df4c6bcc51503366ab61592030fd77deaf3db847c580283e82c8e6fb6e287768361a8fe6c570a5a8d2ac0157dc827c6873cdbddc484c5823665781351070cb23520bd1f35b720244d2368ea0ef966d48d15eed7bcb301c4d2288056c348b11d52b8d7e2746d497409f4a10b18c1c68904ce23855746802d373e581e3999af80e5941f51ef3edff2ba1dbd24d632df0c4882e66d63793328ff672a70c1075eb617ccd4b4302d8c770d7d14fc3b242d3888f0369947689aa0bdd5ed7656c6211bc041c99935c8c2d0248bd6b7e9e55ae1a906011158d1101899c81f4073c2314311458905a68edd364d5a95dd44ecbf2a730a28dd309011b6e538a890690a0dce6af86cff445f4f25be4bde20e415f224961ddc705abd4c9751114bf2eb1734ec09a5573bccd15d175f90deb48b0118f2a4042b8ab382ad75cf3bc050ad7257d1b1a72b61d2909f77f56cf7e5d2101d7769ddf8ba559f4973110a4ba7b188a0cbc7d47ff2c9b16883de25105a846817ed5d104a6ba58fc4dfa615cacc12b174756cbc81dd1cf3a930064b300ea30ccebc75651d42194585336beea28b3b8a007405f75d6d8afb1d781187a939f75ee619e09ceeffe33b922be4657777000bf345fb40fd64ab033385b39dbe574cc1e2b11b411d587ad2cbed5b2ee20927957f4d0c5b72912612dfd7252bb18c6d9aec00f6e4b8058a1e7bdb6e78dbec1b0b56ef14bbd54b707614a57547564de5c487319a5c7d022df40bcc745c83ffbbef3b4d74f420d337cc135b6528027fdaa7f5396217acaf98d6521f26002c3d82ce0125fdd83b0fcaa7ca887be105d4220bddb9c6feef6fe95ddf47a6e621ca6c7f6e6dbdddebf52377de7f69cf9ee8b37e04ec555578d8105f74bdc2de3250326efd08cb0d3eb5bd939dd9c09f6bc56c9dba10f8206c1e838762b60bbefe14425a2d8d7963193155f7bbab862fb1b66c7d576d58ec311b9ed5c64fbd106a009f7c2c0e00dd4a4262ad068f070ece40154abdf79b6c33760dddf29de892e78f0c0cf89122fe5f8e5f330e6cc3f767572d88b818f1b37e1b09873cdcce7775703274f4532d465b731a3b050f891f89708126ebeed853b3f8e1245c317b13f3c44de3442a204de861b2743074d4bfce272007ea4006654f7a80ff7663fe545f4d049946ca862997808f6ec7a79baf325a9d454fe076553c7747a7311d7f57a1efe817bb556c664eeb1806a6db3b3fc0b77901e58e9ca4b32c4d697e3f8b1997221363a16f6b73c3635fa32676268444a407074d770cbf3bf245fbd18a8a97eb3b2e0f8b487563be6dadb482f0e42ed18159e87d26b6069185bb86f0e41f43fd14006d082c712d62fa30446afe5081718fd9423b2ac1a7d09264a07c4e6ae3f555e8920043f65fb9f747d0860a7169704efac6caaa73166e3bb3395c2ed2660aec8e5ffcae4911a3e2485f467f5c2f5abd8e14a0cd6edf84a9ed85c83a56a8e0e6acce832d08e487103417a8da326c6c894ed7cf6a7c7e8828228b9873d324e7105b29d6fcb58c6b6f55a2d5b0b5fd4610bd09dc4afc628ae487905b6f1399afd1312f77c7db5a7fe0d1f587b9350438618b89a570bc48081f23aa6479cebe1eb98c92a7bb7c26696076ead07f0bcb3ec3ec1faaa2e3ea1708c9a9515ababf102d8b05b6113aed60059116e3b6ab5ae33d044626c4e625ac0aa7fd9c96ff0a1dc50fb525965fa287538ff8e489f89eea0717fb28f09ac01070a03981bd56b341d2d696548da4c74506eef259d6161f0177a1543d9515b269e82258aee81cf88691445d439daef8a32b045d65ae6e9ac9bff92ff98b2dabf2b60e6c00d52fc2e1e44ce67099df26e3a88f1ed2a4de692187882a563083f3ad34506d2ffe403a4dac660b4c4fd0cf1e43bb69a7e013111bb0f935c209dbf2381c5b06628de24d84d294d02ddba96a6c3d523175dd7d1ff2ee9ca2cfd2c4db7e6e9ef6aa2517705f618fd26d19387ef95c45ecc0acbd9d890095627b14f6f5ea0fb893d7616eab7d7a72f47ce95567bf0a845d2406827be22943e35cdcd6c7efb8de9465ca59f529de24e4ebf82206d8f620c0b9ad814013067ba7ae1ba29b2c961905f868a8f880639308d675abed719af4310f1ea5912e32596fe99bdd21ed45a89aa4dd51484a9640d68e2d029c5d3d024d18ede06b404df46e986561511cc1cc033bccfcfff42be851c7a55fc9f2644afa63133d8b356ecf29cfafa3b7031f44a808568ddc4d2cbff8bdc33f161a0b7eab7262ca347da60db4c4d3f4fa59cdecacc81f74a37922f030e04d125bbb515088f6ece051eec915168dbc5d658caf86f73163024b35da1233fb742a8d1e99c883590f796a1e3db5dedc2f3538c2056d314db742547d7241531ec5ed2bc422c922420b69b49c68c0ca025cb022a202a1d07d74e7defe41ed07b3df12dfa27bbe50d6a64d343fd6c6151d8359fe451803bc909e0090fce265d56f91f1d8e56818d6bfa5f8b4039cdb017a338829c73a3585bb9f331204f65d9b256a852c22df95f045309faa0f9fd90ebb358f6f2a3a4442dd85c78b582c609ae23dd2509cf71b88741fce19350c1f24c76b5af81130e68bef271aa2d98063c154c4c77c6c76aec8c00e1d4e97373141c3ad17ec675624ed971510a17cecc2f9213b345170c3beb5e43d31e00019ff7cbbf068b2207dbdcaf0e39174429df8c61922592f543750b888cdbd5758228ccc5606b7829ac93a4272aff0f923db3e6a7463a6a8273a97a3e21c2512939816ed0fc7276845f00d2bcf55fd657f510ea240adf1353372a1b83e8602c5d2f12d021e7a68ce12e80e01b65d50841e4bf2473157c58f441db197a915927f79767aa95e4e7e8ae2c9aeee7a6dafc3d962fee5b05eac90e4905fea36a245f47525c4472ac46ab57b7f1aec75e43fd1903c964eee33aa069c23b51c326455b966d5c3104bcae5b13e6632d685b87551171e1400db7ab12fbc6d231999d6bd6ac5a67a20005699479fe744db024c4603d0f6183a1dfcad0968df0d407d3a0df2544761c2006709cd3123fe21a449380fe972a116862cfd72aa8e58ab9c811a95536a99a748200dbfdb584741d0029415130e69cbcc6d099b8c47b86898256d7532543d774c61a7dcac6eeb037458832b4eb248a30e66aeeab0038a4766fae4aab583148f2f94d3a91af9be44548e8564b80822d2260224fe9a6776690be08930763c9ee738b469fa57a3ec5e929fa3a84c4fac245f232827539aacb1ea5d04d1059d466612242dc583947019917a52c0d45eb0f0abcf393ca05e893db24d7a6558392ea4e89139c2761c88093f7c9c443f4501b49755a029ee00c396322ef0485fe66466856052ef3d822a42e4e728d5cc71a73194317d3072785c604d0b8fcfdae148dc4beff03b1c9c1a44198fb73506cab82a6df2d96cde72cb3dbcfe18b29b71c5c474d7e89b20cb8003ec7e92921a8236d8a70fd84a7f46a095858221718ae5452e435907e6108c093baf4608161e931eaa4e127aaffbd63943b8fbf7906dd5fee9fffa85f46fea3157fe226c4eeadf41592e961f73917b401cd7920fcaada7eb3bf1bfbc6b26617dcfad13fb563a690046577a2398b1d0d728ac8a71644c84942d244189cee18f92c002317ee207599f5a1d6a0aefba0dfe6b971e1b3600f7bd2c217df0c48c1dc8fdaea1609e7bc410b86870aaa3785c19f39989047a8ef673cdf84a2b6724cae03b38c95d222a8a1b1d1f9ed80a117b34abfe8c9a6947ef676a1f5b4a568a76c17835614c7b88b16e5234b6cc3a46e7a7ce4e63799315e460605865ea298bf977fa04967e424236876d510815da30225a26f5560c0588a5eeed92b0d49b8bc42c75c4a9f8eec989a1664f7f192e96438b97f978d8a11e4f690f2e30ea55d265a72c15e45af09b22f74690b27bc9ee5492eaad526cd4e510439bef149c30ced2d2fe75e3352a7c45c59970f8a6ab007445442cc43784282181a684a4412838b06e885fe656861c209aa3c7890fcacc94f66cebb1a6181bf5b64935e86eaa5e6c7edb0f6ff73f9cad063989ee7ddce338b5013a5a8c9c8e4d864c464c4448c67a00c3d3eb393a4fd8763ddf277e6b4782a98ea858ae6a9847d474c04e00aca8f7456f642730f0bf3165cf46a8e7e0d3ac768ac1031c888542e5ef352ff10169fedfa18d0c7d2d53ff157ac6bfb440527c6df4d022f96336319daec5cc7c94d75a41b4365e9494cb296a199076953bee0c6335618e290a901f403a36297315ae91d74d961ba70a30ccb1918ebfc41e0c65cdbaecf2c6d57d5ba5ad845ecf32ccbd00b02abcf3f3044aa98f74b5830b34bb8be944dd079188365126a3651201c5515e3890ea9dead38a570afbfe9ac38da1128b33655a1071ae7d7985a4af83e0fba8128431719d7074fff89ed4b83ba8f8ca93ac9e30bd7a4282121bd5c753941505c3fb83a2c77588d64bf38c27d2ee98f40d8e01f899f9e19b8c357938b08e11b1916407045aa378381c6da4cf0465e20969de150c2384c174d2bec1270fea7d7706e0c260ee13a2a8896ae1e27c13b9e8c1ffe4786ba4f053374296401e5ae7f35adc7df0547c41131d76503dea41d5507ea9e9ac68dc4734d064c6ead31c37cfc35ddacc483619f113581a2b542a68fd5b290ec4aa30639f0ddcc860844e8de0c8faf62025fee14335723d186313216c4777828336763577557b2fa564752dbc7f77f79f5d81781734f26f403bc448e5873455a4e8730552e9a1a1058ca966d28c7bc54efc54d948edaf6b8e116c6de8f028a5a075a3b13e5b0ad30c919fa638454ce180f422742ab95c13381c3ec7209258079e6c7aec7e5807326189448d9c3d04b9622769c9ae27e9fb8c9d94f7bda6f3531cb9a750ef503d2c4c2a4b4d2566a015d90610c0367e8a98d2ceb32025ba6e71c175cbf0e3d107e19aae64c3adc94ed193052bb9c7e4157e9d040ca43ea7b2fe01ad9f578a5a5f6fea56e496efc62055fbafe0308375199a5b179f37e50b1010cad25fa175d5b692b3643da0f59a96a9c9e33d9688f4ed142f903146211a973fa8414daab85c5066d4175d3285f3dd7937d303eef8cdf89305cf78372c244a42ef3e5a706c9740856a02c1ea8423239fe1a74c8c5c7b30a24b85636f4807c0afe4cb7ce6ee2d29408edaa2a8a5560a6bd0c3de706f6643a6cd3b07310a61c2a1c4902b33e39454749955266e32e6c130e8af9f5f427d0a090a08fdb049b122e2e09fadba0179634b01b44d0cad0e4d1b1f9585d753c98ff90d1271671bed9f7ca5705cd4db19262194ac7d561d2211679e77b19aa5b761ddd853a170f7245df758ced5ac521eca28dc049474e84e33c7da8f4d497c2118fa283aa9e3dc37a4a951b8e1df183442724179fb7836c0ac2b43cd08f1baca656cf4cb6ea46901d79780a9961f0c41819f43b6a5e141c8a6c129380146461535681e1a5326570b978a832f4d2627ad4e44369f26dccf44541053f73688a3df24ad27109132849e2e81056349184b675688080a41d3c26b869db3d188c5f2109d03dd2b89915f8b336ec84fdbfa3d63e32f4af8223b18cab73d8adfdc4ac2558b37f906b3a596344197adb3b286b53828312b76451e93bd1d43b952db7e479917d4c06e9bdcdb16e54fad3ad3e198aee8b2bb2202fb19cab8dffbbb311b372217ca8d9adc9be387e5b878d6bbae955865263487b718f5535a6e0eb9d1cb2c5ec851f6e197a10ea167b53da6719b77f7bc103c678a90e06a89e6166d88bcf84b5a07130c87729ecd778dd51c0daa8a0d1f204a2228d11435c7fec4f20c8090a546c76db556b1bebe2c003052a8452f95713331228d0e1890271b1fb01cb50f4280ac4b64781cef59e78427ab4776544ff5952226d2f09f4b6f8b70824e9a8432da1d39c22a4c25e344cafba270194149873335a42a02746b0af4689217db81a854246736ad49886a1a35752aee4085e69a115b9f9760ca51563503cf7068e71c207427cde2af7c8ddfa535426070e30cb9a0ffd4037d3108dac5ccebd069f927a4e2fd02643d94409f34c76daa7af332466ae0f4b680c4b70c42ab3f7f10ecc2e099b87b2f27a610d7433413407cd5847d4e958c9b3566ad3615be86b0751c643eb68c0a25808555b0fea2c6514a1e04c467ac074dca950b1fa9f1131131f24ed052645b6472811984a9c92961ce7293294413e52a30c3565416b97cf4243248a2d114d0b2d0ed4f43b2e1455000505bdeb356b9da9d8840e81ffa1fd6977bffc0b1e0118793909a99261093abd52630a0e61c8c781e653dde6d40b88f139dc18c4f89cbe31a97f229057a9180e780293eb99a73328d66db6681941fb3183027989814fa4a061df4851831b82530638de087eba505150945b6bd5d3927186925d765870ee5adef965c7e28db57f04839dffe11646ca1dd8878cc794de60b2146b2d9db029d79a83db2bb1112567f5969a704456e249c2864bf20c9e883fad74f6139bd8cfb84f4dfc0cbd0af46295945fb91801226a46255295a0a0ca40f3f21fd5de1d7d079730fdc899ea207b95bd4ad30609c9a2235fe490d4d1b685ff03163a9862e4f2e9ab051c65562fc64e983c1986374baa03b374cce8c7b020b0d19dcab6f684f026f851688be58982c84758ab088e4d9305fa3f50242419f158034471b1014f7caa3afa704ce800b2dc184029fd6698179a65f30cfb8393099463f8e43084d75626dd687b49f30c7638c06101a2566f8817d2fb5eb42a1fdd4323bd5d21ba38eb19f922468ff6b2ba1b4e3b5d48316f5c93766a9316727983f7199e7ce1c863d5a7afd9538c195163b7268d2ac755d7f18ac9c9e2eb0fd9f9dec40bdead6429067c21dae6fde1a2a4f325ed34e985d66195e800059b9afee5198999f24906d9c378799f54c00c936145cb62be233a0b1f9e14399f8a2aef88ea16ea0f158df130ddaef4f647941b934563da9df4134aa7fd47d8eea382f22e797eae5e6ae6a3018d9ee7929e0e33cf105e4156df1b015d08a8424a95cdadda4cfbb611a1fda532bc6d1def195a690f4f17955f9f419959103260d8d182b360c0b31e2de1cffccfe3cb04dc7191c4387d1cf19ec4a4005820a409bc8f08a7956c8e808cdb077f43b5145117163137597548e4b8f450f6979bcb6d886359fc82b7e85735bade61f140fa9881e52aef01c62d152d81ff684aa0af4a5afe761d9a6d25c2daad94f73cb1dde1322a1366d281d0416c367b794128c67d55b1e112ca0fe34812bdafd71843a6db8dac521b9cb102fc13c6543b45dff51f5f154f12fd0355096f08441227bec1efacc594e045f236a797e0884f7f22634ddcdf32d4830d4f8a92c9af40a4a0cdf53829aa2df97ec9cea608a71f38b179682dd6fb034d3e0f3e3f2538022b401d25be6f8771ecddf27da1d97b0bd39dc145f4e8b6f3f48e0014c2813d3831f8f2c1bf5428c5cea5690124cba76f42962020d6b8232f91d2bab7b3a79265a133e6f72627af51356acf8a6eb36dbb8492359a018320abbad77888336825285fab6ecde8d47f7f9e5267a7abd46449dfbdbfb68fb3e84e0b7f50b30f810051c285892dd01c2daff3ba1d833ad699d9c71c6b2f0527342cbed907db123386af704ab4b58321c96283d4ee863edf27807ff2e19188a948bcda3b1ec36ddc89f029ce29d7002200d316cae6face29606d8c4b38dd9c7a219346e413b812c165d4a550dde392b0f75a3d1771e7001d269d551834a766995a1c1dbf9c46b66433f39aa945b46328672184e671e66952539ca4d64f5c9e6367ab45771de3042da2f13cd7e5c5083b0b3851e6d013b3b3bc4aad8f56101b2ef2bf9c682aeb1d510159fb5b70fee7d16041084c06b3f19d38fae778d03ce44f7c570af02073c7e42a0b56885c8e1c1d723024da34f00741abf81013c5d3600abc73d689fb05f04b0864b6cb977e8f529e34ebd16cd96cabb520138730e866c68ba189e92448cbc0ae4f7b4cdf148e0fa44f068d7a51e64556bbcb13235a824a703bd4e1ecce69d7ceacb4e79c4787437029904720f7c632ea5484ec9e292cfa290b3f3e632eea541c4a51f4d70014f42be59dc4bcd600d3f1246a807f45c9ad9e995cbe233958f97b7cf6f4e9f50a3a063988de46d8e5d197f9aee740134e887c7195b0825c9a28c448d73f9e56b4ce686b029f6fc6f803fc0c59058bc2285b92461ce4e0a3422ee0ffc502b959b55e143c06fbea485fe1d2f5dc1e743e15da43e5b185d8031a92f9ce855482177489f2d12b47b1e85beabafd471b396071efd35cbeff43651a0b0cdee92b8051559294f5b027be4b82c94c76360ca1ce86a0889d21801f843462bc64741b68315229f6356d7faa8f2d29b556ddc1ea58659053b3389fa0afb3a2958ef3545951c82d52459254d845ed9cf1e7be40f97d3243315c22db4faf4fdd06d921112db27dfa6929b39acc1039683b61b44dd3b9b47d8b8ace9db96d0b6c52da3682dcc8b9684ab379d7a72345720b3fd263958c2a359f2dea13671cc4b70cf15fd46a7c1519f3b1d5a95fcbf344f58013714a4dc4c4757f028f6ad682c0c014cb7c9d284e5c62accd284c6a928ebdcece96a6001a31d4bce64b65c8ec21c97f7daea31260816b4462a69862339817042447944ac3f620e571c8ea86029af014cf2cdac425499e956134404280c488c35be96c679b695e54d0f4c1465f2a4350019dc73140a15a1b90e1f12821abb6bc159b6b49ef4c2f23332a139b0de2fc576669c781fb092abdfa931fbf4560a55413112f7a3548227a4e9b8c7011412d65dd973e6b30354b0328e8c59b3b7fe82c03c63ae402611ac4f668d2504b39a2897fcc4e0e3faff88965e98fba0f9118df872043925e31776fa583fb98376a50010e162944b735b88fe315cb8b11eb9189a04e986d9cfb10259aaf44c63daf26dbdfb4493df05815ea8a56b4af1338419baea99f93b67e1e1aae0a1a0766add720427d5676bbbcf4407447192da69f5a3147c16473efec972b2088fee0e4b0b4d85b871fff9a1aa71e3cab3a28227c614ca83f2c100fb2b23cc30360682daedd4731a37308404f7193770ff74f17ce41b67c562c82c5aeae08905ef1c7b7f434d0681f4bfc76e294e2068d10a521cc9f41fdaf9081f988298a3aa1224bf8dfc3dadef8604bbcf9138b0117181e4e00b1c6e562edd022a71f469084b363f86a95635f983b2c50f26fd353fc5ff8e24d7c10dcb98eee34a57749f2aabf5312f53c0b3cb52624b3f20184d1f432b4d4c531d7e6c49b9522cba946e0e2ded49d6387860bde881f1398949109b6d45a537ad29ee89e3ea447aaec006bf205a20da4de43a3e7ecf89bfdf94fd517c34769d8c970bb25f10193a1d6afebb9f22e0a4234f4dd0ea8966c6f89d3420d056e8bac0336cb2e413ba7d82964bc2ed7caab420a714ac56619bb8e1f3f591725cd1097b36fd8e05e65d5e9b444f7929dfc2e04d89a01358f085fb9a7adb1865a31dd1efea325ac8542a5029953dd84082c1bfaaf2ace63b39d7278004616e12a77f71e864094d29b795432584168f7cd96ae9197a84cc24aa1022b2183bee4a85874db0d61e6f081d5b8644686b6576852a96e327f2e084198729c727e455875db82e0ff7ba5e3622a6b5711d47b1210c7acbb02a79690020be4e542d51e8f16e8bc1abd60f5c3d04da8be31461855bf90d0578f8c4d3886bd2e73ae1bac59dd3b5345f01bea0a46c3e708842b09c494aba889991dda6db268a264210021336c7c59a8f2fc132b0ddf03f778c3224b897f389543c7caec880d2b4a099f86186293909ec4a1b11967ff11797ef0bb9dda79183c84865c94b1165292fc298fbaf642b4501812bc27e5f39e2c47505ccb854da8114cbcca3cdd3069bda82e04808379ab702bdd89f5a7347fd233ea3a0bd6745b4a781c9df2ed7c51761378569ff90e573074db2545f28c34897e8532ce1b4b5ea1209984b61b01c5984333a48fcd71ad18465cbf65ed1cdbe8d67cc7e6d0cf1306adc9eddb37792ae0c6a8b96fbd46cd369c743504c0e2bf57819ba26fb0ab885c00216dde0f93c7fee63d90f0974d4e58082c0603cdba6f7460cf5f8f9b1d8b7e96bcfad6562d07819543a0c52d9bdbf4bbcc6aed776528b7769cd30862213de4c9a3cc64d46b3edfd0d5a5bb69ce2e792dd6dcb7eecd2c4bbf2844dd62dc63a4b512f78d422979ae6cf058091d65b6214932805a7c61c16fc01d8348d015795ef5983522a1608f2c35ecc427a37194106c1911134ad53faadccc477e3b3cbf11bcb0c30ca3f31e0efbd795500a8f69ac646825753436ff0aa467f00c20d7f6b076b435013eabbe13471445766b091b47668c23050d829f643276d005b01d48ae36c0e132f67f9b9b920b7c6c5ea1fad5fb905eda7841062f069f06800e854ef45476b8fcd9ec63fdf1dc261f209fcdb3c0b5b1e0d5b19073d02697830fa4707f18c9aa373892da30cd09596442ce247f229d13d7fb542328f3fab934927a3a7127b03a9887f2d4511d1cd575e2683f01e0ebbb40cdcfdbb0a41eb5e4d59fdf49f8affb473b5e4fc81100a52c3cffc7ac3ac7454ca100997711918a745e50cd25b3af3138268b63a438dd5c7f9bbccbff0e7db841da56dc5bad09f91fcd331d3edcdf918fefb56fecfca2d31ff8eed0118b17aa1f23abb6b4b2de948959417b7a3aa78fc56835ca026aa0f74fa8e213c77c0c465d8ac6915af6f545fb5e2b6c10c33bd7904d1b487e9cea2edc152e94016b9283a6ab87cdcf0edfb131f9fbf96199d9231d6b47d447d6d68e2e603265a832ff97bd2688e74b92748ad4298595b1095751004c5ded890e1674b4e892013a44b6a3078e866fd0599132e848f28a067e62194aaa17369468503aa4f46755c535a0cdd09d45d29d022393f5481c9045b0668c1571d0026fbb570211bc61c40d0602a6123ab158237bd37ad0bd87b278e44a58d14e9687433cdfd3c9d317112f43ec896cb9decae07430cde177a18fc8508efbe529d264f1f21293a74b590cff576749f869450062b399b0f5b24cfaa151bc7e9b852f61a95422104b71fc6fde2f0657a51189c8d76703f77991da3d554918275bc3148ac7941d09fb2873a830e07ea86e5d642c9e54c329e1e914084ef106a71c46d04ef80b4289c201d0aaff03f6411be980775e7d892b33f84a18a37ec7cb29a59e9ed80bc01717a6b211f9de4f522a001457dd1f67164abcbc25b9cfb9b9f1798010cd3426a48bb5f0e3309769a51c06ad97f5d77a3c429a1ee3388e91b802c1488690e443a94c5ea0a8af448efbad871c0fd0e2367f53f2db134c627d4157d9a72e1e182d7c075d1823d0f470721d998e4d11a5575c5c366f0db4770b782c4574673377699a4f5bc079b2c993d247dce33e2d717fdf32787edb6aedff11130500f7e98b716c7721b98502913cbdbb23d7e86407b9abb1a1eb9d3f518fa977041400d22563972b56ff8c9337607ca3c090c40238674af00090327ab5c1a1e6d3ce5885ff9c354b4023f0e6622c5854b942115d4b1f65f353390c0530106c47d1313526e88a35b684263afcaed6bbb031295e45ac64671d7386ed2db71cdefa688d54c3637300cde12196454ccb232cd456410b131b627d06cf1d308e56d1c69a8547356b1f995947d8fc50590b359b9f4f9a50033f8bad6795ce0bb99409a63f422d6d8efe6cc45ed33829ef77a721d8756119207202ad3b3db0423ad0d4eb4f36123e97c1d231a11fa18f162935a6f05a0e5a2212b9886a15b91c0beb037d9eee5dc14652b6363dce27004a1a659a8617d3c5c124a4c2c7685b95aa14f7a525350e6b3a46e35e72503375ab200a2aec75fc01b562c6bcab1ed7e2f049e484169412d620a339f521142ae507e45e3ca57880a0f385f7be814ec4275275b9afbd145ae26ef43a52cbf985b992b5f09ed75e688072669aac02d3cb37ed03bca726cf33455e58797133febb84dcdeaf00b78e40b30b74a28089882ff27aa2e205f18ac9dd42bb008ea393a6f00f6297d6ce576378a0f13f5e87faa690cd454cb5d65b847268e62372a89d91d246b312d43e480ad6ffd924d888f6ec65c98fb8e32bb5bb130575af2005989be1e2073e76be402eb1844a1b137f205527017d1e6912084ecc31e905fac41fc77a3151d37032768be068107ad43dd07a2b89260291a37081a94149c63af5db1e9c2e9aa2e7a914580f46f445e0b0f106e0dd1a7204200ad63ebce75f1cd734ee936def6204f15195a12109689387b378263e99669e44852c535eb089c543b9140af21c0ab534053006d5957e7ab35487cb5986464730db784a8f64eb51698c92ff4b0a371c36a5fc60bb2f741c0e1ce9af0eabc0e3166218d1df7d90182b2cfeab5e5d5ae45cc2a702e9d9ee3c3298ea90b499cba0d34fccda7b0d4fc86076ecbb72a657d41a28d82ee8615f8e4f261fdaf3c3cb3576fd7e4e5b8651aacfd83199a307008c51f3881233e705187d9a5ffb280c65e734ab2011b7c02c5a064e266c0f2ec4c98920505a15022e39092109aa85d9b0f6f57cef2d476b5589e5929a2da97d50272560f811390b65543689dd180c999f5bb439b37e489d801459dfbcb81bd2365a0116a330e05b3849fa0d23d9e2ae063584d46b27a419cce71c7d3645be51aa26a24039485904e45229e0c86d2ce60f3b315d5af5601ce6018ca19c949d00f30f6479174756433aa539673945b265f9d2c3d0bdd98e071f4389af1c4a80374e9a1670a5a58b6a13e861551e342d686ca38c2df8c626676b82c555a77bb196186e7dcb1e0c727133066e848c161db31ce1f4a48a77e01a0dec76c3130b7a90b1d71ed44bbe8239bf45f51aaa535d5618fc150f4191c631bf5baa1fbd4219a7156e78239a15842580dc004629712a735da0682a13b29eef6a2e8e3500ebc4711da3709e3d743a66e392de68435951eaec1cd571d9ca847b71b2d9f17103fa0cf8b5055816f3d8816ba9a69352538a5048ed2f1ad642a9c9900d0b98524d368a25a7b4b308e45578edb8cd4848299b083551affbb31c5cb057a5e7fcd5cc4790abb108d4ba5dc93dad5fcd8417a36c5630a5d1b878cc41842ef3b35322f41eca2136bb2ef6e016bf27654248d4d1fbae46bb4873787d945b589d452f0b8d66c74d0497f9ce525a93ed04ff21da8d358adc98fffed6125a6337d6cc2f524089a7ef8d3543675d67329ba13a1b6152eab3190ab2414df97951e7500cf23720cae206e31c3ad24a9bc4398d56fec5c5fd059f5590634117b8c5dbbb25744e214cb2041f0b67ce30521ab6c9327ba97553c6969263cdd2ea99e56cccfc8b6354b9711d80fc9e59330ffa3742bde12ff55bbc3d5c131e832cf53f272bdc422c631736b9e316004e61776522877b9e8d756609918cef4538ae94255b844479222bfaa19ace955cbc1e64df352d3fdc85e0808e18c29fcf56f1de562d119a49ff6a92ffc61055d77136e8df79e33776afd884ae67b57f3ab31c053bb1ba93a619310de2a5664377f314b41f2a56e706ff2458a06ea22b4dfd96ff82c5da4c3d5d2dddde580f10ee2b4d69941f25ccfc3a2e6f2505e211b2e6bed988285a89d17ccf28745a0fd5a68a432d43d57ba37e28c984657096688d1085ace44b06ede653a253a29a6fc6911753f9df99da49e0942e1d0b0e4e5f3d2884fe8080f171e78feeb686c45040399c67e315017b8e209ec4eee2e579b88c296dc0ea335f02377e130c642639b72acca6bf7cc2b9d3661a0a33c91a950cc7badbdd928027a2bcb0e1b6d327c056870e379ade58834993a988cc30a45ee255fd289ed0ba9f2a3f2f0202ba26a6c3b434811e3cae9e814885edaddc52a102b721170c8cb9e4054b6f9567c920522b2c8f9290a702a3f2cfbc70e4ffb07cdb2dd7c52ea03e9d005bbf7ed6953856b83760f7810414b04cbc5a170d42a334b9fc5560f778b99aa57ef91f161d86a3719c72257de41d6e98ce255bbd73c963d357decd136aa78a184f104210cb46b2713a2b9c26d10be13e1d222afac5f9e20712228536713e7b43afc0fc9c61d4b57826a01d57503e89aaf867874d911512a5c6cf4eb83b0027871250d3ae82322212b71d152ccf6f481796afeb14bf88855904041001f0bc0c08252b1086d43fd4da006fe8207cab1d0e5e64ac4263756183fe13ad057415deb74a8a4fed7015c46146efd6c18d34dac448e51a1ffd205b64b60c2a382a14319189b8ea46e5db70673bb3dceedbf824b10df6ba45c638b458e04d5e9f267deafaf1f5cbc6618500cd54f92772a94a36bba72a2b0eb0e7662888c474af85d0b14fb4bc86ca9938f95ed21056692bd71de826b9d9742b1c8501973b1532789925ecb741fa2d17172c299794afc1ef74256b06ad3f34a6cfde00d9cd9a706541e04ee24af23d23e83ac7d01e9dcd8ad8a56d16ec4886e3a44264088d8fe50c362a48670333f6f2865dabab9d7f1b2d009a7251a75cf5cb14d88e4d12830f4371e5b632aa906207d0364c6066249ea73a07d0dbc6b307742442e9184a4cbb8f92e0bc09a9e8be8da72a18b1b899d47419050838cdbb9abbb21e91e6bdc8a49052ffcebd0e1016daf654af1d361dac6ded722ba6b4fef46df56a20be81b0300e5fa5371de066e2634bfe57c17e523466bbdb1772a0cc6ce3c516bfdd2a6a62de987f4b65f0dae1a6f7ff2d0268e9585ba4d08cde1d032b294005775f56f605c918b0bdf3937426d4e05a0d85253d7c433771faae5e2a19bd5f7dfc9878caf82cd017e2931508b4acdc94b637b4bfb0147202b4a0c35d9fa9c72c3e750be33df5a28fab12f8f8c0589e40fdd0d7230055a4358bd9e969609e35bd083dfb09c195a9a8bf11e2fd3e153bd4f4c0cce37184034dd85b7b99429367a5c1fc5d66314784655b37e04acb3e0b94cc92928f7a0e5f1fe090952bcc29023e5688c5a7825a6e4889f493346d65a0ed2eee81d42cfb90e664de8e76937291a3c0e3f1ef0d0a51e89b9763d4f9dc6841393ccbd17d2b7e50ead6af5c105889e7d32a4f083bab9e0d87db93bb0e1b92c9eb5409a7707286b409ea1238faed64d12076d095c04d383c2c8e881875aef624cbafe9f018d3426f28c674f48d5009aa3c1b1853ad5a06ad90ce0be880758d030646fa03816c59709485ce4e051e29bf9624cb87634c0da9b565b097c58accefa5bb5fc1738769b942abedd215deb504f526f6bb05e2ae30579b82b72106d800e83faa5bd74cf33760802a7c165f88e639baa9b1cae745006e252af32ff8563b4f12e6a7fe783783e9fdb813b8ea9c39e3a2979ea8881a33e9c63166a335541eb950c3b60709faa1528491d75922de15d2126d4d31102de51b087cba33bf0133d740bb600ac2643c3c5e92230d6d40bbe7d25479cfb2cc01b4eb0edd125bec276a6154385a5c78f2605a99b28656e6f2898c4a31d112c106097a20255406b35fe1ded428caf7083c08d4c1cb67937dec5a05dee428a17e85d6d92c22c24402c11e2f04c1493fbe773ebec9afac63eb773306d5222c02b2abe2e65cf37587c8592dfc5adb18629a647b867b5345ca0e20f6c69bba11136a3538003c46caa9c2dadf6e1a94f7484672a1cc9150b19a376a8fa7cfbf2121d4960448757b063b50d9e0dd3b1ca8d3d6c0badfd961cd0930b489128a96dd86917a8307a77eed3957b50c3b31bd40a583e3aed35894390defbdc60bdc211a8b0e6f175ad1f7faa86fbe9f9e40d297790c983311bd1d22554d214babd0d7e1212cb3cd86d8227f65feba1779dca2069c55fa2b8e6b180b8db1bef9222bf38868c5f236c527c54e2e8ac6f3ecafd044065fe0e00ded8873cb877373a5114fc426a96ab7230aa16982ecceb85b0efd92b33aa050c3ebd5eb85dab2cea3610dd40e5252b51dd98e480908cb89e41769fbe92f284e50e6924447dc3a920882d16aef24a42df494c94fb3801d9224807c53f25931030a2075c8b7371634accd8ae3007e71c2bdfffdfc9ad668f0a075c1fe98ef433e398c882b4ce9baa71adb0f69446d276ee2d3aa0b723729ace90a3a679197e9c60bb076ce2024ce2b3b73e73da686e3874f1b32bf7bd9864a18b1b28d20587c6802017e4536dd2db7c9d03fc5166a5c565f69de8876c53fdecce609c581a04cc68bca5ee004927971d714043f5b0ecfa0ecf77cc3b8550b9bbc46a3986b2a006109c89e09d3c63fa43bda32694e99e96efb68c98a50c75cedc43b4182d906ad5b06efb6ee592539d41e42847af2c949794f5017cdb457683d9b0a040355ff2676d12fe9a2a5bffc4282cfb9e8fc3128c41a6ec500b044dd660784e52cd33f813015f926d060909914ac747843b99f55ad6b37fed7d382f5c29d0cf8496cadef73dc0684353d1f84bfcff93e9375c2ca361d94124acce655b0c862bd50d95df140b0cd9118307db750984f6c2063ee288936d691871d31afe14160028e7f1b70ed002497ab07edc24211f2c6d9db10fb6cd63ac9bdd37ef32c0fe822dbd49589f456addcb3b296257e0d3018eaea025829bf6e7e7d3aec45a181354568a176ca1210374e5c316a536b508becd2d893a50684f79b0efa4211b9478360829e579de0cbb340c8a960602c91dc1064f5571f8c83b3c7e2c9464d6ca54b033a900c388a56b8f4faf85a0582d285d26bd318ca0b68e0699aa5b9199a1c4b74a113aee04f457a8e64258ad44b9e1b2de98c1197959ee4f195f9290fa47f19d3ee4920d48165974ba5cbdf62b9115624a29a2a44f694253425851f4cb8b40e6c56c5bbe7af369554a1cb8a71ef9d728856f0d1e1c290a36915fbfa5977652575e23162a405fd33e0e7a83ee104f9f26b924ecdc3c8ab969c78f3c400bcf2756298fbbfea68147aabffd0d8584cc0274f4a86e8fbde282d42ef1059bd5024ba4ca8a4c29f6ba35a923562eb5af97766f8dc5ee8c8ade5efdde4d7716bd6d66141a84db11c05903e250cf61e738bb32225e5c3f536d61b95c4afc004389614d599a768c9fcc51a837922453d382e14948422635bcdd02069b692e72b3108ccd1b1de6b8f7ebe7549eac512e58489e07c868eb70481eca1e4690e85fb1f98759126cdbbabf4cb3e425da7d4b203af5103366973c96ff3d794b856c784f2e83882923964f1a47d2c6820b8cdbaaa80830f0cf119a89474a54b24c114f44bc55c95f50f8b39c7eccbf42ab389b9ff0797c57a7913f816642e0409f4466e8cba85ce55e58d87278f5b1b6f2e329a007e5ebedea4745b165b84dbc0b50b03e203e7b6972cc5b6eec41154c5955c7cc46ddac755a1b7f8ca01eb4a2a658a97b7dced0c8bfb3bed58f9bab8cd5b0ca56d1df925932796a9ec05f0393948584c557b6bdadb4cf943b60c2f0b36a8d703691b7f9a1b570a141c3024ccda2c3c4aa5a9ce8b5831abfdd00acf8cc1277cdf58a8ac2f4977c2ce55a457d9af0819f475189b966e2103d8f1193d5f84d23e483bde96b21ae1a282765f6db023d805ffa272f4e0e111c07f29d538a16e5653abb242bb7c6efa0af422ad19f733e35ae4dc03375125e23de3ac3289378a02e40dca7adc5331f33b00753b2540843e288814535d375b4d33a4f9b44a48f28583bd22438e2115281acc2a79324517e4fc5a55d14cc99ec2999103de79456365bbfa7abe9ecb0f33107e4da79a4fcb3ad261f4816bf7c12b8bd3652d683b76fc1624a0ec69a6afd38d123ad29d8d67fab7eb88ac9636fe9eb597f05cfe7f2fdcbc669733b6f29fa2ad4d0c41a7875a969e309f54112b503280d9ff6592750d485cec730c65a895ff8525a279eec00c4e2fcc6ecb257eef949e786e7a44675c47f28bf451b2bee62bfc40739f1e2eb489d1ddd27b78b7635127d3d692f2a778145ff24b6c57222088f90ebca43bd16bdd9dc8ff2eceecf12368b34bb16760ad7eb296d9dff77608d563d9321ea73bb23b306519d560bf81518613c2a5dc3fb1b1141d62b24b62d3c4f21b0e185a9466a13b02ca0aa0d4bd3c49f7c33e303cba97d3376c930c6f03d857991d0244d8c7804993c2b31b1720892b1a8596f0b6125a8b440e2a58952b0a052506dc84ed3f98639684f558269a5c18eff5684ccb46c6a03bc25d16b569494d21e105f1eb00046ad8bc8d1b818decf434226785e8bad73c5ba29b8fe218b6d937ef01f961b4475cbeef820f03838b1eaf92cbcafadac8428b1beb6463d8d7303c05488402f348fb928c9fab3a48c1a0b80d6600f67d99980cf8d2c7d6ce3351d5accba18dd129d9ec4a445842cdf3e60b2c849160869b6c90521070f849902d3880e43ca36ad7214d965d66eb8f6d56c07ced3b1c3bc30db45bca042e87f5b8a92e963b1e6137668aad9eafa48d01350d0b1b0a80a6d41be398074ef751bd3629020b6439a46bf4308ad6c8a30d96e3fa3686221cf1c723713b984eafa1911e9ac8ee959202af9926377de9a9ef869204633065a928825a843ab2fef85d9c26ad1651de7f58c4f851b8877ecab9cd310dbabf13f51181d6c8243e25a444020145b967494f79130118c67798e9cd9c13e9445404a6b770777a849e7773caa0ddb31e22c0cc94640a2a5a903ed3e79f20689ee2bf68ab12bac820c65f212951c58b46dabbe1e86e28c84149f356dcb87f619e8768aa997a7a6997f840dbbe689e9bb68ca40a7182ea2007ab14d27a95472b2ff12aeb6bebb029d486daab3588b59e92f13dec8980db16019a91abf17689a71fccf75bdea05045b38d47836a92a65e90be08f6e867b37fc709e54f8f45e4d1fcfce1a3fd9450e59c5ec4a3c1b9dd3ad2abe76e4fba8d8e5c1fd8b6029c7e4fe87db386c888df39e8f355ffd1f6b273e70061d5476f5e8ddabb6937dc3b2f26cd3cb98093b40765577b327b0e442b3b3bf1d0d72bf8e975b518d47148c22336780f9396ba23e905cf52e6416a8a809292ed4eac59095e409eaa2c0da3a1667e83ddd023f13454759cc8c1f0959f57dda3c6e4b0c09e94200a97b2916b87227c430c5894e3d44c623a1187ae569aa7102a91de8c37bececd1ca6b3c01cf2d9a8a3004538ec413d549c9112fcf71c39b17ad4b20bbe6fd8900bd522524481261d286858b75b4f05333726182d797f8a2182c24bbab99680a8765de01e02f252d5b1866db68d88b6069b5bb426033bdb222e3b66f8dfd03e3d9ff884a1c2abdf7b4e95eb1a0dc07e73b5110791cb85df33554a20798eebc137468a95368545b836b9b8d07e317533323d1f6e1c93e8eede4399e62388c01b7098e477752320c3f340e18b397fe1ff068d6aa451e102a000030fd2ef82d70624b457bcff8e34ec3a732c3b0004f46bf8c5285e40dff0d837999d41e2cb6357c87a4042d13653b0a8a6012845edde3c64f8f355f1c1a482fb45f17023b76f9f6abdbe557270bc8d031af0c29856205c915163c9784ce6f0e02153251f018207e691c2290dacc444971ddbe8813aaad28065c86b42a9902c76e783cb8373a4f228ca124f3502da097056be94004f778748dc125a698d5bbab0b684ec5d42e4de5b4a2965bc092b0ae008ae7d8e9b2ea18728b157dee77d78532986b0600f31b6f6a9a914434e60da548a2121c0187fd98ab00dd9f3bc5ca2750c7b554ba69289b5ed6f18422c984013b8a91462a8c2994f4d08c3125db1cf23540a30f0600f31aa36fdb7a9146280a24469e84d859b4a21861fec15bd0ec581b78caed083a2c8a3b318567a2a5e2ad4a3b4a9146228b2e3093050adb5d65a3192c863f7bdf506091102b6a74a57ba8e0a7942f626e4099586ba275c0fc4785fcfdaaeabed9205afaedb07ee6eecf56c30d6dbe15aeb77fb6ee8e6be92b4f5a76907961c227f743cf0be0edfaf0aaf8aae0a5c9690baa27badfd015e6c6ff5d93777b8bbb7075554513a15b1ef6bd9d1157c1c161d06f67dc5f75d1190dac48f521bd9bdf8c3daf67bb1699bb48d354b33323694a6c39d57c2b582b782d7fb41c9764f785fce7afca8a75d4c19393ad8cccc0439c12da94ea2477eddbda6f3ef14b472c59ac1feaa8dbf3769ee2467922bf9fd0dc5c97e9f4371b1dfcba0b95b0e851e6dcfab53183d12adfab5aba68bc8918bc831a2490ed670e4a3472eb8aa960d234af94db199a6cd3c989272bb2a438abd9f37336df7cebc19cbaeca60bf9056ae58333dc25ead70c455ab4ad33d92b2f1bd177726141e41419d07de760001795f68de9febe37ddf13590c555affd32740821c2151f1c3080a6b5a916493bcebb1f4a522169395610673bd5ae48949091a461fa020a11e7808fbb837d3454291806128eaccfe2a59415a247d4d017385332a9090ec9050b442ac10511475560755337d912e1577c84df2e95a458a308a4646f9ee557991a0a825a1c49ddb9e9557c811b2a62c4da4f2ab725045ff3491b2f68767a22d7df19e525defa6543f80a5a4d846ec084b1399fcaa4aeb539358b548254284a5ba3d8815e11f5058d3063942ba37278c804421d375a3dc90acacc4e4e4c59ac1465c2d5275579e118a009351592c968a0208101914464631198dd266337ab3aecd342e72c29a94e695987557de0f68b42124271a13b1f809d8880b1bad701b70114ae3fd09b2ac3fbe6badb82f92114c07ebe96f79e2a25aad5ffd487e71af581a3b3133b226fe518b6ad1d7fa3c23b0f445b2a6903be4a56f91928835edb5a2b45be54df28a70916cacafca50d23d5679af4a131216c98ac427eb1aa9e94b5f2acc2bc476916810f9706f366e0d3187bb69af345b49092e4a81e94bc588ab45cebc1e386171add76dbe7d4be7dce41293b08eac9b883aab7b5957a58fd036b37266c6735d2a4c52589ac8fc4c13d97ea271d1bd95686c746f407014e661b6bf341169e298dc551295b44a229383e6c94f4e74a52ff72766c9c4c4a4c4e4e2a27b72b58b694d96eaaebc1fb4a8eabf7a31906d7f1243101fe9e087d874914c9d4f697d4041c9d0d1179aa44086298eac697774ec381af6adbbdfbcb8b4c8bc7670f64d8d6545469ba1a4dc62546ec660602de669ee944547d987ca07b4614cef600c73d0947230941d3485a8b08e37e6a0a832c069fa0037358da3e802fca607f0144d80c7e81f0e03c4595cfe1283ad565a56ac95914f2b40db2222ebc3b268b52795ab32a26259adb4ac465402a02d6db997d166287a95a2491935ef63f4e9ca1da9797510ac832b170cfbb95828ab15aa75db303516166b74a91ca6c6d26aa556645e42e89e37409ffff44910445c57c6f5f25432ede2af7b182d93a273bfe91747d13337551e83c9c05aa793910f94c9348e28140a8542a558cce478bcca1a502c081d83808ed9d0b12174cc87b803a2618cd1662829b798980983b9ad6dcd404243794dd449898139f8d0d429bf699872a7ccb4fdf1f1b13030303030a73be6a4a81353d211e63698181871064c696d900d4a4949494939dd285f893a28251d535ea6a0a4a494b68790905dc29a4376e8af85332dbbca5d5e5a6080d01524c864b2953843263bddb48f883ab4928eb293c868b28b336465b5e2624d4a7a6c8735a18869ba6b3065ac8735cd3206748471a679384df77053ef7014adc36f1a87a7e81c1ea3671ca645f3bca601709618ccb67235c0a32d5db9792593c86cb35c02db299a4801bf95265588354b602f077ce680b21a11d51ff7669e8460ad341dfb412149b1a9c82e0425ac798b76e1a263fc45e7782251b5558c282a369dde31bd63537362dc4a94a8f9d142d78aee83178dc004b4f3ed984f105634cd5974cd6bfabe45dbdc25c661b4f7182df3148dfb4db3388a7e7153bf4ed3339fa93c068399316031a01738163d4c204848b9dd566a6860b1eecb89e1c2a6860616ebbe9b0a0a0a4b28d33a5fd12e62bc45e7d8d4d0c0629d4e4e0c17363534e5e9f9a271ec5ed7dbe9761d53fe22eaa0fc45e4c1db3c485f42140ce6454023a0a66d31cd9f24c7b1151483d1dd8e26f37ebcb8d492942811dae9b659beae22ea78571179f0065d4299cef98ab6398b66f19a76f1161de30cd0437c013a883f402be0d7afbbe8fb17ed5d45cbc0e8fc98dc51748b9b31585189094a808494a8f9d962fe244721ada0594e0c588b9958508cc7eb7a2a2e8f8951390873981a0b8c499ba1a4dc62ba96cefb5c5e6254bc24c0a8dce5a5056606949a324090d0813bd780d0951f47990579062a954aa552ad7a00f9fcec08e221c462e565a6eb8fcf0e201e620d0a7ace00124931b319da4c0e2288a4e898c334ce3bbd9ff5cd13a01d00930005c4dc8bf9f71a73afa428e28c987f37e674df7e13756e251d636e13738b6921ce88295b10dd24a085e883355bc06881c3893af733a14cdfafe81667d134af69166fd135bf96b98bb681d1b8c7e8dc4dfde234ed7da61283c150527800639165f2ebc7bc3535aa969879184c6d05f51da1bea3d4cfec23c623e6f3a36340d6dc11a36341944585a6633f3534a1fe8b36b98b3e79024acea26d5fd1242ab49419ca2d66c2f48949898d24688b3f5b41319fd84fadacd5a3a478b420f4781389b2026e36dd1236a2694df3d3d6052efc74ddf9c517fb8901c188f9fc18ebcf988f4aca0b94172f5e24ebc56ae4c50bd87d7937044da592148ea6a815c780623e319f284a861060ab900b4823211e3dac9035edfde80ef124f260ecd9dc9a7b713777676e8b4b732fcb6fcbefbdf9ddbf2caecc8db930f776d7bc2ef7e55e18bf29bf2abff794ca567e6fbf2e7e3dcff35ce89f26ae675343d362268763211303f3e262766017b6cd43271f1b56898844558810eb53935e2e50473f8aaec904a20ca9a48cad144b88a5726264e47ee48e78e1a74760a8782e5aae56abd572b95c2ed205caebb56f80688a536f1d59d16aaf8a45934074e5c89ad65662b3ddfc49d28455c4f2716f9625f4c253c2f56aa69aa966aa996aa6daca68b512eb0f16abe5224bdfc76ab55ce48bc805496669191959ad56abd5aabcd9293054c8a14b0e6d568f5aadd672b9c8170c0543c573f1a35028144aa3b8a02e927cc16227171707e7c57772729696500c435114455176b37301361627e24493cdf8841a55a20eabf01371e840d319c42e750f7d4c16a609ba410aa26ad963ac62a786f1beded3ee94866e7a73a1bd75210dad26655b18f16db82fcd48ef359972e828dae799ad3da519c18b3befcb259377baa8746a1889e0335093b8d3a26166eb7662a269b69b33174979d26ab55a2532d84d5dc45d359869be60264cb7fc65ce4860b2da1d1a1a599554b891999155ac55451d72361293914c74ac0b93ddbf62b776ef7dadb05819fb3ef2a04bbc018fa0201779b2c10e2020f285f2717f4ab4c98ca484c95eb19a8d5686972b37d128d73ae5249a7c4db73ed32a8f95d87e0293b55e654069b9d030969cc064af58ad16bb8da64d6624256d16ab99b2daade12422456a654dffa28ec9c1560c68d26730d98ca484c95eb1da4bd30d9bc9622d4b5fee6d252f7da42c66ce4860b257ac863b993981cd5eb1db8d261493cd68544a6ea3699392e40426abcd541b8b9594243398ec15ab698d8b743e262526f2f2623b41144080c0c0940441616454bf5abf5a96e60c267bc56a5f59bf5abffa95242630596d16f39ef8f1c3ab5fad5ffd804451ab4ed4a222d98c04267bc56a65d901c166b132f0c8cd72b11c6c260439ac695bcc5ac45ad466af184c5603e976236b242435121308c6445eb3c76edf0199c5728d84a44652fb2cf6c9be1ac989a6dba4c4362329f561b257ac469ba996a64b394a49162e6431ef07b357ec3059ccfb810bf6d294ac77dd2a62100829482100fac741d73e43d3669fc941048e226ec85ad8f4e4028003cf0c1933361c0b990804a149ca17979a981e185acb596ab7a1995d5612001c7866c898993908def03a009fcdcc68b390e5e0491f04c394ccc1d3aad40f38891ee22dda017fd1429c4537e02e9a01afd10b38ed3c4e43e011d017b88cb6f1185da345cf7320ea7957e280d71a7042c0c60586788003846800031650a3956bb958b0ac0f223b6487ec8f96688b5cb4da13cc8561852d16b9162ba4cd82d01a93e85ceaf045af5c6ade42ba5aaf9defe54261b150e46db3d870add6f862b1e15cae140badf39c8e4184a6c073dcd048bc085d817ffa848508498618a24e2e71cc0728486828085af3f018fda202316ed33a3ca777380e6dc473d0b216dae634da7bcdcd59f65bb40b0abc469b91942edaac02ae183d362e710744c3c822295f5c6a62de5263a991fce55ad461a989a9952d9ae52e3ae6a5ae56fcf8785f4ccd4951a7c624a62626a6b43b808058585858584ef7cb57a2ce4b494796db585e58b43883a5b43cb468836c8fb2d461a9f53fad6fe4683d3d339a9222adb5fe77f5e926f988a84352d251bf4493e88b3374598b946225528315d9b9256d064bb2b5943022a53ec0493402dea20df0174de32cba0077d104788d1ec0735a00b769051ca71320a381788cfee10678ad000a4800101610328448912498153e3fe8d88f090b01989cb0289999d0ba13170a50a0ac40eecdde44a26a64548f82f4dcaa409af5a0a4dc30c76263f195972445ab0409d291942bba53414a18a36d2ea36fb64de7e4a001d042fb701abdc36b740c16ed95fac56fe8235e84aec00e3c10a191380574c5c9a162ab4042571f7588567bd281a7a671f875004e014d7174a8807ed001c0830f3b0829c2d1b5a1ad6b42a61b2360bc70a113c3e6db5e1062b4ce65344ece715a87dbf47dae44c7780bcdc369b4cd6bb48bbbe817678141a2f7cbdacc8636bb51040e227298363db499112612526e37161d72706a9f0f3df0b0830e3938b5ef8682d2126a0d001e7a784efbb0830e3938b503c0871e78d841871c9c9ad0516e1d590e23eabc1c46e4c1bbe5208509513596a4360811c3775ad0f4e46c381632116891bd845afbb059e81d8ee3e139ddc371680a9c086dc473e89ee7a06dbcd3e87b981a1867d199f61bba022f421f71da6b38b59394b3d93b9cda91a0dd64182f74728c3ceb6c2a7004058ce851a1b98c0cccc19892205daef33e9a16253e31202272741ba68409f19210b6c8d17a7a6634252c38162c8a90a0d2d58a868264a01e30884092902145ac88f9fce0589439929cced5f44cd942881a48376cd09ac6e1343d63a679dee91916a21e231e011923649e65eec93cdfc41932f7644c99bb9091992153cad0882e2e374433333ed0883268767664dac5658c380b7d739cdeb7699be734ce4bb4ec2d74ce69748cd7e89c447b2f7507a3897a7a9e3fabd1662c2f3ce8a9d166308c78e182dac8ba1392db883afae02a5763b1b19089008b106bc286c08a24c1aca03018d8109b1a7dd242abbcd33311d012b88c361fa34d6034d1044ea2897a7ea3081c44e8d326f03c81973d7f7121baf11a4b491481cf5626b0a29282723382e224c444c386dc6c654da8e486628b80cee60d4d8e2e7a5cb07881833143a2614360446e740913b2535060dc340c18ae160bc66a04060c5a278b820322680114a2082205288c2060eb0c86e0f3c5f8e4e8181fceddf7da5c17f7c585716fee65f18bfbbd327e67fcd65c9adbe2cedc9b6f77576eee5e9edf975f98df1bf3bbf37b2fc3a293cbf93df97ddfa7a37f9ab8df735c62e0ec1b182f5cd8d4d0b498c9ad745927270735f27c3a31725028140a35c6ec98725a605ec61466e978dc11ef4827268bc562b1a01932b28cbca2d5deaca34badcbb22c4962764a2d2c703794db0d050505052565860c9515928e0b89d2641bca86b2a16c281bca56b442f229ca392c56cb459658ad968b7cf948d221c9d7ebf57abd4e64cc78e18129296199a6b9a222fb8c49414c50725bc262b1582c13242a4e4029275c24f982c558e8d5d0089118d749a95229954aa5525dc60c1595bff0c0e852ebb22c4b92989d528b4e8e4b8c9b6d47d95036940d6543d950454599e545a780523ee3914e2c2693c964b259cc8ea893d38242a15028146d4690a25a51ada85654c3952b9f22162c453e3e453e86923aa0d7ebf57ac18076e8f8e41c2017209d56abd56ab96276409d9c169f22968f2586ceb94b8c18df89e972f7bd963f5a4ccc8d89892123b372f05713e9fcd3798c9c171e98942a9552a954aa91989dd57558725a6e46b1502c140bc542b150456390fca24b9473169e4f27460e0a8542a1c6981d534e0b4cb9f2297ad165e9a28974def2183aa5924e914f91cf8f1c12dbd54439ff72ca94132c65aac8c47b0c4d71be154f2505e5669e9894d8484afd1a6d268bc15ea4abc55a8da85223ea2486a0e9e4e37dd7fbbe2b851d34c8b6020082116a158c8400527aeb7804fb630c4d504a6ba5011d830b587082312431861f3ac40a4a69ad55ec2146d83ef3be43bc40299235994c60e882222a50137872a2d2aa12526db5292ad86b6f14f610e369df27dcac847cf1c562f80a83116810c2d0836f05e0bd37e3e09e86c2d0829b3795220c270803083e37ecd5d5a522dff7dd6e0a5c2a954a224fc651afb875064c800109f76a30148196f2d7b5e1de1ec623f0f64a1cf50a4c35a52bf4a506012f813e4731edee69208ce3bdd33f8c38f43e36c3b46f8903b589bc532a6c978dcb9396f7576c4fd37bd35080f7e61a6d268bc15ea4abc55a8da85223ea2486a0a9943fafc3d7528ceba6de152aad56d81780f5d5522c527ab10b82a0369562480e70873bb2893dc41827654febac1e5c7805b586a5a775d6532b3db856cad3eed6b1d03efd7496563eb9ca9e560e4f6fa55a3f9531d7c79317e8fab8b91b125a4d9f9ecbf3f4313fc5ba4a893187dd397572bc8a3eeb56f928f2b03ca358f459f7893ca5ce329e52270ffbb5c8737a4fea174fbf42e371eaf4c34616127d5a3bbdb41048cbe5d274b7724b679ed35bcffa1cf7b5194022955a828577263f1ef3a835a78f25b5e6897bec934cb1ca9edc3a39c94abd3cb1d0269fc3eefc70a78e53e353e59903a7c87cd65327537eda213b75746f7825459afccc283fc79dbafd24772af54c3e678d774be77c178b2c4fe7b1a2ff137d7215151c64cdd11edddc05923da53c33b63f4c50ca73bc95f637f602dddc15bab71f3777839395d82ccbcfec8f92c7ec8f8dcdcf6628b7194ad973baf97c9be570ba399b95b1d94f3efe0add9c15727d989ca43c33b69527597b599e630935daf9e3612425b79d942a25c61cdd2629f978d8b3c843f29e92f29fd843b8496e85941cbfe42625fe35b5664979daae0f4c529e25ec54973efe5a798ecf1f4f2b7d6c7c182c7b7b6601bc825a33e69c7339d68e37edb93c2dd21e5f2b7b4e87759bf651e8deb0ec997cce0f292ef57cda78bae149ba7e5a2a5ae269876cf1a3156f912cae75f17805411647b7285e7cb638d645f11e16da634db2263ea9ab152c6d7dec8f10eacc42a3161a4fea2aea90076f4c06dda9f3a4ae639fbef347d8a9c893cfca3a364be7e836acc42ba82de36feea8c593fa25fee8def071ce20ccf5ba8f8e8e9ac8add3c193abf5931c4f3b59c775d2758fa7d7a3d3f1e9d12997c49dd4c7d7f1b4727a4a53951263ebf9a7b125eab4cad3f3572bd66a75fa8d1d4734dd797ceaf4d4b33e3da52b506dc9e3edcd4ffd549e48ec531eb33ee99b8be3dba9546a3c81cfe1477d2f6a943e47fc93e6a1fb8a9a6e8aeb985f7f1ce75ce2f167a44c40c18ffd953f36dedee9c62915260be0e9f7758735893dd42450a89f4e4f9d15a8d67cdfc33c3e8b620fe0ce654d221f3ce8654ff651d4f1ac871aad89cf30d4a6f0eeb038fbc406412f0ca3a93c756491e25212a5b22651554a8cf6d95aaf6ebc6b12b5a52651aa3b50d5c302582bad2c88a20421a0369b06f09edae86d6c49846528ce477a1a0590044fec950ca4d6c5b3b53bbf73f87b6aa2b73cb38924f7ed1e6a4a71d0e17537dda58108c68bb3a33dd9c4edcacac4788ee38aac2b18eb1419e9ab17ff400a9283f615f2aa7783ae10fbcff37078712572efdd3a4424c9de7717a6ed2f2edaf519297c02a4b43c476c4d5ac40c185a4b7f6b1daab5de5aebbdc7bf7546fd2d4f7b94c20139f257eb74d30914c5b75037fda451170f7ee1290730e77124c9df344f972dc6c69b4ea75bc6e994b1f34f193276fe39becb1acc279ed3417dcee43c8e24797e83a2181bcb9de7943ec7d391a3462e1efc4589278d1af98d35e9f37454a952623c953766462e963d2347953dad8bd73766ce537963668f94d49aae832db12596e70b04411308fe6402499224c1140e40d0757a5923a9cb9ec1c75ee05dac11970d3e05134f96656b259a76143f0b0feaf3c6ccfede1dc5036ad42f78101477c29f333333326662165505486f2feebc2fd76b51a8eb4369ec692ac8d6db34dc92f12626366b9a2626b6c434cd6b9a27a74949e61517f6399a368b3b499553365e99f5190313190a2091dd1da4dd07c64cc3508354535ac2399732e8cee4a675572d8cb8f3b430562d8c2a9dfcaa8531567addb11d6f974a1db6ea34643c41249d108c5e114a034469cadddd9bc2761e15bbbb17e47a426e90ede91974a5d3c20e2b7c6077340a6ec0a1deba9f1e1a64744732e84a57838c2788e40979439406f47a80af3d469a4390d2d0dd5dc6bba34e08d2cd7950d85bf7eef484ba2136e709d9c88c3f556cb2ae0a3437837a423707c48eddfdf480d8a8cad33392f17ea88ce5e915edeea4403c1fbb3b9532e80adeb9a471824820987575c198511d124a77539ccaeee804912892c59dde1da4c2e2f2bb8342284d1a8ef2c95a4acd9d0fdac8366b76bf39977b9b21621d85558237ac211869d4e82ec3e2ccdddd87ee9dde5d1090255020821f23c400e7a442764769ec5cee1847beea7e7da0346068ca39e79c73ce39e79c730e95183398f5b781a45d7a4b8f9ca557cffaac403fb43e526680c6bd756fdd8a3fb0be2a33e0c3bd75675dc6bd75ddd1ee9ebd97567ba43beafe5d90c8c98e4c01a4a1cfceb7016d984c600e8c3ffc9954a7ff7caea0b77c5349a94971a635332e859ad9bcf8a6db77ba9a375793ecad0d79d70f50333fdf2b68aeeeb0b7fc635379a276c7c3748ceff1a9d883e93df87507d975bbeb8eb0356fd0a3ddc35479dc745d67739a1a4ce5aa90eb52a2b9cfe7def24776f74c694eef4ab1ac3c6ad014469b6b91909bab46e599c1d207a414559eb57dd23589aee4e7309b4aa5ce84347a94a685655d35b2b7dc1925994a1d08964aaa69cd112fa135326a4b7ebe8dd294ce13d34f929d7f3daeba9f63e74357f24d3ab466beadc2e8f9f0b2052dd2d8bdd475ef2ea3d69498406dbda503a134e0f36b7e85c2d3a7b9bd300cc3300cc3300cc3300cc3cfe481a178428d2915188a27d49852614a73373daea187347af6a7b3403bdffe801da529dd549e76c7cedff33f9fef27144551144fa28812c5511453a2a812c511510cbfd0641275f0b74d26d305b2f358c71ef960ced5e8e6ea1ae82d3fdb2576be585aa19d5f607992a952bee733d61e42d6cc2f8d1d2e73d1ee7edea04720a82b0f5d839c30da0dfeac3bc0a37e6381b20ddaa69f372ad0c6ba02d9fa938511e78bd867470d027fc41dd3b2613c3bfcf954213bbf7421dfcfe763cd7cbbcfcfc70713d9fbd0cebabe61b44bd89c8a0dcab7f935a95a61ed8fc5a180ec8e9a04ea9535bd877ab4a67794e7bd24dae4fcba23c8e278d42b764ea2b736d05c0e1a94c644434feb019deea702116132d5a40a647336ec2dbfeb0e1e775b18c163ac6fcc6cd33d2c8cf8e2477dee94f6e9e14119bbe433de5833dfc69af9ddc110ac3dbe2ffffbb1b96a85bde5e79a64cd7cda98f935ac696e312361cdfc3ac5682a89b0663e58dea04576ae493b67d5f3ad8fc585cfaf912f4436bdfbad6f18b1a6fba47fb03955be0c1d2cae7bcef93994267c7e362c2e040f3b3e1237f77a297fa6fb703a15756ad82885b8ef42309ee3fe81861ac6d3ebf163dd75ecae3ceb969535ee0dc766e0f3aa581cea670971df33be863571771bdd4e8d6795428cda461263b7c79f1469f78c658d6ed7b839f2f8e3253b2ace88cdb83a762c075cf6a04a22f2d8c767d35d83558ea5c548b6c6e9a4489e9035b12896d587e70d5953a835b2528d42d8b8d1a55cad91956ad4f4285d7fd2f42c5d57bbf319eb291137a7c3045374c0055f68810f6860061c7c246e4e4713474948a20a6340c20d7480835fe3e674d8a0045a08fa410bd620861f38180c2df0c11454d8118334640107d3bd3b7df7486ebcbbb2061ac6ca44597db03e2eb122cb55eadd71d2c84491b32e811bb90a973a0ed39b2377ee1ecfd242d81851a5563edc1c11f7865577fb707314078f650d728584c2271ee3999fffd2b11f5e6fe2fe751d38301df3813c5966a0e290d781236a21ac8d1b44dc1bfe4b2371ab8fad3f40948a3c787b658d5aa4ea82fdf0f95d3aada5dbd7ca2cb3eb3d9b5ddfd96c884ba5d24f5c3aaeb5565c05f05a4b2ded0eab87c15e894fbc7157666b86f8ee93628a2bc26edbd11a6b77b594d274a7620e30b63dbe56f85230d6dbe7fc3de39df1f7ddb3debdafac9e572d2ecf715b4a6ff4e3f78dd5f155fb868ee66c63b3bd9f2ffb74db27bd4f2f54089a7eaef6891c8f7f6ff7771c356a3fe793acaf5d6d524050c8c0e8a6a13b38cb259b9ed293ddfd94f94e572c9acbf7d6d5d453971a8d6ebb1cd5aecbe88198ae288dd5638871a52df8545ce54ee664a5c39d0effe9fc4aa37bce47473e37d7d9ae3c59dbebd9f5205ed1150f674aa9b91a435b6b59c36e4f1701045da159d8dd13405bba5bd2b066f7a127406a3b6990adb2f3c5e133dbcbb0d5e69bd3810341f44685e88dee217aa33ee88d6e1ac40bd8e62aa634f794c66e50676b625c3511f80b7ee10d415d71ec58ea485977b58c241a6d68cb7034bd1badd9ed9b7b63b1a893bfb238fbae94c36e7bc515575c51cab73cc2eeb1eb3e767fd196ae7bf72195da3acf7639c0b55a5c99182fb63bacb5c67049b7ddd50460c6d6a497c76e5acae80ab5c7cf785b6cedb5a5a663cd363f97f93bc1587574f512810553e09460c58e735c6d8adfd9ef2068437ca5a86009e5045413c69e0fbcc5954ca8aeead2814c34beb22671a2713a51dc4981bb32081585cce24abf4735c174f0844f38ee1557b480a5c90474a271c2678edb5d0c3db03c4d654d824e51aa0128a334f434f0c17c734464bb6952f7655db7f7ac4f0c6cefd5d361e38635eb0e1ebbbec72e41bd8d1b2653fd4cfdcd3a847770a9594b9b3dd5a08c2f683c7f73eb8d197bd29ee60ed05a4fa93d4875dd5135dd9e876958b3d61d53b0a3ed466dc3de6041694a2b2599457344ac284d486d7d873f138c96889bab463ec49f7205bb4ed411c25a1b44c6b316d9730328bb814021dd5b7d16ab913e83d0dbf48a9433bd251d40d4b066ad1ed25782f1a453ecfa9b6b2b15bb5a8cadbdb797c0ccae61cd5abf5f6fd68ff5a4f541d912106e784da129dffb9c7faf093485605832995e3279faacde3fea7927dce10e77387c872303dbfbd7c3dd27f84f8fd61c7dc6fa33eca1beea1ef1f542b59667cf7dae07cf1ed34bd55693f72a9aeee99ef0a0e962e95ed4f75567d29a750564dbb6ab5dd7fdc6e66ef4de956777d09be1f5741f778ff72a763df7a00f486d37d779a715c7d394666ada9c3d8fd2223da042044b00123a0111ce6d04465940c10e1b44f1069cca86345801093f8a6ce1099cfa8302149461073930c20f0eb6f50a90dace928fc591ae535414eabdf814bbbb2badd0965c59ef9e90c5423e2ec92a4f932c853e2cd4956d786267a44a58038f8dda540949427b8a7d9d308221fbbecb21a929353f86755cbd8a3a2367007e0328ee529375fc1aace31ae9daac5316bdb16e7a153daf8a5ef79dab3e3b38fb787c300c5dfa03f24141ab81a7901479eca61d1c432ceef4c8689f619afe8cce1a747e467b76fc636b2b139547151ac51dbb674e108137d6dd7963d775b43ca2ee71677c063fcc04104b636bb2ce3a10e32a86a36ed68fc0fb3b0ed4fe4a6bc51eeebff288ba59e539be7bcff87c95c8d3e33d47ddae77a56883b18bc2e268c7ef82d85ceda14b631c34d3f6da82acd0a5eb0e5d816238bc2beb0f1446d4ebf1c7ea9628c23f2ccec8e250f77162269cd8f8f4c44f78176d309e540a1b8f883c3de373943d382847dd7847abda8eb6bad3ec244d5b20360a7bf3b1c59f16a9f6a89fae3c804eb212a8dbb3b2fe1c8ddd59c74cd8dc4f13243e16b238d7f1710f8bfb8e8f87f01216f73a3ef66171b163208b6b1de34d6aa2da6b46fbfcba221b8f9b24c1ef098b73f17cd3ee9d787bd6559eb4329ce5ba8f202ffb8c36fe8ca238a282c86705108f21267e4011640a211657c4e26a9a248f95e086a2ed1db64377b23ccb0d7bec5c79b191d4713da6edbea28eaca4b4e5846d60c3bc1c3ad8a957ba608fc534b5620edf0f6be2f27bc28a43e3f939b1b10b96437758791e51774b4b810421234a80912ac10d43bbfb27ce18bfb3bed7d08aaf1274691eba5d2735dd60dd618ab63b91a7b6bd12476d7b77e9baed4975da58e441a1b677b2ee4fd79d1279c68da312d91e8e8ab4bd9307c14a53f6aaaea7bf93fafc449e7ac5f6bcb30e8a3bdebb8f88395cefc5fe441e18db2b71c0002dbdd5e293ab34c2ee23bceddd862ed66fecf784c5d5edc4f7e37bc2e2e8c6ab12f7b078284c929dab338ca5ee1dd35c8d7bc3f75c464a2c840a329e14098b3ddcdf7777c208a2e8543fa26ed56fae8a4681c5fab3f1432c449a74115f3e12128d1f80c8cdd81c1d9460458f20a740c82adb16a02b1509ba525f8ad99a16406a3b311219b0391df9369749d807079d5868e35f249b2f919cc791247fd3443d61c4816e517cbd5ed7b1b33e6ba886c6f154cce2535ac7ce3a47b747f137d73eebab733e9dc29fc2dfd8cdf21310920bfdd83689f11e04536115e93e6fe8dc8fe338ae76ec3355753efdeafcd4e9e67b7ffabde1d57587bdb9df0ec37cd2a10ef851d4b9bfc575d72dbe3efc9df5ea293df251d37d1a5dad5b5ccbd5c2819e556798c5c1c6fcd36fad2dcfb13ceb4b9f75bfaec22b107954fe3a0ea25bcc74e763a151d4c93ae24190fc8afe474dfe448fb7473797726b7d641c9b9907712a9fc562b14e27af78a74e6fb5708fd64faca263a824ebdce78ad4a91911001000040013170000200c088643224994e4609cabfa1480127286765046980984498ec32892a31842881842083000c68810cc9058011f9904d6919f720418a5292dacea0199710f21a4081ef30a0c3e8261555c1e8b71a67795b067b8b4c5d5aefe72986b450b3c99043b880287552cfd948a599ec4d078a2f5a530baf231ac8ea8d5ecbdc50af1df4445ab6c135c64947e7482497c4776614f739bd78922a700e8b83027dc998cd5f5634eb7e51d8566fe6413c8416ae6032a0b2875c29dd158df9dfbb3ab4602d68fc35bf65cfc41746962980c71c82f33f23efab5d7a89b4877bb0c57875ebd218e4b6dc4b96da2b952c57c4821e3e57055d40c2cfc61afbf0213228767241eaec0a1a31a76c53904e6d12dc60a2e163ddf7c0db127d000cb8534fd44f9816fd910f0bb9c86a16c93dca20e38f10a20bfc443ea6bb48d6241fc06fac856f0357c0fee98300b4b54e4bbc134ee288645c4b1945166be77657f206fd07ecae25a8d94b6db64cae8c719ef8bdbf42f31a7c939c5577cd0e5a9ba28adfa4d4d5de47e3812f9ec04ac5bc63a4d77d3e3e4ea6cfa011ab2a662345b2ea4557ded87ecc761587765edd6035d744e87c12b0ebb75d7d12840c51002ef8a6d03a029668cb66ca10d1d070da1f5764f4416c468716d37da8750fe30619d30df36ea9700f2ef596f072bfad74f9624647f490cf397f25a2d05a90df4bd002b066acec214d3ce30a6d7783ed84a5d48fd2d4a7aff8a5ad8e7db9b22df9cdf54170f6b6811e88969d32b313752c03ac25cf9e68e487569fdea01f7587042fa93c470ffab129f0eac6fddb69b62afe77900fea0ee15ec3410f9fb9afc55598cee67decff885bfc0926a92c2751fa720c8fc6f252293f9c440014f1d2e70f0d667e513308a125498a6ad99d6181b61a4c266331362160d5d8a6b1497c3d5445c59693f2717e825d5c09cd065b9792fbf6adf5b5c0ded870bca73a3acdc0b72a2b41f704a0e3eda9c4628a9a62a8a496e0f03ccb13636658784b5fa4059b8d12ec2e6d2d69b92a7852b4bc4e1154f1ef952082c67220deb9a88146e189fe2933380be3b964f982ea01785b4060214dd9a08b42e4ca6163b40f4e883dcdb6c5e9812c0c689bcf18644b2e9a70c0a58ea64abbc5c6a2706af552e6b6a68066468222258d7432c1f2e5a3c2a2f09380b22b69f9328328108c604853bdc6f28ce990277aa387084e6e98dcc8857692e0e8e58f36c97c24cd44bfe8978463373caeec22ce0ab3283c497912c4bcaae819aa5ab426a2b9bc9f20b36a6fc31a2936847553daaaa349f6a1495af89d86fb5b6a04674681b1f4f22ea51a4af63a41231b2c6b306210e8226965a3521cc86e29d35064371feaea6e9195c678ec8c20be1a60cd9e4fa64ad3a19b6de7158be511a6e1a0c43f7ec9723249ed4ab89326249b6a210ab9c229dc79bd0c5e236bbeff50c9173720e14bb733022b0a87976daf2cc35341ab5c802ff572767cd5844bf0e6986d4f3e8098428fddf704abd9bd50270c0b2a4dbe3192d9a60aa324dfb23190bac19b778226b8d4f79e8ef12debf38891802ffb06604e6219d5b2862e8a87239a2014261ad1581651c52270373b30617f18be1d3ce1d227f007686e52c31a3c4679bd75cfb1110c620dae648f43c0b9869623891a48558b5f01153ce8b120900899862ae4ca7a9ef890381a240d4de7c533f8ceb87c485f58783b9964b940351c7d55c5a394b604a2282daee8feef0f10421ac186b7501fe69eab2dd4732dc23718141795697af0e64e02c5003381f0ddf15d0a1f3b36e986ce7757652dd3a010c0824234cf2faeed7db1dd8c57bb2288e1f7f7898f7326a123dbc037e4690765b8c6cdc60a02d809658d62ccb20a47a41d6c1b4de6db348f1dd9f517ab0d6adbf7e21182b5ccb023ed191f8990057e6952525e9592ebb862e08d59e6a62b29c3f4335b95b2df1545ea8759ec4734b3777a2d35c2eeeac6e78197be7b0d8e912706246252781598b739b62d7c94d8b4a13b2e360758580a71e144e25ce83694525c3fdf24303246556e099d5465fc1f2a031208c795a1f6daff37421ab03408cfae474c803c1098e37be0e61e3ec0c5f05b61eee9410aa42067ebfb97696a03a58771704074b5f8cd139f8a381898c6b62d3bee0041495ccf9cb15448fab7040a4ac7c0192519f5186cf9ff5a6535bdfb5284fcce2cb450c8118c5903739d9934d3cb93845f6160b2408ef6641221d1ae2c24b84e88ae9ec6b281ec7857f0095c6f857392b1b001b9ad12a1df271279c1055847c46e34d7c0d7ca94a1541eaa6c48168281daa1516f817618498ec610b5d4804b50f7df451913075c3e7db95611c7ceaecc75611c5f94923f33f22a365d49c3c78c910c568cb4e566863210e1e03f9ba27be53a56525dd202618dfbc0406e766adf57b3819cb586c9c51ede0649c9c44b70e05f4f1918fa5d50e3ad41519e1577b535b4a4a2df1e638400fb727f53a8ddf51240eb6f62f18be8974082b31a4635fbcbd8eb3dac8e5c9a0b14cba448f4d11aecbab51cbaa62d878132cc583f1457ef4fbaa5340c964bb4f31e68b54b82d3e499717a9d1384e42070c194f6f1b368d829323d00714de5eabf2460d23db8e2850699ac181f0f52dc0e6f962143e50fafec42fd5dfe9b4dabd9e4c084225575c9bf3c9c4e061f69af44c03b78cca4f87678d2270c6bb413de2c942f39c286937082f6189d8871b71d4bba0869a69414b5f04aa63d1e059a4dc545593406109c9d9fb3b54be533d10bc81da692864b5e142ea5a4181f1cd45d3f8aec50b06140862f97998c172244a466a0907a96a05169c0e7042126cd37146be1a0bdcfc092c14a6f3531a3f2e487f3615d0bdb0d9f8ce7d2e8e02e197697fabbe0fab85b259d146c6ac44c1398b07675fd809b53a1151aa8548723bb89adde52c560d1a2616019376155877adb2546c801b9966b318dec5c3c723197907f5fe0008c101643e15f05520824bd403feda5d6efda7dcb2eec6345d21e40f968c697a2b9cd02041ffe882ed6e8788f3c87c66e563870bf13b676d3285a328a6499c7ba45093136a906ad78d79084713b493bc240d464960e888696658ea72d5ff7907c4d8304d5d31b798f5d37cb3551d161c9f62a4b6ddd181eabb87fb66d67a51afb05e63a5c95c62559a3ea44a136ba29f5f570271f6859568833a4f74e3aa3851ea7a1925b472eff182d5c58759492f3476bda7fa7136a7eb605bda528d15e77c608ed8a88216d49ff017db9619fa9eba5d6b9910d1daa2f136a59cff481f9fd9695695938097391dde55102aaa0c19293b93ca9382632fea22236688a226732336b8a8f7d3d0c850f740bc92487fac45a81ecfe90ec1920eef429ae5ee43ac21cd96cd0186a0d8040c71867869e08babfe3a007a0114b6da88db0dbf6a2095e94cd50393bc8f901b01f3f909f01c8d508ceab8396450fdd8a083b762a952ecc4043b49239772d1026dcdc6b0d6170585831dd5661f7c613c299c0b7f048e895070d5faccca7e8612466b63e2754fbe8ab2ad9628554835e5337727045560f299761cd4f9ff137c667445c0676ead42b4da0e61b84f5d46074f83db105bc1a791241c77a668e75c37a433caed0bc2fa642f1ab542055030cfccd55990c5fb3add7d59d66ee414a24f2071a87eb56b91d70ec4466baff7c8dc01812daafab1ada1f64213c39135670668455c769feef1a02c4cda5122c922210a1592314b4742ec2f44312879f8cae9dc9c143b57e9527fa15dcec4f41dff29350b6ae9abca810de2d94b6459ac158e18dffffe7bbe867d57399a483e86a20e51ee07d14f509ed16c4bf8095b4ebfcc16000b3927cda2a9f8e0c4fe9534b3593c09b027acb3bbad1b0c9efdf9f96f48aac585ae0775a7ace40bf4a1db3e79f21bfc9fe13998ea74005796b13690bba9b02c08cb2c6ada6ba1373b04392fa18a97380c39b802d3c98d5b456436bd4ffa4b8046a9c7e9a501bd2527f11f600cfb90d76ee02a805a297b1939cf0d85c2032c6b2cf4592c05518c56ea926a057af372678d7ef8b41cc57393f6cda378656541ca66a6447dedd8447c0f190c63794887dc56b3300d882acd1c9d742b1db00edd4ce450e14c047d327b39b1120490ee69e8e49dbc453438f911677ca1e94fe14a524a0e4fb033ec5cb1c1d6f0f54c489f7b61a7a8134e30c2bdd7809fff93087c08a0db4eeafda9e26adf545706cb0f852c687de206f8a3bdf43b3adb175ef666fe0026d5b4924b75080af9f5e14d37382207fc8ecaa17dfa6625a70f58c4f35a8300737ba7b7202b3bdaacb6e4b22303e6aee1134eaf7a757420df01a0325b89c784c7cbf0ac056728e1cee015ad808712e72572df2df07b9569dea31b86ae4345da758e9507f520832a536aab7d4083db534b14eba0c4631408e63720cf78633af7be966eefd18bd4f6d80a28f3d20742f5662652952bcb08a79ee32f60b5cf5e754ceb4266e596cd4e814c01a744f9ce964dff7d744eb52ea14ac3aada5668cb545587b4d832481203bc15a814aeba447ae8c711de62113b0eefa35c71fcb55883599b194c1d62c280633802e77a46c76d9a50fe68510f5d4c7f982ee9902c14e93f88fbfc1b8a59a206a152d462fb9b69c57c10a4fe4effc2b81dc1ea89e0b1c6feb1aa1776d1eb3a8ba77a4dc1abf412a500b9995c3003329449222821a99c119c32985ea46247fcab7a0401a9221893bc78cfdc1291c5039c3ed9a9aeaaaf2c8eb90222516c604b4dcbf6c34564177572ad839284bacfac637666ee23ff06bd0b0b148b548732bfdda9ed194200981d727128a0718f54a49f1d7aac94d9f0f22058a1b4e1f57349ef48ce1c59a4489b59e31e16ea823731e4edb8314d63dddd81edb8e4261d4859301d1d0edc4b8955e9530b4272ea7d5f4e5bf805fc47e53dab4db9672ebb79f1517b7c7666142494e002aba4b4d32f7b89e9f9d20545c7aee0bbf72a774855761760cf9532ee0fe4091101d8164763409ce906eca65101d2f4ade4ae4b074dd09c1b9e8ccf969652835b1fc6a09e229e010941a4f4f6d778d0c0c1bade16f92d2f3b4803e7426bcb4d87513bfb83b3be72f2429026df50f95d9c28760a1fceca1302deec809bba45f0b1d8b4ece463f9b5f20810f2b26110ce9a3a32aa59d14775a1261e83f4d3076e34f07868d692778a5f733aaeb3f95f5fd613328655343b83c24b3727750295363154144335383566f97cc569e3acf6b0199c360d9d43a212de86d57c1003e55f469c30b58c12d22110de9966da159f0a5192191a1e965cf84cae044829f537621d4bf3fea03331536ba9ce595b50c4876b089c36a49a771cc4e7626ca5e824149123aa6c8f660d477d15870cf91c945b4fc8873be270a9c05641a3b750cdd24cf47c16588b23fa53b5c64b629af9eb38a205fb9b38fdf43ae29fd15f0ac2f9d5f349c5e63c0e4f5fcf1d8b055e5ba5ffceeb895cbf1c2182ed808bcd06495472b7b9f711460e7998e1987b2a365827b146b351bc70f9aba63d8ec765e12738ac66379ca1d6977d5f3a44f31e5848a0327ed2690e119c96b3e0a44e985d3f7fc4a25df0f3622243a4dc5562a8e678d42fd5b40bdeab67e506219bf4a91a54900dab29f2cc1398cbf2ea5ed46b9ba1ae7702e890ac9fc230b61f2de2df379e80d4a7842ab10b9d96759df8b529f3e594b31435ba2ae04115418f3b7e6fe9725864962700eab577f00216b8e5208a88f27dd8cbcea14a41bb02771a7af0e4d0ba7245c1e624859ae330115b3a94f6b24aa3ce039ad52c018ed765d20fcc74bdecbbb7c639bdd11b6701993a9afd59c2f3dd742cbf659a4a3414d58720b7d3bdc9d5c1451f06c6d162a89d52ecf67d7194a3b24516f1f01553396be10eef0f5bf348b9cf98b7522d22f2971856f29aa6d738cbc54151519f16b8d4ec47397229761c32ad779ae020724430508b0b8eb2c12706d45b0a6e474ae795a4b0769248242f0de00eb0944155d8dea29df194ea03e7efd2a387cddb2136dd368ade74e8e611ada2d6a91cacf943d2ab03856973f3a88b15c5b6e7165078e957c0abe961797ace07b2ab59d2346962f50bc1b85f48452cff73a1ae70f4605ee619369d2d2c7bb9f893bde9bf1fbe7884bad2aa03b1558fda81470dd8aaaf5a3c02b8470a879a5846d040a1270d723b58d36785e1c78a620d621aa50ac71d573c77950ff70f68400f85d6623009f199804c018b554429904fa1873ac2cf649827098a95fd9aea551ace6c1a02c4e418658e443368ed5f045c3618315ffc79f1e9488307b59e969a9ac3e8a562218536147ecec19e38d86a1dab8c7ebc65dfa810d540abf4c10d5d5cd93da941de0d836ee899c80cc7d03627dde393743145a7ab635b6e064ec9be4d2c774d11ec4440b437954cbff86ec0054a3c4e7728f9d543dfd65e56e10ef015e491dc1171cdaa0a02344d8c12f58d49a0dbf8513a81026d1061a56008ed0f64ea72907fc890915bff4c7c6db46bd5a8a442f1e432900b264a73d858e7423167e4a6b31ee2b192829607201b8ecba30ee1d4050aa68ea79fcf518b22bf4a710341cf489070a380cd6281de37e8f46ac4d880e12ef65cbe1777ca2dee2944a64464b43c0be34b1f39cebf1ef429eb930f251eb9434319307d81a3a7c340e5d5a962a845cd9f5b652e49367f323db0d6934cb6b081b0e677f027ccb868291864885fc90279833ab750aba84058cb74b603a3caf55952c8640576ea3652ed60984df2da0116dcc730b502d026ee439b8ff1114994a7381e1e6d3ef0b4cc0a420758573869fd7791669b8b289392bad2c39cd236296e5b9252a6594f621c62fef8fd09971e02a15066fbed976d1578e6ba89814d0c6195e0c4cc944cba4d60527e1ce1c439b39d4b06399a97e1732db9ed62769cbe92e11ac9641cda74991652331e44edbd392a860836786d4fa6baa1478ac00380cf91f8af24bba1b7bfa11de250ab38dcfef98efaf02d95ca6a539612296d01cbec011a83591ef17a45e707cda15e2778bb5ac615222249107f102e83442497137c1b887ef2a3cc0b8f8f2ddb15c390a626948e39d0a57c021322ec03214853ccad3441e208e8682907144aa0540997518379f0c0310ef23921149812b585aee70947585fa4d1cba77d761e57106ce6d2d7574e5fdeb3b3710aa6752f6929a7d6386ae79f9ed2cd2260a365000ef8fb0edd0f7d54ea341023707231538350e3a121d6c1b88f7728c0d5d7f5dc1aaa89160bd7d4e59a415d45c736a2ee6091a09e2e31cc988fc5caba444d1ba04deeabb0367bd47593bf2c80e10509b94df31b1963bde3e6a19c811260e80037fcf22cccdc8c5a2b064efeb4b18ee4ad099fd277c8b405ae22de8bf4a8ba6d2062cb1fcbb3689b43f236783a64d641907ce99281f47a918ecbb3180372a0d385edd8a5c4c2a20a3e1a15c75d7a8a69d57b3f649511b72b19e8993c772042e9b20c04f6373f4512569780e2bca5b79c732a2726c9e82e63f7e04d01dcee22bf8252ab12df8582e94cb94556d5addadfc984fef57da751ece9e2e79a06172cc879892dbf4c7ebd1166fd5955154ee655ca604fb30e2cd8a8c6fe126944c3fb6ddc19f92e853fa30c05769915f953811c53db1fc130708dea31212c3fec3b2301ec60167ac867a4629ace9c9be2214ee66de0d5b419583be6d12d649b068119d1c43477a8ed3df2577a7f31ec9994a61c668c33172c11ee084f46b2b075ae03e52b9baf0ce2e2d742758118a6049bbb5de8ee0ce9a95ef967b6ac97ba2b4ef2d24b8b41cf69827c308330e3da9a5a264a2240e9c8dc4146a19b39cf4e0b49ea9534634236f46e0f2e6f16508bfdc40322726f6c9b14c12d0a9d7352ec6861c9f62cdb1fbbdd2dc66c04bae18406b35c9f63f64f0b3dcb305a886016f94ede4f25120de9cff65a8902a2deb126ee057ee1aeca05165ec3f18c731f4388897ca9248c481124d3708a20280f5b38d012813357e0883b7456bb0c70cb572887d24cce3443005fde1da451fb05c3c0068637baad14442ee14cbb7504b80b190741348a775049031133ac7f1a342e9a82ff3ed136847b6f4609016fe900dc18140a46b5c4ea0d1d776ec1df0724b2ac083b0422792d9520cf9d843227de168a05b9c5705e0d0b7cb340225739bda03d6d4cae5225183f03a0942af1fc0e0577a5d104a808332e1b01a4ae6d2196f09739506c302f735e023128d132ebd92970bcc2d65fdb762bb75338623cd183083b488a3f490d7847c63ac9a0cfc46b11168b4997e249a81654e638f3fb5a8abb2fbd34a264326a5cadf5f353d425d302f48ae5fb34c91c16c6a8c549d85d77414348042a5e824affbfe93db707ad35b21c137e0461add58b618f3e236f76108be427f1b88bff0418375a9cf04183a20c036d48df04b8e6371c55a893af76aeaed2807fefb430ba51e3af9855b7fbc6e4afa48707b0f35e9f44a3dea145806abacd25fda783d8ff9c2d67caab507048021cd83f128cc8fd1b52575bad13cd00714b486203b3ef484af0fa96f1e8f7cb38f5f2ba351896685d4e9520552438950fff15aeecf76c05dee3117c1a9e2890c9b78f3ad3915bb13107d2c45555f680437c0a964c8b4c4e3708ecd72085e07ec1c4c467379919f94e44f8b98110516f32e6ad70411592de9ec3b901e3c68ada09de8a551969cb05e8e34d9eb21f618e6e916c89a4eb6445798a7c5aa8a7d71cafaf465c78cc6fab18bdae71df7fd00da1f38837995c8853defeec18af6fa5be6d05e4bd28914def9e026da77ac1e0a5e78dce5cabb4371df91e233d906b52baaf866d1142ec04a20100a1fe8cbd99882c74e59a3a011063bce0d2c17da742e71baef6513e31a2837665967afd1cd0c3432a2d8ecae1b6a27b20375a048079b35f8fff9fd4e0ae5ad055672491f088e9ed52ac004792591d1d66eca1f1914378b3fd7b34409f0143fdaf9e38ef95b895441c26d8f5d9fc763ab84dd6cfd25095bda6c3f13b8de54666797b60d32c6460425f2a3483ab6d56ab2b9a81271ab002462be3b611fb6c3719fc6d3150a200d03c7bd563b2878d266f6b82748f30832c1fda3b7a334c3c2a8026f17fd377279823c7ec7503d8b09cd337e15db435a2590800ae41ab821fe63d40a64e1a32cb4a32bc4366adb00a15f0846f2123616a831360a37753841ba01ba975c9d813e19f266470416edae77f51aec27ea17fca524ccd7ff0634ade693deeba11e3295361709af7046e8bc921f85000c30695a5fed1e5380fcac19244116ccd488973571ca42f4b781f00e3d1154fb1181b6f65b0143a4719df95bb19a594a5f0d6d2c5cd0321a79ffa1b1439290687360bb88621b006eeb2e03e1112c9c7c9af5abc7f10ba066562f1cc51fd1466fa41cdcf1196b3681667396233772a73ddb05d484ca0d87095ffc4fe13cdf1d4212794f35a84f99566c694ac779c0fa5e5e50b6e00bc96b50364f4c0880d886a507b422cd69f1de99f0f9e6963f1946bccead7cf51f9b0bfd601349ca66777998e85596fecd797600a5a4e8561b0ae32c2d055c4bc5f9ad8fad2c15e4b9629c7c95e7d7580b808378ec68e47ffc7a9481ab4b51092b7848a9646dc2ca0f3ff593a9665e99c6f53f984466c32c2706e0bae963105468bdbb28812ff1ceb69e20d57845296482ce8ae4684c442b924230cfdd5acc32acc96d87d0aeb41970e5c832bf70b46187a5a2e92ac450f23ea90a60986ac89b3b28f285cbf386580716a45a9ea33a7a91824df8647036f55a303023e9e0b1acc4061b73a968fcfa74092d53b03ebdf0d40542a5995a9ec0ca604f3aef5a6e11ef166211d32fd5bf4e51a6d5d7e49474f51e89ca598793c2caf3c25c60f94f1632c044762aebc7d4417e1f1bb3f173fba06a3a9632041312a662bee7ee6fb42a50e06657047e69f83e17da145180dde225dccdbaf64c3086eda25326fac28b6fd98e1c64a7af7063700f7f5b2d980f65756cc0f84740ab357fb0c96c47b75bbc7de0ba1227fa86be000a9449b10c1ec7bc3bd37b2d4e49cf81ed2295066956dea49814b57d95430e24d9013ae349ac43dae505f70efc0a1f388669bcfbaed6617037367a58dfc7c9c756357be866db2bdd7b99227a48d866b83694dbaa9dd0caa471790c4da21e483117e1536bbc774613348f492c601be70886618d961c280315db220bb5eca86dc8e717e07953b6f0704a9e3bab9ab7e84ff030a8475808403b42531d78d6768078ac66b68a679ac6e08c81b6331ee4158940800a513e83ab8ceaf83e452a47a53b3638dbf0e7a233e80a55aada9872c686ab654b4034a942131a766776fba80008970c11d91fa923b6cb64797aa01d47c5796cf9764bd9935a0ed9d8f64dcd8842203d2c7b2640dc40b4e8e89f48d9cde7a5a8d35605c5ce1d533c90a5a67380e04d2de8ed4340c0bb528bf1ea8ed047fe9edfdd8a17fb9b15421a9d1e125a274b03cea67a4f89f67e4da51908dfcdd2874f82b77adc289cd5f06776e9363e13fbd55a6c86fec0897e1f1cf40c8705b7b6c0dc0b58e173f2d11ae073225f9ed69c3ef747ab746315041b147183defb5166b428c6e61ff267db2f70bc5a97c78556da89da2709ed002e9906b6f59deeffd1587d1334a523078c30039bd21a5e204b293bb968bf4f63624495729a2e6c781382f6f17ad7c71885104650ea4df3ca05d7269d24ed9b3d3d427d3cec949df6a5e62f8510626554c607f2acacc4cbc95c12573a08ea899d93c5ecb61a5b793332ff880ab4c12178532d2776478750b34cdeb68765288fbe9fa1d6ebb65beb8bd7bfbcab4d115c1c7ac53d66c5f08dd82be460fbe4e7a1b9d5e0ea317f7b121e2b7c50d0e55087165bacb7b40ecc7ce9c39b8334d8fc1b0a48b73fcb2a494ca990bc5eba90ca988cc21e4aa396b5253b9a59109adb1c821b26eb182abfbf8f984989c1c1a20ac3be7e02aae837fc4dc3d4c7c99cec06d8961fa37d1bc0960fa3715115128be7b4df9fda00c3b1a386cf7ad17622182e5a93d6eea03e04e4edf73bd8a1a89b37dec65f725e799e306292848a3f70fe231525f583ac80767e89384e6a3ad642c3beb0e2087cc62a5227bee0436bc81d34ede2bf057c923d33a92ad36707db8ef90ed0dec47cdb4e9b842eed15ca8f587df86615fcf49d96ae83bb7b5ac6810c055cc850b7aa66427bd876986392dd86e611d3f6e44a5dcc979236977f999b0c10f4358cf4e6edfd7bea0100039b108960715c73ac783db1b6991fb9fe1a5907a3b24571b1bd71d9f7bdc0c7628e1fbae4161963de7a74a5d1a63f0d00f02a8a5eb333f495e676a777c4c88f4e6729455d846f1b210420bf4c9850426f668d9212b77c3d7d9fed72433ddd630594e3ae2263e1d5a2b445b3ce0eeb6d609ac199a8b79e0da919d5b39934d03e75f44b738f5d2fd43c521ff0eba1cef879655ac5b1cbe7a420bedaa325bdaaf1902c360c28b99f603937a99365bdf830471162e9a147465a6d659e48fa03976c4858ae41e41011554b072c9a4cadc13ff3655eba7c7b0791717352ca450f9e85c23c5c1df66d736ff9b949734c0bccd035f77a8739ac16a07f339caea527d73d387ff2980231f7937e1d94c1a75d8037e3b578353f99bcf5af766645ddb36704213b54d51b52d7e5717f1b12b03b12a14becfe0702b20b80cb182bbf92a016641ea4a3ac7057a801a8a60669294c4a2fce56b8f8f48ac48eca0a201858d84af1fb58c2a5df01c0ef12e208c60e97c7bfbbc3f693b6784bbf0ff3036c2ecb53a510d817518f5293393da49f6202ab0f5a129c2db88c74baa1251591b8cee3e576527fe90a7a84e7fb06e9e357a0b1ca826a4fc32302216a6f847b97c9fa18671b46ac26aca8d8ed60529e85e385a462add37ece8be009443977ae7a028d87959284f285e360712b39b5485f7092e31b3ff13a0a4370a269ddf268b55daec6f73472f4523b87f5a22928adc39632c42b4461cb681597912ba77208cda606914cc69e0bb3fb476d6dc36ad50eb01fabc1a0b6114cacee653e400ed310ce2810373274caeb94d55a3cfcd738ce7d4da928595c6feb22e39f7234e9ebde920dcda3fbc4817fed71c14fae6e00685c5f58b07201faa6177b592a582a9137758a1b0aa4eea6842a3fa0dc94ae853d4f355c7695d97965f56ebca3aef956728fe0c73deccb7f3de0c03e7d9185fe71e5c98c340bac9108b283f49860657e626a36e61425d12e0e29af7f2d2fdf1bf015eb8d257e3f07c61ea56f632d901a08ffd2f052504e63d932b4c6c7746afef771ffd6cab95408db7e8f737c8ab90d57eca5e5c458e2929ca1271dcd6a579293f5495a8a6674b0633e70584330670da471ea99e8504a3fe00b679c9cc73e7939771f0fd805d0b1ddf906ce1f36c1b728e6fa239d484bfca86143dec718768fe48a8151c3197c44d448c8bbda3767849941c2d89723f5d1635474ba274ac4b4262cacb2fe388be5c18c0aaada2f16653e3eadbb0bf51ec20e846f16d537f427998f6bc8568e12e1c28d1d2566bcab999f6aae6e44fd1988e8b633586291e7115670a9a0cecad7a799f2206edb5c2db0875e9b02ea0c483ed5a2de15e07146d437816e3f56d40781c2154309d5cf5444d94cbff0d809692bf5b60fd3cb587b0f73b9ec51e45ac9b7ed42350998ff78e96a3bf21c71ab1a07375bfe31cd357db101ab55d0cb6a948a26606b7805444d54111f73bfc826a76e9d7dba4afd8afb48d20ab356582ca71a617e100591bace84ea52b9e90b02ce8a9c2da62411bdf047ba9a546d072d71533455c1f1f9553e64856e7c17e3423a8056b234654764baed26a484a9809819f6393fbc5e50f50531bb478ee01f5f94e6560746ee0a371b7076c24ac5b8a07c9dabb5393f50a534566aad223c2c09b05c878f3a447f4bc8cc9bdd9fb8f909f9e177bceb5778b664e6480865808a076e8f6fb354e5c5b3af473f7aa73da63238ac3e7f65f13cadd26926ef076c0123c9757427a42dccca7d7f3715b32c2a821efbda6a02b3352940b66058d0457d671b9f9242041cd5f267d59b76b18fe30f6973dad5b3be267db53c24e323c2a681948ba2e630e16591d20e4a7ef1a207591df5590b9d7364b9a2108964a7d4fc86e1c2aa5a658adc1e0a5ccd7594a8997a36884793757d6452ca302ba23b25c3f0a37a5e379ac33a67a90928c9c82fff695b3876d4a4dfec49bb3ea7514fee314599f2cb79fd9f70e9e4810424292a2109a716c103ffb6b3534b4ec8d0e5fb981729102ad54bf56b0a89dac85dfe7b22d7d850b1b997430139e30ba12625c749a30d1ca96ad924fda0bdc8344ba0bc201d59f507d8dd02b610aa07a809c3a1d960661f2d06d7c79c8772508d5825ac675c018d58f709709571847e6339e0b8a835ca018cd4d5351063a6584b21f56d0e1070a05b89884e2da167012c8e81801c02cad731752d20077213f04440b2c04e52e24a06186edb522c8432e64a20896d28fe4ea0d385c241e367a511f35c5fce0d4fa8285be9a13075e04b07f539df1d97ebeb9034c7ad227c747cbeb70e81ac845efc850f072b893571f523c3a8aef333ab81932a3358ad633744fcb2a17c13b10304d7a17d956c69dd6bc7f3eb0709562e2fcf6ac490f53c71eea5586e18286c95d95f10d4e1edfa1ed42c967bc0a601b3a633415ea41f8e55cf7a89859dd6bcf026cfb56af62cfbbf3ed80ccbcbf704eba0e66e0db7b5207ae47e5d092a81d5906cac2d4929813d1c0082664cbf427e4ed6c4de002c9b46d07b6ba067b01d4fb43415255e3cd05fa095f9b90b920ae4d9b6cf0b7661ea1bd1aeb9221d992309b136a72b88a3768b616e5bfdd7de1dc0a9d359838eb2ca60b9eb197178c9878ee93862b46ed0926d76ca46ac183d9a60b663e748e39b07b18c3037010e7e468118c5cb545adef750dfb7018f28b30f0650737b5a36ad33bde0517c0559bd718c775ae8240e1e505de4d599f1d68a4b16aa604fec6a213672d455d1c749617585f3b275e6151a451c4ba05f669c946aaed09191d2a6334eab87a90f4d9d8da0c5a86d2b80a566220950eda262ed7067ae7b49009a8c89f576b2f10857419c8c51728f3be17485fe717aca93446020b9dda356762f64d9d12b9be0345fe5e90c918e4670cb82702c07f56fc56167c9e3d83155fa06601fb0ce50bdce74707062845aca71cd53bc2bd1a1958be20a4d48bb8c99710acc2ab85e577820416213c8d32f815c3abf923b6ce5c48a971d755f9a05f907c464f7815c0407fc1bdf6c8ad3f2bda88ce5cfde7eb4cf19d64478b565875ef2e73b5d0fd0b9441083740ddcb013008b48fa91ebb3ec567b1b22ec8601d6010eac40ebaae8ca5da8081c61627567137c1fbc0e7896a72989efe4b7dde46ef2bbcf3f4a8acde232eeb063fd88ec89114a4919ef5986ccde786725721202df58f6236d4150c7cb863fa6468aa2c00c2e1391e0c88f8eea1e3e17530fd8203f357aa8323cecf54c1f0dde365c71fa977ddad090319dcc536652e0b2bc8bc3cf6ad9f721c296c292746e2df17260cf4bb473576f89c734ec583887f38c87a9733cda84b8a5c9b6bdb247f4a5adb3e8641b87aa5ba58aea252f00e5b4ab39c7a0c834d6208d3f2b6846a03cfe8461c08387570360c7680214c8bf712aa0d9e45b6c4508c3b3db69c4ef2d4c31b8aa8e90e833f8ba8686f2646515c33a3461a9579b086ef306855fac15041cf811830f26282bf3054a3a79873b6ee9c90e2ad0d2baada3cc392e73df71a22e51d7a1e88c1d7b57e82004a383f30c311f2cc904fe1af0dab0ad456c4e0fab74b2175850a16b47e2b523fd2a7b072065fad39e18091c87e2c84174a0cb21a965d6123ec175162a055dffe024a0dbfe50e3106d416014e3198d064d5301273c38a017471d1b4fb8a22dca7648b90bc5c0deb19e5f49f1dde1be21783a552c5180c5e7d03bb8c41b76b4b41637008345cc0852376cbc83377b805e9693d06faaeff2fcf90445067f93f6bc4a25f4774373e9b40558f01623d2ab69797c0405d8e40be91aaf7de00476c45ec1f03234bbdc9f8d4f20467159293703b0d46a842fdc414d3313682d69fb020644031f75bf77434b2a1c0daf2f10770460615334439d0fa1f19d48c7fd5597fb6a74fa22abcf881c5703cd6d906cba6c3e6dad2fff9d46293082656ec2b5e9201290c1d7887be0f28c9e08600970cf46a097b0d45a68b555c5666d04a9d4333d8e3f916991ed77f2b64ffe1835633c867c5ad814c2d3acd00aa6935866d1370369a3a01fe3cb1a386939666669d849f3c7a85202e4a2390308f90bfe30c0db8d98a14aea7780d281bc019646b10326aab7b82ccddd6f3f1c0bfc019dc0897fea9a54310bc4ca5658f7e43a8207e86a50cbe98abd385dcafaffffbc09c9d8a07a094704b0d1d94e9c1c5d04a192ce1a07c208b2a99686690a10356f9997cd1061cb27193c6bbf3d04afe99ca06525f30d552edb3b10c1c54ea1c8774ecbc700e257c193832e319fcff687688e602f61403ecb385c2edad26340345644fd29bf9730b57dab0b49216e849b55a2a493f612143618f412129daa280734e11449f10d0b6debc9c44507c6f68c4ecc666684957635f69a6f1838c2791a44a5a53006d27364393b8807dd1f803b0b187d9a305e60c08370080c11aeab0138ba027e70e0d46ee2d857e3d03df06c68307c508578778872460ce73a26f80797e9c24a087649cee9c3eab937c067aa5babf26135a130d9bcc11ca8e3648fca2bec243e4ac07a98bbf15d3bc6252257ba143448a1ea26fdc1da2180b881bac0190737f673507b05d0e6263acf65a0f5c70b7cecfa031b11241dfad57f86750d449118237bbdcc45112ad1340fd6c6600810603111e9e668cda4a5b7fb4c837edebe0de90cfef21fa3b1f10d57aa334574f228128bdf0eadf725424dd743db65ace2bdb1707926e08713d62ac009ddb438af2a0ddfb21a22efaca58536616c5f1219b84aeb4b9046bb4dcf97ae8153e5aa627e811434c19123efb365006c148efddd3bbe048071053b8b7488b037fa18443ff7ed3a13a8242e501794b587f59606cac2df10d3ffc8776166646ad7b2a201c6a2d9c25a20252238355aefca56faae146b1749c354ec7a0ad06458f3cb4474daa856a0ab0844f0a49133af7919da3efea370bc401be9fa16455fe520e0e9f7d957f90fa124110ad1a4a21c7cff050a03f060a8f8661349b098b64028e97d05290a5f3d99657de25d4e6a4d5939bdf4ad1dc14b4dd7f312f000cf8f7e4049fc47e63643ee3254afda6f3ff4507781573c4e6edc720ca539b22cdaed6f46aa0ef0b2990c1322adb7be9e9203a64b0880f5a40f2fba0154206840e4c6e405f6fae38c0ee0c1fd44157ecd652a27733f02ddd7c1d74450950b53fbac380a6f824233240a3e4646e11f141e92a15870919c8ca900c40534ae5b9307b4cc2c28b9141c0e6a6ac3c0cd97645b92dae25f09efdd20cea049294f3d12b845bb94c879b1afd9ef124ad27872d9f16d667390fa10a0495bbc31f40d21f180d4bc22421e840077cb3a0e504602144360c701e7fe029459055a6c45afe40754dff120481fc749ce7d0bc42847f30ce5e6f4d06ae62f05cd544406160db5cebc639bc0ac7333d962b3a7c28f03a57f6e4ed5660e5c9529301c1ef4f2b709363eb9d409feb6450364fb841c291f50a84115cf28d5a47d5887f6ab1e55b1842207b0602eea14f58c267906c37c67aa02f37e766d04f43c435a3f900eece8c58876cc47d6ad5f2ad3e81a4f2a7fabf9e008a06c2cdb7006e95e36901f6dd5f79e88cab20638bcf5f4493f2c0b2f435bdcefdcbde921c07d5ddaca85a6490df3ff4cc780ac9df9b48d4c772641990183276646760ccd877a206ad4481e420205e82801eb4858a39c9f3f6d477c6084bb341d1b648a0c148ac02065f7dcd44c83250c24eb555e8bf885519dc2b860cf4e0c83616ffce01b562f015d1a746ad2a4f5859061634f96484eb5b0f45a4eae98069ec6e135eae47f83b84f6498581bfea917f59dfe932e893876af5a591e0a0e063eed8f8c195568f7b4a98610f8e181bc2802cf7e57b4438e06f961afa5fba7ab47c884c0a33e012159055fc15209c64550c3d4478f11e2dd93ccd54ac7fdf2aa584e299e140964c0710969da706cf512a88b7cc189fb92cec2d9be6050920dc1cd0beff6658b32df0a2ab1e86c209c7f43ddc5d4e9303ea49d8a8f4ca0521bb5225b5907aa7b2ef15ba9adb80e0660634c9074527967c0101567c9c871119bc13888f936f2d0ac2116b9e5fe21402453380f5b10272ba0193b5ff7aaa863c788253c58c54093ab4ad1cb362ede86a01338707cec64a90953e3230df648d94df6f089f44b7008f01e9ed9d1b2eefe37b2a13dc0cb36bb182cef0974faf589e586d0b39ac905517de5f3daf71fa871a607efcc73b72ff2d5f8bac0c84014d73794a5b475c662f557f539f9908b23a1738a173c3c652979501a57c05831a3d65a51aeeb358a29d7b90c25d1428dc91dd1cbb109a32ed6de18836f81c06775064365f5a06b175d8fdb317f8c17dd89ed4a700886d3f59c4a2979b623f1193758f8681f5d5f8437e6861cebea22ef657fb4798dccc05669091133340bd6375062f03d258f259110cdf13777cf661b40c12a9c23998159e2468bfc3caeb6a232f83ff1022342841c851273240cc0084eb27a3021a791e70ed6a4a593a4299eeb24fa4f3c6b85202f47283825a9519047636c5577208f2aa1e0846f0ae000060b01e47623383491d7c9ac6ad412efd4fb7d186ead944bb0bd7dde35638f0c34e47cdc232ccc06db36efed21b076aeeb259fc325a2b4c984164842430a627d6ec8308ff0cee4791425c0181065f83ac4d25a312f0418a2cf96b3abdc631531ec89a7a20d6e21489b59b89c20d5e8eac43e60eeb61dc9d08a8bdd7b92aa9421fa7e603d08032f1c655990e31bb6bf65644ced5774dd86216b218a8640b70db4e02a1f83a8ad753702d7caa11e5eb29b886b2d711a0c1e7db04a03a460ea08150c6b535515a784029b7cf491404b112125f4f92151c2f197ece326464045b0ebe7102346878ea1d112ec17959230eea2019e476637beace82069f9fdb6c4c98bca2941de0d6b4eb58818206d229248944581850622004a39e0e5a41035b6310077d7f2712507fe2b2f802547280b6bc9aba9f82063abc25e2222801e4580de96060ad44300c4b1d1d5d7d1d5b04f42d476ea8b24d4e48969090b87cbb59d0207d814bcda1e5e1969abe101ad08a0936b18b7808f20e88d3caf217d854498977bbd41448b18a1f3f81345aaafe85aa9af66339b093d2a786fd671f62895c43039aa374f3d39f59ed1f65a7536969d2a48c0c1d13c307bd6d684035012103101d513fcc260c1cc86d018a826ca1da288a32f7a8b1470a93d98c47df93bae4e51e29c9369e7f3c8022d0dc433aba5762cddda3c34c3a8b0aa242960020772f57eb9d7d6ef98c0e71919368f053f9c581935b9f44c7867fe058f4f4f5e8ebebb022004ea54a086e207c508f8eae4e3b023996c3b52a2a2a4cfc070f40ef4dc948c2f621d1a0cb8e7719836f2a4b308b602342896ed06559fad5ba68405bce26d7b643058b8b79a22b8823e42eb51ac3211d3305fa1c433ace314c4f47e7565f34e07117788f55957b52c117cca74825110bb8e7303685441b80150c363e1092e326b35e17c6c524e486949918f3e57b7cf018627016c4981d28efcd9fc36c0a947a0ccccce4f4b8c89279cdd80d9eba069721430b94f04a75473e25254dfa0cc08e630e8e0644e194769c964d5ad3a2e2aa1c0d36ecb7ead5066f6b1628d76ddc1c0d36c1a634bc17a808630fae36561ab21368b74a3391f21fafcdde242630eda6794d08a93d19e3a7ad3d65cb74b68d0d8c1918813d17515a78bedb8428d3a019509531e7c41bd6ee1309268d276e670a41708c4d888f8893fb8d30c803954e396da5194228ce7a3583494801b5028ad8b5a0d122074899cc29fc791f9e7b803c37b319d50cf00a1d2b056f358972b834140d0b60d566b0ee33100a3616f04985fab5194c29836e06904db36cc24017db76d62c7d055b475291a65727aba1ad6e06d64673cda2d0f5ec01f5723696ba19c47cda373475fdd60cff990491cf4b17b81936f737216b4edd7cc02e88b09e510604513dab0ca8c41b7206f4df984a2297874c53cfb871cab16cb55a0be8d2dd7e9cd48e25c1a3c17136b2661a47a766e6ce18bb70e1f365f4a293da68a04ecfe0eed23b883e31f2cebd260eba022abd150854a4f73e0bba5f9965c4908a97def596398e9799fb276ef6103f97604cf8b0840821cbec1887eb135f20e8a3146c301741ff0bd721d5e896fcd305aad3a92d19988d9206c13b1483e372162bd8ba01352a57153c5528a38f927c8002e362a7e0f8fefe09063fda5c626874e1b4f28850ed7740c1b8042a9044b77355cf8c8875f42aff39e9ef1a77f462136dfc46aca3159d2a4bc6c504a83fbab9ea8afd2dbbd03e2f51f389360e25a9d2c3e82bd0388eb0157685ee89c626552f1b19e98ad53855a405695ed1403af9a08902ae4e9773ff257c1a964e9d7edecd4a2a801d79581afc192d1458738dda861f032b4d9c9ef848dc3814793ecf3867fec23f0176ad16dce55998487ebd891c3767017ab1e5d016df56fa659c5eb16ab8fb54aaa04522faa53aa8a62adfe58627b224deb508aad226265d5ab199a57ad5c32dd8fd802ebcf2ab05257ddbda381d25fb0c9ef05738857df4f3fe8fa0f2140f3a8bf75a03ff4127e7219d09e3e22e78cd43da5a20f37e79d53ce43bf55b5dfc8a3fcc43aeda6c44238b46794f3408a890fbb04437bd479d2a121d0878dda5192e7e18eb889ddab886f41c529be6b5c1a55492501c6a0fd3e2a214e7e83d8474dcd690aee46c0de9ed93ac0bb66bb977c994de1e2efde11cba3b49995679bae8c2f523f72cbeece59ac96f27a799f8ece69ec1cb9e5cb3f9ede4348b87fddc3378599b02db2973265807effd221a8fcf7170dfde93cfdd8e71faa1b716f06dbc0137225dd3c80f8715b04fa2cce2b980c3a5c386a65e5eedc5392d16dfd4d702f93f23bf05f8b0f6e3a66d672df28e1b06a5b120208511476b460c37451b16fb12e6cebdfafdda4054d7715490ea3f9eeed21bd8b0f426fd589e5b23ed28e6e405a3e6bc410d340e362cc3fe3b799754e8c7d62f8d7619ae71089adac2badf5c7f114cf8be6350e4e67f015ab90a832ba551e3ac9485262fc7218d5053f4953af72a6eb197c6a02455f9e66082a4a8e3a020a5d717d73fb13a9118a991867e45541147137a76d1fc722f48c452bea1112567e8588e0674e017fd6596eb12e4f6663ba3583fa029531acd7fca446d1f1c9ade2c40790277d66094bbb8e30ca306f1c00a61679c2d4c52f4bd15747d582080995c75ff355e94a3ad9d45684efd22da5decfabf7dccb452bd29aeb36066baadd720d94da640e4924d0864bb586e8cd359eec8771ce764d957c9155975a0a6047fc6727714f90fb8146b042890055840def4e5a0af729dc4d4a20ef7a3a6745c5552c836372c14de373f6c2d11947cd615378d8992bcd3ca8cc1c96ab055fb6ef48de883c9af4a561fc117d109d10353a51d155b03c34e47613248a96d10d585275e37567187770aadb75ae69d159616bf5c21d8e93685cf3559c6b26144ae5e88400696e18efcd3643edcd080ce480c2c37974b06e32bea3ae1e4298a5f2b9d8b4a61da7cd56f7a60aa36c53e697cb46fe09e0b85403db0a87fea22cb4b4f1ae409f5cc54af02214b98a9e33597b694abd5a571835dca177c0a2f13ea297c5177b3bd025cb2bd32f88f3df02aaa30a9a5603e9d5e04ab930b5754f3d803176b6d98987d5460c537287e7c0dd15798ef781e5acbca1a6f77c64978b2fe572963fa2a27bc45a91b89214b5991c7a7083fe96a299e5f593af689744c5eb3149ca5ccdf3aaa46ec205dc6a18ba91681942fa04286b621947b7a6ac5861ec83d4817dfa2e7c15d8cee6c0ea04b8e3884922e7643cd1a3e5168957a5ed838bfd36f1c38dfcb75b6522dd049e962ddeb664160b3e75409a30da7a32f8281928ea702cf62066ae8a678bf61d026cce808db32fb309f3a54d90f884d71d17252f4ef69f5dd8da18dde6455c3f1937b337a88ea1547f84d38361defe14f0b94998c0800d8a78cd5ec7c26a5e03a2e49dc8cd4c42abbfd37cd97c4db7e9884e295e8e554921b7151b214a1a4d2ea5b445be9d9c096c254c6d5af96c0841d40408ac20492849f777274ef0f8d9a4f55c651a11864a928ad7cc450222dff625711cfcab8486d17669c605850b29122ef832b43f99aad6c0d60428ec885f7331f1c75150b6f95fe05aecafc57f4f8f199e41788ed821203663229e8a24931fbeca879ad7b4fe6814f5a35920abeec8f74388534c391e8c5f45d8e19bd4f7944455383d3acc104f94893c92b33e61378b07a84caaf29947ba029ef7f27b5d0f87881c050668e24711dc89fa3558f8ecbbfcc093c0f71f3f80f4a8e821d8dcf0e1eee8c6da0ec3fa04945e4f3605a14de67fadc234a54d8d0ebad37170308e1cb3180a0183976908492fff42f154af6ca8d323293a23b8098ddf91e779f3254213132fb3ff66b121a0ffd4bb9f690a099371026d10bc6135b3fcddf006b684fc48f46c971155a0b42561121cddbfbf454b8f3bb09e580a223ddbe431464dd3044d63f20ab7f3a9291c10418c83dcb652c143e614434104bdd4a3ac8961d3c29a12fe8096920a7b48879e7645acb898c53dfb0a280d95533cda30d486823e3bc3d9292a3d1df5f30c23b64e16f1cd565a492da5fb176861032c02229f936b01cc79292576a4352ec9e3dc267463a2d7db62a26f1a60f183928b30d14db876b19fd9820d874a94c5776f170c59be62d56da0bb93dda4eca039b14474c9d820dc9966ab0c1d1c1036645d4be8dbd7d7f6013369898e646275375ee16b2925f06f311b15d1d002283b3d71c4dc0043fa22154380be3322ab2aa01c45deb72d41b6c7106b4ca3dda592b15ad3e71820110816dc9ddb75e462df9f5c698d5b121dccdda98e5c75c5cfb45bba5ee1eac18ceb951bcd0ccf13230c1d0721e7e893598f2eb590a5b8bfd568be383c0bec39ac8025fd28f0dcd4132e13047958f0c84b1381a1afd4ff0e60620eeeaf2ebb9c10b9fbbf82959f0f5d03e96934082686164e8e8d7bc2297620c16624470bc780731583f255f482efffbdc466b5001e8cd3b84c29980cbcaf33cc11f2a791c2363507ec2f78360cb003b048a4a16abb640a368c1d0be87f106490416748f80322c67d1be5f4f282a7f7ddda3920b48653fa799f8249564ed29510756a9f8158c69201c60977a34cfac10d79b3e9f187b331ef20a5810864483af7cfacb34f5a786965e4b01a9e3d2a408c9339071c89a4a0e147b62bf6f0003110a5b657eba90dcb3130367218645f81db6cbd7e41980fe53c9f792d26dcf08346903f337eec10b814afe4a580f187d8af327b83100206f5e468a806e5e471f2b49aa0ab14ff54fceac09c9ae7f72076a2943815a903affc2e1c154255b0767080d5b3f536edaf0ecce65fb910990d6e2bf3503427e29176b6816a492a0a5294955b2b773605ff3ef9097476e1881eab90f5d043904910161d63635a6218079a6bb1c456a3f4877765b2637f6c2085b379fbb7e2acb1c9b3fd0095d4f78e26e5ac1dd4979283061542ce26388031f54a8917cae4683bba7ccac18bf0710fd02c8b72bec861878064ef18cb60934f1261c85c32945081315717356c9b1e5ea101c74e03063b11ac0dd578c3fb58fb40af25437a08ab25709ceb93c2827289116ae0c5af0e0f44085db37879ec2f306340a18d0e8682b915c252f3ae68a465e255fbeb18c530576eb8acee815ce1af3a1c2abf874fed46e9d51d90f602425f24fd5c06bcc4b8a3db7dc1be7b4bc756379777bf822e53fae7782520b3a6f5f5f99e6a3edfb465b6e86826b63ed8f1cb216b8cdd0e909b273176a9367fa759a43debd3d10ad2ca3deb7e6b9a315bdc110c92c84913baee47856e4907259d271e9fa86301e01648718671aef63df04e2057e9debc1e97d56255f484457f283b77aced5fd3e20fb4d0251a8a99e5f519cc1b61ca0471dcb4d9c136331407c6be09008fedc90dd8e0f9391e311ed37ad25c3f9fddf52758465a1848d0f2241790514a18ad3f9aa75a582007b32d3467b8eba60fbe6dfbe3c765fb03e7579294426d02e862575eb76c8d9783744cb4ec2c4c28d7c7b16cb1b15f9bd1ae6452b5308e2f9694f8481b9db635fc904b5f05edacc9efe1bc72e16209cd16516b1dbda88867de5725a82d99aaa7d5c76ccaadf162813df80db771fe682b592948356983551615b3307e09250a0a29863961c0fc0b959b21ecb3495f6fcb139589afe8c08735b774b8bf031f79a8e697589b0230c713964a0a4520a98478d999aabe372c2dc724ea2347f77d7cbb4f0d9ff75189ae10c019d0063645de371d672d43a55ce0495de17a87f0427b79b1c66e37045d292cf858b830e4f662037cbdbf68cdc13032fe26adbca9747dfc0c84d748fdeb00fe383fa739a271637b6438224a2245d3a0ca1d3f3c727d8b1069b1cca533274341797ed101d20fd41210a989f701022429755453f8778c137cd328d0283da3cc4d1ea0bfb872c591fc6b8a1d48c75f0e51e49f9263431c96f53641c5c65c97403881456880e5a3cd573423f853fa80a5eafd98a73569cef1cc9e744494bf17f04791ece487e3d4634509571a797ce178a86958ae5979fc9c87a0ff98537b02e163f27b82c5050165133d3a63b52e7b17f620638a8120edfa93051222cb998816a378ce58c4dd04257b5afaca10233237932425d21d9e4e8bb4f311cf47842e8aef6b9d146470b7aeddcfc34ab36b12547e78c4dd083707fc3e3d4e584c54cdbb6e483a25ce2721cb884536ac71bca1d6dbc9e36f50a4b1a6ebad7165af8bd392586083f92495c22c719fcc40a00e67367a0d9e77a294884b76451cd00ff515a6de76553498c0308afd4956502159e919df0962c70fd057844d9a3f00c5b8d7a6ad4a8d5cf33e181a28958266d6a9c5141890a317b4b46ba9f34c5603b9551cea3ccc1b959be375df7a70cc4c770a1bea4d486cfaa5d508d7e4baeaae92f992858f5560e2f326ff060ac09daa56a6f3ecaa65fefdb057b866625d100fe3efb39e114bfdac7126b0be9ac0132f20036e551053eac3e0e74655a07fe3e2bf56f8b43e0d751996c932943b89ffa5c4fd966a7f8463b1ea0fd8a5303c53749d245305162c1c0ad68ddd8b8046c782c701c15ac2bd6126bd3c68e05f2aa558eb858f272e7742b7558ec9c90187477aac49ba691c33ae445010d7ed32c131052508090ee4d20a3bcffd317a491d45d8f151c0f004c56ad309d556413814c94dc298b1abd7b068c30f30f00cd62ccd41edafcb17b6c6de4e6dd95ac0d17a41eef86a84d5d701e58377ea2ae33df494e6b3cbbb7910d2ff0ad86c1e56aeb776e2026f6f9617594631262d89185ad25e080be740fbfebd93722841df34a768b5be2b443117b25bf6a404d4cbf8490c0a2eae778453eca0f3ba4979a1f09d9afbf4803c8531dd094561e30466cb0c90cec83ec86c41235f831bf51e1b861c16f94bb6af227df4a8989f3953cf7affc499cccfe1da55b236e1f4c0699bf0a1522af33c509f94aa63d2f66112dd97e265522f9951cb1959f85f67f25472e2246180f8567e5fea5fd96e3f6af64a76085f8a811e6b1e23394f208533318a551de352755f2eabe49a14e776f52e42250b941cc4a189af0010d7925f642a057e67708f5452728f61a93278ed95204ebd5111460215a8de8e8d1e983253bc8d20dc531cbd9677c77c119ccf21154c2d060be8f20092cbb8ca1292c99abc61524aecce6d30d7729f1ac72aeead4a46694c9a215de175d36f55903fc0b7de6aed53921b1642f1a57085cb3359bb2dc55eab5cab803f5c54ab7d62a21bb0452259345617ca3e22e061eed76436835a176320c4447b906c1545d77c4c9dfdee9ad53058d7e14ccc4353630c2b1bcd32e8d275b2f461a9bf1a9af45990bdcb538fdb05c4f6737aff3299b05367a2083f4b064e44505f9429f8ffbaf08c9098cf663c99b61e569bfba3d19150fcf622865de94da3e96fcf4fa9bc1d30f9eefda02c5f648a793abece4d6c91affc5105face3d8fc95252a361032fbb164611ba8e545b28ba6056ebd0d17c278081dfa8498fa1ed26b9694c9826b3609597bd082c8d4baa68d348f358bac6ba427bb1285c7baa7db3907857a3b6fdb2b4d044e68c4263f96acfebae7ea38feea349289c3238ff124f1442ba442164cc89bbd82820f6ff8556443563f4a1fee4243263eedac1c18f864c9f71e6e0f651976433ea8270fd3d3a24304d8b6fcfac8722c7100773f61be2cd90da32bdc032017e8ec32a9b35d60e55a9b073b2293241caf5c79bcaa1ae441e6b078414c6f7b488cd5c8c3ca58f62eb1285e65253c527901c21c12b8c3a6afa7a3abcb02f8cdb63a4b8e33344242f3bedcf7649bcb533a8d5ad81c7abb56bf5d98ad7fffd4ed6eb3015b93711121c21ad7396e08a8c941f66100698c7f2f781a61790396320be2a378858deec0640cba2bf282417d702dbff9afe8f462c63eb0d5a579820d546489a3eb62b28f3f25ed869c835d271a336639ab180b8d60c974bc6793f0431c16e0cae24ec060d35258b222f3b3cae7d909c4a3f4d3678703d7fc67df5f0c72bf9a39755772ca7b1fdc373bc2583a63c97d23c9850d37b75d2bb7973fe75f9d419d4ab07c3979c761c590181a6903c60d403a34548cdeca5f9b796f4ab487568f387c39c52cd9fbe776693584149b14c72f62860d13f907d2ed0c8833c8e82a5b3fc774b096253b7a3f7bd5f2008c7974bbc656fb3386f21faf8a52967cb7cc39780a5dec88017b3c4475b19b4f3449311966a50a6a87df4e6ca4b452f984a999fe25200bccfefec9de78b053846c1d6075b0a2c95312b225b2a1b3939f211d6652292e1f1afc66c962f3938fc130229f3e359f9834d739c216cc03f2cd98a60f71acca16c92d8ebe13088da336c524a3f642cf991fe6b17ef6ab693b549625d721b8790754df7ea388bd892f4baeb6f7e963f5f36ddbfbe074f6f908e35afac5055896505565b6e4fa532a4d65a3125ba8833f633c74bada91c238efe89c3cc86c0f05e03a669b05ad1a86851fc5c8db9e8af9853fd4a3508cdd31ab5c4e44b55d0752da699968c168d8bd2f8f107c3935fd462592bb86e41838535d50ab550d56623ab9badae781fda51e215582d593ac4d129854ebcd59133179818a0cde3814b1d1cb0d1193b5793425d02feb2eb899b7b49d0d13063c108ad753e03acad7c2478d29b90ee5eb28c245c4e442910c82891e25f186621733195aa51be0bd0e36bac09e28a318f355d1ccfcc671ce180198ddfb54d7414e66ad7a0cf4878488c954534e015f8a69499aa045b60f1b49eeb08ec926ca07b448fad03ee70a3578e1eee75bdb89e6abc8cd71661ab39c6300e997295610058d58519ec4e44808dad8da0683e0d34e7a9b8a4b2693a5f7bb6754ca671c21abb5f2a1a46e0cc018a80a0d0b39606c1d0c314457475f57878e3101be5b0c7c129323cbdb77a909e33176af1d91be09665e4395b095aacc24264b20991b2cefa83eab433d50436fccbe29d8370ea78f834dc7986c5d957bcb4293536284526354f71bf933d12a6197d9796e5e374c20037f3a33c174efeba5fcf0e2990622752beabd68f1bd3f4ce98488ea5625ae0c0caf4e13314ada0f064e4c4cce93906db6739f31831b9030f134b6041d010ae36bba86f66e6863d5e3f7ca042feb41f04523cdfef75c9948c3d35cf40c4838c67c2ecad408c48c7872b66e093151e9a2c9fb9d83cd3348474fe70c20c7001bc5e40360be4239aa453a140464300c542be5a44d5e5e87ffaf7346cb0db4c8490a9db59a549f9251931cd237d320522a46cedb78fdeb5b8970208e649743721dad0641633e64989e8e9d2bd08ac94568714c14e6dc390addfbb3d6407459fbf4df7f6d225a27ca90c19d2969c188965a1a28c4be727a2a45425a4252ea2500c2edb0176489044e4404010f521048a3743aae5f281834060b8213c20c4ad5cc4c339eda36104e2c160634460d030ac315a4440030ca288f9911890060240200064630378c087000219e71ab516da4fdfd50a6c96feb4d4bdade5b4a29a50c290e4c0f5b0f8ff66821071c01c099a58109a242db121312919dd112124986e041078c4094a5a39ad1f873442b600c84d700bed8bda17d76cf81f3f1d953d0b15180489d59731ae2b5b47701bc7eca7e5754519080b99a31d44592f10939e07c81b193800df4f7c892322c6ac050cba135017ab80dab6aa4c8321a03b43476c9311605b40d517e0531fc3504973152ba8cb9f970cbfaec395e707c761d2f769f7d044f871e4f1a3ebba9b5080468229715af5d6b2e64af1d855de69c73f6f1e5b3f0f33dc0dd5286f664c04ec889545c0c2c13037832fe0a0084e94de8298718dc0ebad566b7e5f8da45acfddd5b0a8d4d72d370b2c09d69d1cad28225ab8dc9673780871f75362178364c8d8dc98cadcce713589e6401f2d90bb04b123e5bace8006277940245d6598707920d6f8cd2956506290d879a4b329c73d1b6afa55829b689255a2e892b5e234097f0b59528afa1bcbe22f59aadf8da07302cd1b62f850da6a4b8176553ea9abe760d90afb53ee17593d46bed66deb170733a9c90f952a338a56a5da295641b1b75345a8d95f22b88cf596b433e3ff9eca6cf674fd53efb30abcadbe72c2b5e9ffd041db1325dbb7a92fc157bed228e5232f42426221d2381f9152799f02b8602533ca60063f774e4ace7c78513343832183bd45b877d9e242f9ada93f66b6ce8c9f5b295f0eb9d8b819d0f5696a3221e41ea7adb9e0c22c444d6238dce4a5d2f1c365e19bd92fe2a5da627fd3ab599bfde2710c2a72a73562d624079a2ae176a4912247249415cb40551d73b558ae10fd7d73b246644ba757bd25d407af2572bd78174bbdab95bbc965c71764a280c423bc9a5c2eb4901fc1b8fb92129614f6490d4d51a9f4c7287c28a11b7383058d4d51e9fe412f27a5f006c80c346591150d45b0a35c48b313a256926ece0b60fdec144f9c32502fb55e8f52b87cf2c8a3ee75cc522c9e716419f5b947d76137288307ea2123056c33bbb88d1e0fefcd3411049ad1697d15ff97ec58eeff55c00684f2f4dff3cc5c019d5ec0148c9d652edaf0f597d59fa87dcf31c7d85774aaf2119bf5d9360fabed47627a7582c9a30168bea852b163ffb08684f8ec23b184a08cbeb10af1d00688f3602954378a65f26bda6370e71c30abd1283eb0cbe89ab90b861051d74eda9cf3371c30a24be475c35f8d0497c5996c75eaf67ecdddc2fa75edfac1a6ed6ed183060c08001ca098356ca90c1877e66c89041230d87434d34bcb99f4103e50ff7d1b4185cdbda5f34b1e3df17d2788d9f343434c69a9a9aa99a9bfb3450f943f0e6fef9fbfde6fe040281487f696888fede0766a0fce1deffe5221038f770f9c3fdfd701b1008c46bf9c345cb1fd05eaf979dcef3cc4af9c3bddfdb7bf388ab9e2bcb122e7fb839ebb6bffac307d71f887a2d7fb8f7ab20295ddf4e627073c5bc838d452092e83a89a4443acd5d2f8ba5530974bd24da6850e4f1aad7f754deb1aa528904d7b686468a19ffba072cf66bd735891a56609cbd4975791da96bfae48720342988a2e09218c15064518942466e66b063c9dc0fae281e49f7e6384df91969a97137068c0d1436ac0bc1e8807110b3210a22dca08aa7cf679f854e7cf61052206236f77248fbec3afb0612a248b941462cc6922612b09ed2822dd09a5c742d3db903b2f7a200c20df229fbec1a32891e484698208122c30891baeaf47a9f2ff147a0d2f2d91990e6ab1c4d4a7e186139425273ce190060c99c1cc09afd3a62ec577b6524dd27ecc92d24144b8a7eac3ce93a3c349e7415528079d21f0074f5a463b06242bd359b2a9f2561121956eccae4cc98540d909ecbcf3903e0f3d2671f33058a8e8a91a8216af612ec5c83f6649fd6afc210bf72c0e9fa15840805bcdc934fba99263d5965e2ca93eea4e7c478b2ea8495273dc893c5489934322d8ca2ea70bea4c64848fdc0a2a2c1c20c101397f4d24791a6c4d488cf9e6a62d6934fbae98454552289c3670752997d914fbaa9f50f4ad567474d10d6e507d25091a2290f25508b8522c94b5c91129416b524a6a5d85f2f35d1f728ec9e5d5514ea9188a176248b4ba392e46477205a79a5248a44861cdf7e92b66ea84efb0ecb089fdd0c6223bd8361b5ed82e328ec0b0aa13030374e6e2701ace26408d159b1827677ec22521cf1b3937abfb024040565a9850f92ff209292205257a648891f5748cedc5ddc459fbbb93e7c76ad77b14609902b4631209f36754d7771884574f0b1f67bd2cdbc9342941861542dbab6ce54521fb5111fcb1c29463b93948f0f682295c5a484929914ea3a5c40131421428cac92185135407a084af080bc83d1f287eba46311223ce93c584408222c565594279d015844f9a4a779c79e05fd680197038809322a793cf2a1a5cc902c415b2a495c5bb06dbf89f878a594da5fb1f1fa3616d5a3495454fabb9df47d2a3b2b8fc267bfe663233ed614bf84cbc1ba4b9393ed2ddc5be20b22ceec1767bdc9d2e4f9b23d7da8b5a74fcb68e4f3dd7baf0f85416ab58c3ee7989c73467f18d6f6defb07ccf056abc916d0579665094c3dbc3da6984a7d3c1e8f97d2f49ad0f8cef33c69825a4d8e047d288aa2c19aa0921a1f100804d608bd7eca843e1a1a1a1aa18d8dafa6a6a6c606835613270c3e1b1b1b1b0c199a20693519d26a527ca1efecf1d01f061b434a86febc3e9fcf4754215fff01c91ba0ab807deec3bfdf6ad52ebe2c60113e167c3e9fcfe763018b08b27055041d5f25f87c20d047b4f1d4c6cf0cdebb414bdfeff7fb112dce61e1fe8f851a07faefe72bcf7fbbe7d985fe739b1b8b3ffc52ffa1ae1de8343734fef37b13a491f688eb0bd997896b0d4a5cadda75fd9af882deeadbc4a0af3c62d0e73b892fe8b9deaf892fe04f892b89ec230efdcfcd9bd27f3758e8e84dcf24aebef7f9bc7763a35d7863e3bd9b53e82ae4f3fa4c0cbe741dda3840fb5c7883c16d6e84448bc177d3f3d3bc597d6fbaf046e30fa6f7f28f97b1f36ef09b37d9cb167c2ce01f6812418cfe6c5d81179ef49dbdd41fb4db17e08c394aeb96b8628c7149e69d6fce58043e8158e7f6edbb26a4ae77dcbc8fc3c1ade38b9a90ae3a3cd62b45f1b187182174b11ee77513d235158ab0378f775cc5fe00095814f2eab36026e0ee86f75d1e24dadeb2bdeb9beb5a6bbcf6eb0b2a60d224319226b73e22f78d36d244fae89e707de700496b8cc91b9f793711a933e23799899ad854e632fff5e1757bddc42c4e4b2c87c2d4675e274bd3124dbbb630e2affb9c26694d9c834dbcb35eb35fd3e723cdbeea5f2f79af0fff7a89c47dc92c266f137b373dabf6eb0bb92c795a97e50f5e6b6b7d3b79637d93ae6f5692f44d92d9ed8d0a7906e5e76c5f677b4d222649d7e525ab7076039445e9f5229ed5f769dee940e4803f60c797c3e3324949e99a833d87e7258df43eefbebe29d06f9a0a3d67f0cc4104dfe02080b0799eb387900322844e0e881028eabd9bf585efdd1e71838d6be2baa1023fcf1b6c882bfcb2fd17fe7a6fed390d06bf36da730550ef79de60435c01750c376bcf4f111c15c133314803e867795111b4a3c4208d54135bf0b9452b208267e20b447094f882103c7b0b3e4d6cc1f7221077087ef242e0907a26ae1b4000211d6af0a186e1d087bae7e5d0b3d630f4d4cba167dd23aee9f0e60c4362064f33649b334d53df5e937349433bcfaff3fc5eed429ed0e6afff56dde1665f0c7e6fd6167cbd9b1b68a10b89ab8d6bd737bd0a68e27aefea7b1cf49e6b0c2bd838cf37b1055fa64981be9b1c10264f27079f7b90896bcf4f3fcf1b10a803b2d639405d05ac3fa3c45b816de2e7b90af841e86011bd33c7f702d69ad8b3dbb421e06092af18686fbf621c7fedf0d84d78fb1503e953e0b9b973ddfadca19b373cbf373f56d3b5f79cc723f66e822fd2d2edf3bceb8cd26737af6ad1154ccfa55f9457a22b94fe82d28725895640afa06950da13b4bf307c6b4b18e2125d21fffa832f4b37cbd25f309fe7432c22e7e7dde019e42fbdbcb9b6843b344bb7c620a6b7c6a0a8c72ec4223694c419dcd74e83fb4d77153efe8a01f41503e87da9f3eede41a6f7f28ad6cd5f75da675f51bb7601fb21fbbadf6ea2056c09d9b35a4f6e92f25ebb0b5ac3e98c4dcf79bbce6ac532fcaaf36257c9b5aff7e94bb740dec435c39b6e4bd0f8492736dde0d737974929ef5c22a55bccee020eb41cb910c37284a80e8ab4719e37fdf0c6b776e8deac3a7ffd2c8fcc374223b36e8f3e6f8b258cd43a897cf1cfb9374480f8824ddcb0ddbafd4bdcb0890dd0bfed63c77fde54dfdc1b1c31e9081e9c2317e2b393778eff85cd57d6b3d19fc07dfa998d30babe00bcfe431d60535edf782dbbc84658c4eeb9b63c6ddcd6d000796e6fd6f433ee815632eff65e0f04df660578eee222a067f21c03311bd5a440dddb3e014bbc20107b9bb881a7826fa8e15ebfdaf67a2078d63dbb896950b8ed0a02516bab35107dc47d5df79c3cf27937f6cd7b7b19f7201359257bb1ce48d7dedece466e9db866a3cfce73dbdb3debab4934cd34e3ebd9decd23f27226ba27ec9ee79beb196e4ec77083fabec9f6f19ab0864843c4bfbdf7fefdb6f5cdcbf65e228ff4dc731ef105bd08906e7fbe7f9eddddfafac27e9eef74f4c6c7431d60fd053ca29b68057c6f9db8beb0ed76df496e53074fea09dcf5054c92ab6f933716b36111f6fed66b1aee300daf65a3f55e7bb3d199e370f8b2dca587e04b279dc8e330f485a0059c10b480a385237cbdfa9ec7e39dde028fb71dd0ebb5a00516be475c4bb727eab9e4f934cf01a7a7cf23ae2dfc7693685b106618c23cc25ef1e01c9b2dde381863bc7d38e76abc7142e09cab71cece5bf3f0dc347d763b267926f18ef8ebd7a78345f0fc7a082cc2f4dede9b98a383c3d3c4a69368f10753e3f4dc49310e0892244b2f9df4aca342ce79d2e8b42048ad5691da2bc94a324e3ec0bb9085d4e6072c7dda2e25f1a50474f20e4fceda4760110cc03b5635c9fd2b0f1eddfbf306d349bc49f46111a8eb16a044d2c9f48674e0cd697383fae31cadd19ea661b6166081a8019c23769d9e700e19724d445ac48082040ef5a09f6810456d6aa03da5d07112da2324daf4c2017d9a3aa681f6a4a88720463f5a5f4a60c1a192805134da52b32154a745d54099855ebaefcb975ebe84fbb28402038bd0132466c658a9a51c16166757453bb3196a30bdc1479b1942c732d01e21fa66f4288abe1179d431128aa250c7764408450917463730d3aafd00a20506d891a32f31ee17b0ac232bb441e646d1d35816225daeba824818f901a466c38914313954218cfba585028c82c2b22c85716155e4d650d02519a96bfa5c8288912440d5b021e52503c718f5c5970f061dbb407b8236a589332e63949cbc080a6ef101c367c2cf179159d095245b6a59a68efa99a2692a749b52485ccd22ed05c6075d071d13a13dc1f40643f0a4cc14186a46b52655032c6011a9971e82b2ed4ba8a12fcb1c617e0499531222e38a5a7a098468192c1db7b8556e5d3e4d1db3407bd2122aef60280c3a07b3ce39eb9c669d89607dd6809572ced926173f67c839e79c7316fb9c8539e79c73cf05319f77de207482347d0e660cc49c7f3ebbce39e7bc862118e4f041b7313ee81a8b30c0078343bc3e4804c7071daf407b82410c2eb818d5a09b160fa13dc1b5602a4ca1a07cea3a4d85ecf854c89c90afa54162eaa6c52ad09ef40a7bfd0d72f736368e83d01e1b201dac0a72e48542c729d01e611afcd4f10fda939669f955a2953f8eaa7e60b9a802c2e54bc728ee9694924f5a42a1c247393ceaf6c8a3ae5137514fd1a7258f3a3ee1659a840b09365102edc950249a90d92981b31194a5e5a237d5d20425cfd47650f18104664a0d063592d47111ed497db07814f57b87f6a0656abf9891894b6a01e6890e9718cd52566ed518729685c812ac2f6970c946fd9eb9c72a1e62be745df6c8fad2af1d56ba27d3799ee7799eeec2da9f67799ee7793a0fd29fe8a980f33cdffe3c4fb7e779a6574e743c8a8047fdd6a13d289a1251bf66d01e74158695b9120bfbd26f9912b5722aaa65e997eec672f05f41800d995d8159a9f2611776e22a2c6cae2c97bc15299249c7a03d67aa1343ca4deec4969158873a1b664e514b76692716362c3c12bef88c95619161a3284637a5c99095202628c4c85d7d69f9b2f41b87f69470178ec8e7f3f99c830fec7d3e9fcf87d14aab39a61bc696f50003c753cc7b9f7b6f73933b6f7263995dfdf6fb64dc7be7eddb79bb0b647eef2bbf5ea0316b4d5b45c680e4beecba2437223e256e2cc9b1f7de20b687e0e1fa6deebdb7a67ebbde7b63b133ec3c71fe24f3f9f3248bf2a7a73efe3ccbcabce5980880f7e007964a109fa2fac18b5b0d0051e82519f605400138209c88e178e23526843566e58b56ba62e351df1efce0c0fd22c525000f80f7e0830b2000c41fdc1d619a8aaa0f7e402c7ef00f44ac3424ae58ecaf6720ae58e9d75dc4405c4b241be24ac209892bd91624aee45a4a5c493494b89262dbb7f63ed73e2c3123ef7322e46578baa13d19ed2ca14ab352e94bacaf372c302cacb0a42ea72e16b96c417b360c86be407919b3a548886669c6da9c8caf3914eaaaf3eec356aa7851b24328b9407182abeac8c40b1c549c0f1b9dd68cf36ceb693943ccb2f75a9393bdf7ce58d09e1cc686f69424e86012c70696032ee803483c4c5c6dcdb811b46fb946c52a876f0e0b53d65c9a18aa124f66ecc88ac67e2ca9a79efbeb5b29ef582cc2967eaf60792515c0cadabd627b1d5bba9164061d152c754d7f0a102ca7ea84cc0c2a5dd892bad47d96e5f655b1e18003e2c9d3932767c65e9459f3edbdf7de7b0381fbbdcb725f2a68cf1e009715017e0d411496e5b3a7313e1bc5aa7c66323a29f3d9ef94edbbe5ed8165e323ae784d0ada93af94a0d63b8345ae1835c58c9b141340124d25aa54599ac2e54d0c8d72a15c301f3c692894d1d455d59831aa0b8e22133246a07640b9e5d5c96a518628b121b19426171595457785f54c4f3c9a287dc15146c55728dae9f8ecebed4b331247b8a08c76c72ea520b7bc509c6a58301443a4d2e4b8b404a9f796a13df9eb4802d9cef944c093a2182dc8dc2892a1aee9a701496263256575e449d21519d85996be313561175c9ad7d66f4fbf743c19962bc819fdf8a20b72820b6f6cd19084c22d2f3e415eccb688a9e70dbe2a4b276200e07afbed7abbd9d5159696650a4f27d817367201f992ca0b07453f4e6b4636bb30be332f65494972cc9d75491ba266cfd97b6f8da20b6f8519c1b54f9fddc5fce176702273ec42336ee5d83a4fbdb5af435593de799ea7d6228e9ad442d372f11b84dfdba8f5f4db78cc3ad1b39f23d851db10b0d3fa5230b3628352515775f5252ecc4959a2c415af615d9fdf2bbb3ffff46be53bf70f27beec2bab8c59517e5395c6bb7bbdddebf5cc9e6dfb5e8fe8c4f77abd5e0fad87c601f71c844e082c627fcf79aa7c4f43afd7ebb98dfbdef77abded83855f72f812e7cb1565be2c4b164e5fb6287ed9a2ec4bbfc61cb1528c9c2a7ee9b709da538a956520428c08fa49cb0aa2a2224a48284be84ad61092152c9894687f8b9ffd2e417b7289cf9c88f815e7c7ea490a18bf53b8fd065af2dbaf929c5cb67df69b0409dad3339e3d041cd11c99427b7a3f2ebe34b2f72def8f2fbfbdf72e82a24a4c6aef11b68815d9813445c8f196b7ec05b02b8b0f0e323f37acecad4f983e765851be7b26a0ca4a4af4f5880c417bceb21c21172da2a5880bb12632e8644001b30309c7893067eaaae3986ccc0b9570073b4c9cad28ebe213f582a4aa2436184c414ad4bcf7ded90785809e8920d867c709407cfbf8d454e4f1d41686249f3cd16d7001068ad0d1d8c387effce133bb2a6e9d9550ca2a216782eb84d209b6271d11529ea0bc2832c60c94696e2077bf3708bc9a9851adb919d1a2eebd85d2ddc2feca218de6d711764b766c678c7733b0eea87cce2a4e7df6ebe3425d28139a9860e202ecf84446cc878f298323545a47a624b5e062efed714708128105d784c7de7befbd77e9c401cbcb1700bcf8f286f596950342a7c643d4d47cb1a8f13535353535615f7356545992506951f1d9ef0e97b2a666bf3a4c26b4a7a6b46a11c30bd3993913683987050172ee24b815614938b29ace1c260088f8188e720055685d34c884608135659d34d8820223b3426c87db121516a40d4a9728ef6023190b2159bc8c54b9ab53d7e12dafedd9af0f922564040b8a9d4f1b929a3da7e7f706dad353f2a58d6dcf6c292b5a6254d1c385baeafce96575647218fb026416860564a8d7ab21a250e32659798141020d0148113375f723cc93295d3da00477c0c32d2987d2d0538e7dced061611a987c763de587955b9d8cb23cc92c15b3f06831a4c68631178f1c5ac89c0919a3a28c5df57a489a878b91b02e3f31d26dc53eb87fb83983dca165792f0a47f783f1b7eb6dc585058c74dff206f1edbdb7110e115e6c1740cdc89261e4eac7d4960fb35e88133a7cac5d192b43e2c26acb890e4579e726a7b23c8952b1a52a26e068d1e2dc408a6832115fe11c7c2ccb93c5cef2fbb79b4b3ebffdae1896b7bc59e7cf0e4d34357708edc9a75fa1ab84f565e957c5de245a44b9b80224ee8d4ad7de7bef7dfccde3c7ef5d866c0aac8c8715321f757b09cab22c6bf61984f66ca1141c1b4fa0dc78caba6154aa0194d36386c267c790131456aed60f18553784bac8a08c6151963745960d16bfaf7ecaf48ae01483b465398605a5e4f76fbf284a5082a0f1105884cdd3d0d08ca510317c9aa7f90262a3a8aaace82c8a4ad3f5a3c8cbc40a2f59d4edb34fb204fc8a03c202530f36d66d00bf5a395be7b35ea3cbe405c3abcbe72caf315f5c6029b2c0a0f438b29515e656022b4b6c4c9613aea5ac27749ffdfaa03d39ef1326d01e9a1bb458c1c2461526533ab07894fd5008e92afb42c17502acd7736b51b0d706f942b37bb758425dadb546d343174a77d03d8d234c678d35d65a9fda5d70d320f46be759d2da39b8e8117cadb5d6a5f124f33dd73d2761f64ea2b1e73fd0a531974b3cbe745d96a5fb60581acbf2587a0f74b9b3f776fbe3f756e2f27b6fe7816f02fcf61de82d2634f3f02b881f9154a606970d251b739f3de706d867d7b901e6b3eb00edc93bab75312ae60007688f2ea17870b9d381844897194577dff26aabb476575a4928aa0cd51883afce1bd800edf7f617b43e6dd6318dadc5d69522a1bb6f69656503ff10231504cb4b0d1c5a9fbd1a944e839bb4e437fcf619e4ec03235e72398e439c2c51579dafb26261f2e4e04227356469ebc80519f8dc7e15d2704a693879ed3118d26416658c6f4f5882deb4a8ab4ece39e74cf5b9e8f3fecc002a488ebe1cf530baa2ee4d832626761261d0c256171959aff5950cb9d73396bcf617e80300d5d882d6a28b102b75d5190e5f6badb5d6cea3b5d656b4c814cf843075495235d11a4fe28ad72c8f8ea7d6b4ab35f6dc05684f8f68d32bfc8c03e2fefc1a6284bdf2c2fad592a97db619cdb62e84bcdea1cfeb35fa6cf365b2eb8d52621454750583c567e751e1011638a2f1d9592841d7676f810b659f3d0746568a6b404644313efb45523ab2738ae104eac95e218df9ec2d306e1e2852ad7bc415afb140ebb7ebed2bd0d68c19b11c654cd0526c5100101c280f92c05cf4006b91c5ca265a155cbd587aad3d052ad8bc638d9f802730cdf6d951508a01810e02f840b7550f7c20100824a28d3d41e3f0e381c79599baab3ab02bbb1d9ffd043a3b26c0a9d94dc0d3d37a88778f38e76aef1e09ca38d0f510b1765546eab58fa047c2865b965d508b21a017f7a6e0549541a8cd7061c3c7952d3ad49c7b447c249a5b04e6159d8910dc5ff6dfefe71c7e56fee73f1f621109f85f98a8b238135355be98a8bf174c8a96987d91b11aa36e9f3df4d76d1408d09eac3fe0019e520ce70cf439bb7945b7e5751922af9d85e193090d742fcc3a906a29516959b5813971c47844931a081dc65081f9da1aca32b35a732c5e6b37e7b0bc760ea418378db9d9c0083a93cc93d7ae81b42c411720518e64c415b5dd9b11f0d98791cf193e0375414539d2a872e5c4cd694ee17e98214942542caad9cd0ca4638c0778ed18b8d7e9870d1ac61710236701f30a0bcb588bb22a2d6ee48c020f181988a458754d35d538ab80982d0150808bd3f148d1118f2d72391fe08986dc9b0a2f4a9830b94245cd190de3098851fa20812047d008cce540c0096b0504fc2a942380dc92d7fe8061b9021b5a132542a0888018797c34a50bc0f262c78f14a62f636ace18cd682ccbec80b823af1be03dde1213962d635f734d5a7fce79e7f35397cf992d2a890e197647626acebd340f7f2571051273aefd1157f324ae69af6717f14eba8186618412f0dacd34ccf1b50fe3965e3b039cc41197a6a4256971328888f88183e6cfa0deaccad4b061660160a83e737d76331d7a8eaa43428ac848982073476b2acad2005730e8449958a2a4ea84d931c33346dc9f48637f622171f95346d59f9e8370dec6edcaf3365546575438a9717764a7da35e76d5ce76dbed0aabc8d4dd5dbb8dbe0800c1b50131a4e56f8a8369e81d01e1b20100804f1c0280f040281c023180f3c32f240cf2974705e48f52bcf0bdd0065664cd1c5d9e052445d735ee836ef34002b491693284f8688a9abce0b85421f366179a1d0f30fda234cd314e753b73e699ac200f2a96714684f6ac207d98c1e5144711e9ed4780f1e8cefc1cdbc430163902e544277268eaa07cf3e5ee3ab01e1ad8bafa971d3a8c9d7d4b8777d8de713393c2ca314cfe3f1789e4da03dbc123f9f734e761d1c82a7ea3386bc24e39c4d6461ce350ac81998b34dce39e71a315ece39e79cfd08e9793ccf45b3a6a6a6a6c6f19dc3407b1b1bc7678040c7765a3060bc50e8b80eed119ee7799ee7799ea7633368cf6914f5e99332a60fbc549049e27292ebc20564856e1263cb179718389a909ca09b437bf28b295f964cbe2c4bb5749dbca3d5d279dabea4299794b854c3ea6ad75ca0fbb22c9194b48c866aca5b6631b4373373868a9492dd132c29e6608451c1258612b5bcdb12c40a0a2d25547266746491d3b2455dad11e8da0492f11fae3f5d9f63b4d65a6badc5e49c17689988d68b2872714b4f243a0e83f610b3cc8cde344dd3344dd37433ab8c4d074cceb1f55904b8371e2a282dfee6c6f117b4e786685333edc4a3288aa270e8a3ce53f728aa0445d112288aa28e0211f518c204e3ac58877a097d8da1c26a82437de75761da8d11b0bc9c5469487f9ea71b10840d71b6028beaec89510e2ab4b0cf8ebbb892b18aecea4a131d175b189d840734b69cc879e92197c6650bda83fe7877c76d688f53b5d9780d1a7ca8c15d83e7e49d5483060d6e6ab06e580bdaa373ce393bce8285e7f8f31d5c7770cc666a85f9a1f087487e987f38242a168b27d4e1d0436011bc1fba99776cc264c0f50873e68b511dca41d90a12630637f49c963b58271fb25f557dce576a585852593f59699f5d87276beeb3aba0b575e5882f91ab174cec848c59d280567bb4625c5b501c797d657ced2a84516129aaeb09dc8d326eaa766c65ad0ada3324da942728e945702d82e335b44704a24d4d1740771ca3d62992fcfe6d237b1781404abf6b7691bdcfbd6bf6c60a5af11bb8a57ea37b0f714b31d776c2f8a44bdd51646ca0d81205c9c716a38e2a3da07ebedc414982822b06d30c2c2a6a0acefc5425751923c46bd3c15d25e948a1c30cd0d4a36554569ecfaee316638a93af715de3580ada5343b4a989c5c03d910144c5cbd3d69faea3fe84b216c6065c17195c4dd4d3b19a63a5587a20d07114537338da20e4b50fef5e3b86828290285b6598c418e2a6aee9cf0a2218ba544d998a41c4ac875a3a4e731c553e04d721383e437b4220dad45482f1bfdf90fffd7ebfdf12dbff6a7e427ebffdfbfd7eee82d8ff687e6e2fd1ffdcfeaed2ff7a604ebee29e4851c9507f562ccc0f144729259cb6d435fd5f9834a7222674b490faa95d090d297d69d9d1f673fbfbb9ce1299d75ae350321be61a1fb494b296527c765c566232b467df08f3a5872f1d8fa13de513b4e77763c773e0e0d809dac3814b0ead2ce17b3dcfb947503435bb565ead1b472954af77f67a6ef66e1871617c22a34b89a31e50b17bc2c5448e226359580ced39350084ed49cb0f921a449e5ebb0ef23a8a2bca158f2d4890c854ed384ccb8692862f1d83e59d168c908099d12209c90a1588c867d7403ecf600b0226899b0a246a76fce55a35ea7e6fc75eb86bcb4af145f1a13edf99cf7ac9b9bdcfb5cf4d3147dee7e9d0c4fbc650791f1924eff391d17adfdc8df7cd8d799f632eb4c72737e53364c890c133784edeb94ac562d1849a218387c0227a9f214386140744881127fc6abde0c27c760dc6f839cc8acf616a84018315e60e2bee0c4b2ec867c75b684f16c379cdf3daeabc76382cafb5e6d2d2f6da4dedf8a885f66420da9467cc6fd8e0380bedd940b4a929a4ea83c11f1f74cf3bb79893777482ae13028bc81f749eb30f8ef8e053d06dd0ae7d5007836ee61d176cbdd0123325636a4d0d5699fd5c8991438894baa61f2402a465664716d21529c19b2c3e655234d1920125e85842c25e6bc75842a8321c28bacf8eaf865616aeaa4873939de666e5791abf680f06efadf73506bf294904e1661741205a28675d7624154d49c54500c99d99991a46984869d1f8e831e54c8d31585fcc98a19100d21204eba8895c9622d5062b2832642cd86888991bf260c14c0e3353594c7654c62522c718473232a07e9401399dec18bab9b1b23052cc2c3571810a2e4acad78ab5165f7a4d4f2e3531528507959c99b728262e987848c5387bf12208da6fc70a27452c88b46cb9b2c447d152955695294aca70f8741045b066a49323c245884e25dd95b4f0fa3132f4d5e5a7ecc88f49026e626727e08aae4425c9e4ceda31f8a96f6e8a7f77f1075bc65e90516204e544253715d99427c91c1d9e947145488a21b6a9202ae98e916ce09993738fb88170ce750cc4edf396da8be37fc475f8e4d29fc455c70641714edc641481bda93de26a3ee9040494e5f523c8c9492d897755be4c7c7dbd6139513771f52761b059799114c595a46ae29af3245519d193a4e323d67cb460f06e0daeadc1b6a01c017e0dae41b1c741a55f83377e0ddaf835685c83c7c7412f0c9cb8b2e2c8c2c6d31635005dd49e00296631a3a4ae41a0a0189f322c2340dc6aa86b10e929b075876386152c2ce6d435a8e4634c927163aa079bba0683fc54650991b1ba22455d83d848437387425d69d61ed390ea4a4313805f69eaa068c41ed3a0d5f89526e9571a1abfd218698e8f69bc1e3b4d31ef582a5943b8003121c5879521a38d2d2b5864b14853579ab0ba2cb7a6292a49a0d49506894998a1145dba3c91a91b5c7819a9a47d659d51571a2835868b30476070f1e8a1ae3462697a97ae3d4ed30fbfa64aa9523ae3d754c6af29d2af699a7a3df6b49877480409ab0a2a33da2232a662402d288ad31521aa19ea9a021d31b23d412be195425d531ca422b4245b25669851d774042b369ccc8e45c850a86b3a77a445ecadace9c40c754d4b23107807647b0c04ba01e51e0331119008a816e357e0d1af4018bf028d2b3085ba02bd1e3bb098776ac8b0d4cdb88b232aa11af199fa814221c66c4d5d81408f79a4609105a30cd60b7505ea58f3cae2c2c89a5012750566d1a93989f9c41d7da82b102a0519653c5a240122c384ba02a7cab59fddafdd0da9bfb69f1cf1d71f2efe70f167f4ebefc5af3f17bffe8cebeff83fafc7fe2be61ddb36244a95324534843c5187b47865918244b5445d7f408f5920e1c264c6149f28eafab37a62c6eb8c92949016eafafba931c5a2ac89cb8c13eafa837a0714a9ab7342a598444985b22760a8bbc76e57940d45e5fc57b46e0d25fb8baa15fd8a12fd8a3aa1c6153d3e0915ea8a7a3d76b4987748489d4892e348652eed47c56c59724479b9c214a6ae285013a6ba2c4ecac0505734061328426f5f594c6ea82baaf4654ff8ba3bd5d5e7f3f9e63afcea13f389f95afcea63f1ab6fc5af3ee3ea3b3ef6793d765f31efd82aa4b72d4d62ac48518dd0aec6e4f84124860b75f501593429c71d57762bb87c51571f1209ce8a14c7a2bed8aea8ab4fa9a9471d1d11ba2364a6d4d51707c535b7228bad098cbafa96d81396dce99c6b8fcfacaee72927c2afa7f1349e43bf9e42bf9e2a7e3dbda4fc7a9e5e8ffd2ce61d12299c7cead8ace8dcda54b8aab7212fd098a690d4f5042a809d1818234d704057ea7a226928d2230b59b1051323ea7a1ea0eb8693bab5221219753db99a2489fb528644c90b753da77ed8137edcf57a3db8b9e1af3da8deeda11139f5b2fcdaebd5b027d4b8f351575e8daf3c1e4f2ec3af3c25deca432372e21957def131cfebaeb027dc1577a5ba9aa66962f8d5243289cca05f4da05fcd14bf9ac6d53c3e36bd7e40462d4d8917236535d4b41d603c8cfc1032c6495d4daa1f546d679e947022a5ae269257d2162a5ecab07e9ad4d55422808b2d2546aaa459995357336c0e8891112c5cea6afaa0b4bba351d7b22c4b9b5fcbba5b2c4b14bf963ebf965e73bf96c7c7a5d76312498c2419d3c5a26acecb0c8e78d98001747687455d4b1e4ec8cc7a4451c1c385ba964b4f90242de5882a81435dcb335225887abced809ba2aee5928f152341c7aa2a6eea5a4ec1c09ee0e5aea7ae2449c2cd3d5e491748128dc88934aee4f131e925027b82943b17eade6e5beefc75df75a39df8753bed17dbebcfb8c13111c38a4277a4ae1b0984955a8f1a545490a6d4752b4dc09e60bfeed4deb8ea35dda6e51eafda025a0ccdc4af9aa844f1576d5cf5f1b1f67a5bc6a98696b8a01a5098a8d8cb8dad34b9b5b51a403c2d5e5e7051011627455d35520b50ca188b594c1091a1ae5ae9b109a92e348c62772ed45543bd0898ccd88d09d93a7254573d95809c803b3554b56b869bfb5cf72ae40f62c67cf76b2672cac6351f1f67afc7b60e8b2e488657d6113b1968814a030394238b8cbae612700977412ac658eeb10ed639f32b267242f22b3e3efe157b9da0c8172ca7144c27c0d4a4224977735fb4661851570c848010ec09d7d76bf738843bbc5eb7c7ea7ae51edfbab7fbf512d5fd7acdfc7a8d5f56d7eb317e4b0b5303cec5d814752856d4648b09ae2668ea7a814804155f9818538106d6425d2fd2d316a4b223596ef0b8a2aeb7ed0ec810193f9864e850d70b5522eeaa055894ad2c32541f5804a0f684eb38cd21582b168b3fea0ea9627f01ef68157b0f2c0af67199c7748f5d052c420249656c53c21cc9a106a4621d5d5db850a9c052a453b10ff38e0d138475e5e9c8ab4d858aa3ac6015db9a2c29aaab5bbbb4baad2b28c4f860a1622c53494a647101d7a5ae3c8f4b235a0acbb22ccb6059fa75e2f2655a96655996c6d258966959ba0b4abe2ccbb2e402d2f125992fdd71b0081b2f3d07efa4c56211482d5d2704cfd097c0b29c91c4ca440d2d2b2fb504935372e5eb8595a2b214e37aa1bfd0f114da231462e8bdd0b111b44788d9c4d0f860113328dcf9a063292db71685f6a469508c889c8f4751c7436c1c0b31d19e720d156670ba3352d445ca570b754d7f4c0fa517cc8a51b09809c3fa5183dc8100224c4df861f513fe407bca38b0013cadd9dd318d7912671666c5508549d529b9342626688455f1d1234c5c141ea5c5b236b70594e3c8c2a8ae3adf41eecb2ab12f4b4f5b8072e643c793b2133df494d4bec54e617c1c9b4a2a35aca524b2629838ab2e3643b2292f724aaea08dedb85887962ba6d40423287a838f281a0ce6d038c0109d2dddf4fe90f500e2d6456e489b088eaaecba5a8cbd7d51579d0f03c64acd4c614a0d2e6e6a604f193801a423c8270d8c11a57d03eeca06ca831a67eacab4a2905d39010c8a901339e2c4c6dcf4c2bd4d952470d4c91dd390966401a281beb29fe05219d26710de1ac9dcda7434ef924e40c83937ee3dd73da2999708b15e733e3bceb9bac42b75016637334e4e9a7708b5d6aed422038e3b8ecd5bae1b6dbfba0e59848b4f512e96649469a2140404595d52e8c8e2a38825051c2b9848813b4a4ddda9504c601c4d9983c3216484d17972bef7faf0ba8e452f987bcf7b75ef9abc33bd341df274e0bdcd33558ca29cd19048c468196a6bc2c484baddee9024a9a214a66bbffd760ebeaddade7beb13e4d67b8fb509710e11cb6fe7709a449c1cd7b10bf2c148c59ae8c69a8258d1b1e6c488902e2bb425294842f0e8e878ae83aab1d2800363ea2b889818a30c7547e951c50b4eeb0b99249c1cf739e923ea6c2359d3efb81434dbcd10abebecdca28e359ea64e08ae1b767fbe358e49f955e77f40489b75a4317e47153f341ade1e55743df0ed31880c9a0909f8ebf6ba797d78e521a9889920268666fc9859ed38d285e2ccc86d2b4bda0cf2f8648c919cdc18aa32b94888471148d3b1079dd469e623b664caca0bd80d3507dc92ad20435e4cb123bcc5ba6af1c0b7585745231dee75f3fa3085bdf70ebefd6221f4f769cd4955912b5e6a7032520ffed7bab6737fc7d06a480d69f1b343edc00a2622213a1338ded4ebc3eb36d5b9561dd63c230d2d6cc811c90c99a434203da6bc886371438431296069912a1324c4ecc6097040d125b111c762089a182469367658f13012f3801c4d9a7cb05dd962a688ddd26e8a70f31a34d0fcbec9226fcef92b4ad967fc1987463abcc3c934acd17ccb852445861c18a3131c2d846821c1b268674bbab6c84d291143e1f4258ec895aa277130c8516685e48c9b58d85418b2c7991ad29799af1543547d226cc8451329634fb490495203cad0d8590f3035f0f28686648b50518a29b8e194931748b8dc38fa42802e62702ca5384b222201aa23b3155057aa24791b61234dad686736038c27f99e102912977a975f5e6fa991565ce882ae4a5cf9005399598ee3118ba421cee160d3b6a89959dba1246ccecb56143634a015715b3d7e08a9d9edf06bcbca56d2af4223e42def754bda5c2fb47a465d49bcb45829d1a30ca78b95295645b064714aad70904257a602ae8a96745965a5ad5400d78b1e43c8ab1f8a05f83aecbdf787b75d67ac1ebfed3a53c6496f12aed7e5d866e632265cee767831032a2e21291107054caacdcde6edd7718ceb78e6dbd9755e6fce64faa42689668f0536d14a0165f4761384b75f4261cf7bfb3534e4c1ba7dc10c2166755bc084e142024910181132556e536c4610a990832535c6e9969cac93e34c1122654ad6e24610b2b6c328264808c8ceaf3c6b8e0e0c21098031049b6fbf60f030ed170c1e7f731089c2fce6a97b6f00bc3db6b8f1346f8f44559e36d97625f4322116dc41921334a5a4af1c407840615b8c438ea8487a1a9e287e3678a1583ad988d688c32383978f91cd190c6e41af135450ad35bcf53a91e5cb3a02d4f23a28c5ebf4f53128c66ba2aa75076ed061dd484a521b53b5f16a0d495d9c992c672469665923e19862ca031399191734a284ccb0e1414c092b4e51b0a09881ad7c47a0301a6d80b7c714511ec3db23d08fed058b568f9e56144d0a7565bc70719a82b204624a13912c51277c8e6078fba565f4beb75f5a4044d84a986af20526881d9b91ed338c1f2668725350a684210b406abda40cbddf2b7fbdd4eafe3a079cbf8e55a4d8cd7bff9ada7d588845d8afd33d1e8f38d43ef49c6cb59b9ae81cbc6d4ee75ce23a7c6b9421b404b5e30d0eec5af9ecc2680dc7db982e202ada636e49c9b75f5b3dd21e22c8dedd4fc8721f5914fdded942d180b6b79b677eefbdabc49d5d6792984d4da88e9a74587d15a93b871a518d3931684437674ff33a11e433bc3daea02a832c49deacd94907214803e3179044f3eebdddeaad497293fb622ce536f11a901600693805cac37673afad709145e5c7437044cb8dbb1e71744dd49d663402b503051c1b1395ac295c7ec71f2aa89b1126485b4d34c2d4eda6240b151c51566460a97b935beb7c5c816616e0edf167e97f6fb3a6cc59d9ba312c48a8f8f09acb32c59a114a6261451455c5090ccc4de62caf39ac4d7b245223e5e8a48f4f25103228e0ac9c7073a242d546202b6c277cb88e49d6a7b5d6f94e6bad79c6985a6b0baa855ba7a950cbec7f6fbfb48ebfa63da8d26e32c63653f8bcf5e2b39797eca65038ecbdfdda0a3a96195353d1257fb1b1653f80c2c44519f97163cd7284622ceeca8ab2f32d970aaf22648ed24548eea649850c1d4c36b824a9ba84da992f2b424149cc549e35256db6c2d35aef736fd3b7a985d05ef75e731d5f93aad6da4d7d8bc2b75c2acedef76baaa59c9684749bbe1dc29640661ddfc4d5a7374eb39989660b32d2d57ccce17d2052d357bc4dbd4dd2247def4d1239e01c13e7acbebfbe0acb4dc4a9bef1996e7f354d2287215b4ae2efcecbc4a3933f58cff1e165ba9804be796c97be847e1da9a2b59bba24fa5273fb6a6edeb0e8b5363363a754b583b0a6d65b6b2d1c0edf5295a1fb55a879d7ccc61fd69e89da6abfc4a65ebaa3d528df355d6836f5dcf49e49ba70c793cec1896b0b384f7aa691dabf5ea664da93592549b72649240e71cefd9b0af75bae2118bfa6be874e5dfa3ac26ed242d15ae391d160c78e696b6f4ca68c7952b3d60e225aa4bc6c6dbda924d197ee1bdd64ba769328bc699f8f449f9d03d710dbe71a35a3808850919b950f2e3eaa07a44401997d854d51a166e2da024fcec537c29b21fe701dc35bae21b05f8524ce6560ea884059c17436042d431d588b1e39aabcaa40704144c6628797a1b57b0676a445cae94c0e266f291671cda4d2b49414e0034e1c4932e52ea88a18755bc1282a22819252dcf0f60be9c5936fbfa4c6a44eec1bef1b8d4ded9668122dceb122b4cef8f3dd1d02300deebf80ffdaeb0eb8f8e605fc17ceba6dc1ebe7edf55baf14401897f9de6b7a695af4caf9bd54784064e708aeb232f26e37b73cc35b2ea0259fbee502fab1a54eac6f99326d72af6bf69d735ec9bd3d7f586fd6bc77495c872449a664ce66fe606a93687591ad9e68e645400fc55bb7004fdeb146bb00bd7d588415a1f7defbf7ab808fbcb0986fbf8ce67ed529e6e11bf1f68879188045e0b77ac8a989429c93829d198bb7c42c87a9cdc6b21ebc2526a9490734f19ce41153fce1762893b6b74912b191d4a6b8cc8f0e65867c76d3ad107fb8ee7bcb3534a30c8e5fd3bf253665d8369e5bd310db66c2b9138ff318e1d0724cf735d3cde39ae96b4337dd74311ed3b470451e1e1e1e1e1e1e539bda5cbbd7bcd7bce6bda6bee635af79afa94d6d5ed3534f754eeae64d531da78b4dcd93335cbbd8d4e131af79f335f5f0e2f46233d57eaf69e6e438a5a6a92f36b5ce1dea1c3779cca136af79af9993ded4d4663ad4437d4d9da34da7cb939adaf59aa9ef1dea6b4ccd54eb983cd74dd3d43cc3344d53d31ceaa11eeaa176f7abc3a3c3a387d7bcc31cad738766aa7d62f7def49ada8a6953f3985adf6b9a9684d4fce2c3ede2c5f256cbdba1fd8a821a4335e46c8bbaf2bce53167e3448b201a56d455e76d5892b9371e4d82f850a7545942442a8ecb1575b5c7b25f2d1ad9afd698f3f60dc593b9a42b512b549b82ca14a92c4851c2a8d6daf4edd85beb25b06e470605585a17b6353b246e8bdc54912b46d65a9e8b1552b82801b3e5cd16d5c802232a06da9b15e22257a54b89981475bd725436a82879c1c44444bd6c2fc0e034d6d7ab16039e6f8107edc9af57cc89bd4a62d66d8d259d7493f4148bd8e0a40b874c2989a394f3a44e083aa55f7990d475c49357470ebe5a31b21b5f7eb5683a56edad857b522ec7d2af76ced6f95c20117cbd444173bfde9bf4a4af97c9a983af172aea98f6eb9d3a52d7abe4495fefd5f1c92b46d696f5eb452b52d7abf6e4653341ca41e1f1eb9d1352d75bf724f6c14044aa5f31d293469cf424931394d18d5ff1140c75c54aaec48ed07ec56832d415ab3d4942e1b627492c5783c9af786e85bae2ba27b3cf9319e849d2b311d3d4af1929273d99999c707ccd5051665cfc9aa752254fe6ab7c0480af598cec45dcaf194d67cd6a996d275d73db93192ecba59124d50d146e4c55b9617fba8fe0e769e3e7d9a4c69fbd33c9799270deb5d3b6fd5c78f1a7efbc60e799c354020aad145b57c26c80a967da12108f33b22244d435b5470d14017b6164266d0cd902985ecc9d298991b1cbc9c971dbf439ae7372b09a98f91ce3d8e778b681f6e4106dea241038fc75c403ed15700ce81668dda6dc1ee81a78e4ea815847b890c840d2e581aec373e281ae4292ae07fa036e3cd02da064ee81cec292ae077a09981e88c5a4ee819e6ba03dc0243e724367ce502c11a96bfa24b8c5224820e1eb49f85961a468c78b283dc8a824784e427b4820dad46dced8d894606333e26d6cdc12bd8d6b1b37a5b6bc8da75261dea608dcdb18c97a1b1b23716f33c5f5369e69a03d362360490dfd08238c3082e71968cf08449b7a9681f69c500f00d700f08c84f60080685313eb85d27bf0e0f51eee7bf0e0391e80ee610577268ee058789dd1a91ec2e882d4ccdaeaa8b05cd43d10e83906d5d28df3f47c84f69c3078caa83d0e8e6723b407876853b34cd6f3303cafeb79ee38bcd3792816b429b626a11e402acf8a992070766b5ac2ce00d04dfd799e7454fe2c93f4a7e71740201008f4eca28847287c218717ba700d31e28542277ba16ba19ba992f1853e547a21d652da0b855838945e98e3e78539a8bcd03311da233ccef834e5fa344d53a1a7a9f36c7d7aa65b690a4c539b347533ef6808d222ea0c8809ab899a325df9d27a1165034b25f2642c320624ab4b8a840e09da0d2c4f8ad4f586d4dbd8786e918147209068ed68cbdd97981c52d435e78165deae6029a9a212a4ae3a37763c1008fcf2caf140a06716680fd07863e88542cf2bd01ee16d32a309135b5c142a2b9a748c6427aed69fee2c84a0980342922446a49e9e87b210da930600e76b407c4d88af195133a2c692cd40c161c6d2d7f8d0bbbec67366d47d8deb2cf91ae7a161f735aec203747c0d565295aff1ac02eda931c2f25a67bd76c7d109c1d3f53ad5585a7bd0627abb60f65a83bed258235d25f23a8579adad34d636fa54b361b64099500245cb5913950ad7da8f24b136b724ea9abebef216c6280a52122532fa465397991730e6c27409cd3911612d255142312309521063939d9538a6187068c727aa14a6205fecb89808f2a413709e1cd640fc19e2cf11476f7fba3edd8c51e44f4f03a688a874aa19a93339c81465350400000073160030200c0a88c582d13c11c4527a061480106ab2525e4a1a4803e220c861148498318618030c30060c01ccd04c1b00f62a0284654235f05124d754493236ec89ba8a96e8e682896d2801b18a9ea34dc862248c5ff0af1f59fe5e00dbe67e07ea77fb276e08d54b83b6fa14b5ddc27b7368e8fc04850d6ab21e6016d90b62d6036d14018292ecdcd440b1c0a68c043eca77a68f1b503ee4b0f33ed80b6e1323c22bbe262565f660ebb707fd884f3e50ea0a49ae1bb395212da9badb1a66ab862b8311b1256f1bc4b0007a722d1d7ee2d2d39cd615d57848da806668055cca51cc07b0ae6bee79c3db45e5871d03f82c2fa627ef9d03f35372b319e37a9f889c9d0e3716b1cf7d17262a635c32686cb134d7f22248e891ae2f3810b7c661c219cbc0b7c64c8e863ef8c52b8520aaf2937de4cca05519088e3ae13215e01c6f5ceaed42b0f528a267aa11f25c6f86615e30ac7ec288a25f3d4ddd367623693fd738b9232b7e0db58597b36c0520935c5f5463f5a6999b940acee05420a9547688c181d3141b114b91244ce424ddae74506ef32c0ef992e26ae2375bddb347563ae90dbbee5a8062f944a56f328f104598e0beffe52dc456323614c23fbd2acb7b9f0e85bb8692e8eaa6506b0accfa2b0693e768d355f5ffc1231fc3f085f5abb103215ed40995b6885cb8d6f9aa63c3d724c054cb1eddcce17078d4b8fe28a89b39a9eb374c91b4a134e46e2ddf37c8ed0a53006a9a1818dc0379ca77d82cee8379502a02bb8c18db2c91720af52a606fcb0289ad538da5fd370800aa81a81cb7bd253a05862b93a4b4643915963f343efc5b5dbcdb525f8aba432e0fd074604ac5037f7255a99301d6ce11401eab7a684977e800396d3bd90e3b1182b60159d922e9679fc6669e688db35423fc2d09cee3df90afaaadf291a97774679ff0e4ce4152dd2ce489cf39c7cbf00f1e491b200592de2a7063d3204168b7a4892956a05a2521c8141b24c95f016b6dd25d08cd3768ae8fbb3a48840e99499e543e55d765777af118926071a1240d80c48746e04875980dc4f49215e886be5419c4f2ceedc53cbdd21ee001cfdf4cf98a638c28cf551a3a6f0130d5ceb524fa36ce60dd9fee2ddab9cf677c1a588724a2cf486e70b55bfbcb5ace9850cd3891b03a9273aeb58039c2c630a72c62503157afc3f7df96b0e8412e0bf484e812e975e15ef75ded7437f31c85ff2c1017a0275fb37d6f284b35be7198e9ae64b8a0bccdd6b64e6210cfd894d64df5672d24d6e95e7b90b6fe682dbd160bf08f61b04bccc30464f159bc516a9fca7f08770282c183eac91d3d7190ff86d80883ca9545f3d6982cf161b32a9b89243378d52afec98275b5602363aa966c18bd549d951628d1ba654aac11c6fe827a604c0c5e9ee3e623392e661b83ddc902d7361903720c82fa15a42e51bd8dc56091ec23982d3b18dc6ac83d4e2d2b8983dd7695387d5f8dad4334d2a1109230d1d7d0810c54a6e023eb559172f135c5d5c04e417d56df7c976776f53459ebad2a49469afd73cfab35e2b42955654927b4d3bd03d42ef92125e2d6eb51e054ba53ad26b88d0f8cf5d22ed191d39c5e644dd64a301d0394815898a5976ebaf6b16094f2cc4517b99e637d9a1620e57dc3bb5ab047274a30368c7ac7213cafa46eb7c3b477afb5c93f22f35e8ea8853558b87e8a5551882dbb60a55551bab8f974ffbc0c62c02e33a7cd72a705e185bd5d1e054bf487343609bed59e81b1596c40106677b5adac540fdbc89c2a249e7e4f7915e324157be08ade196b3d110df32a8f980727e4a39bc959215c3293e4a9b1d163f265f938301f855da3d1c28519c4704929dc0cfb4d590f7b769e977ca2e898bbab0fa66d600b3d92e5e797b08a38106a08e10746ad6b0074f056336b49ab368515e1727feb2110d9b88550864ec90ce1bd3fd4121a317b08fbde17b69a954ccc0f41b08c00d8c5c0e7563262e5c368ecf4e2c64a0f136957f01413308e62726cc70b323a8cb9b1bfae79a73ac0ce53e9354d783c969f605df9a5f6e02b6a60e084a1fea3a65bca20566b44b0837c96250e9bfc4cc0e63b1f847c117873aca8d31f8b3c03f195200af892cf9b38b0deb6baed49fdf36873293e424d35eabd69feead8cbc2472ca64b15498bf458b91205b73cf972302678415791f6408145330e43258bd158085ba630715d432554adf1e5256c0dd91e3c678b0b6831481141421831ce8efb6240092ccb9b61358430cf8ec61a14e3cb95181526c38b63985d65f83a25ba683b5763602a12d7010b3f9d13aeeb334df3c0f4b4ebe2f2a9532a453abd23417814f5a2e88cba936caa1e0585ef5375c023f2a66b3be608cdf7559204d9e34e3e3b11da5c5069217eea1c526b8fc440fbbf497b80c0a4f39713e5262c44ce072549dd74046b94cd0f3dc1c63ce7c78c25937a080f16e02f5e776ca7b6ef0007ceb951ee2086b7467239bc446a7982b614e1857818b8c4620f57e980e4814bd1d7eb920e118b8c54e8900bc772179e077050574a7b4d9ba37dc94680023000b2c98eb7d84214768291a0887882861b71bc172e80c47eff6a48f5d10b040a6cb15a47b5f8e1223473839a2b28568c400f64fcc6d7ed3ad3268bcf1ed54f36e3e0bb4615cc5d9065af6233d6a7f49acfb5bf9467cfb10ef75b98ea0c6cc2e6b79ce8ef89405746e4a80188a176d491be364aba52ca203fabbad8ee87f5ec8f802081309718e3596b09055dc690df9a0afb6a9b47403c79bc1013aa166adec8d9eb005345a5d66c7855b7cc5f1cf44bbe37ec55cd82c355dcc1f7590d7e13c96d306cb193ed4ecae10ecb71f0220e9e2553adaf2bd5839f4abe5ba9f59ca937f1344f159b862cfdf13b9815f0673dc515e5c9fabf199a51b0de1b17376e41541b0544174200319e211741e6b4194533f306b6362a773ec1e247f06c8c3576c4ac60334a0f9666c7401d26538113865ba88f3a32568b1e501a373ceb012a4995a19513ef5b2993ac2df42c226d8fcaf4f86820ffb66332f20ab74d8a4df29429f632e54436bc03dac03e04351a25ec10bef852c61585212043ae8dd4a0b0599bf49c01e1257155867c77028082514a997ad447209aff2768d902aeaccbc2dad42e20ede408f0c909e398f9af7f899d4e2b3c8a32c168054ad52f3c4706ee1a80fd629ced72b59c8d5dda06f1396766e6151943502a6ef41b2def18069330898f47c73fe4be0862e2c829cbbae25d395131cca27e02857aba980ac89610dc04c447ea6c6021dfb9cae346f9478ee5e86f9b23294fc4d65cb4fee775b33ae3ffdfa9270e3063d06f3d3dcdc0d584f2d7c34382099a1a35a5eb8c52bab7e213f8077687908713fa57f6b04215f4f38304da0f115deead0e4242583b28961f12cffcd9761bbf4c21319cc10077638b54973f3b1fcb6e222c682666b555dbcce48f7fb6c11ed11ce7d38e0e880f49d90b6d23d4d4ba218aba41a8d38a9edab016f5eaa5f3b7f99a0500c004e0e33656ce34c201225b3ced76f83fb95dadcfc142d15d183d3072e34cfa939a2817b32e985442c31ea4bac3814fd80541b45219e042d699aa699375188fe40714f3b7f5a6cf882b87e25ce466640891528a0b59d02096735448a0d08224405072a8706a944dceb79af02dea5e70f516acdec03648fb9a2a35314d5c303509e1dc3d76aab0a10509bc310ad2a89244217b571b063ab30c30c36cec58fd5d85af392153def463f79c760f5c6c34bdefae4a7132bb858aaaf6c072418797c862d21e0fcc8318fb44459d13c3f5f3003338ec26915465c4f059d7f34ec358525a535e1a77a9d829c379e30547b903ff65b3a9b5db793e00fe8615ef54190d1711b333e91dc0704f360fc0cc6269aa9a454ab667e5de6ecc2b03b6e0a4697a799fa8aaf169e52854d9e202bc57ea31fa48323ca84ad8f0d46c77d61b8c51e51b418526f03291561c90f652a33f5662694e19d5dc6634144e04a31ef2dc1e91da5bc8258ac0347e487b7b8812085d88ef34c0495dbbc5b4bc8895327e4ffcb83bb41477ce47b2a8f50cea98ee387f1936ee723b8a5c6d1b5eddaa1284b4386e1a2411e7a19859917b7c448bd32608592d281d7032f34f79a52bec6252a73bb5a7892f896b4de111399e102c2aeceeea5695cfadf70ced82a6768b42ae913eaf38b147b2a5cd1a0afb34c68fd8c272d322447ff05ab3e3cfde05603b25760dda616ff9d40c3ab0e91ab76107a4315f9333331c1887cc4b72ad0f99cd9d88b228354f46ffaf2a3f3043439d5cb20ad7a1a1dc3ead159e7691b4b87cf1d2062aa23ae1c6b00137d6dc1c8b8d7fd9bd5ee4318a5bc323f29e9d3a6da54c740f695afae957852192825b0a2d2c5b0cf10b2410d74fc144e181005932e23d191a8488c0c16ae68df8667c8047ebffef3472a40343364c4173aca467424ad11153d0f8da3012668029a0cbf6576842a4b8cd483581dbc34ae42a35aecdd0c80aafce7ae784e8ed7abb1f36241cdc707f49e58180594bd885fc84564561afa727b96f8434628b2a359f1aa837e6a604f7d0bb207255182a0ecc487f9bb5a5a6a893c3c0e029020c8dc2dc0780a3db025f6df4a96a12e3ba95a9ff1b75cb9e17638af62da335968ac557fb90a18674e06b5e61577d24b88f3ba3acafd568533937d03b847627929b08d555c4e026cfcbb5238e8f1ea54dccbdcba3538615bf3849158b848bfc9f1126646b12c3abc028c972e0c2615fb7eaf039a42f02a1d6860a19213b40a106f30a54702bcee2a9876365a4d555d6084cce7f0313bef8da21d5501839f4ee29edc11f2a4d1097fee8d042196f19c661a34d73b912d50028bcca8371651b176f3d971153638e36ba83d3a84f82666056cf8276fb233c92ba60234e95bc95bac18093d14607ddf632b521c3fe05d6ed67147517d612d3be6968dae5f6a1f7b9a1b707464fbf7c84d66d9da6e546077cd9cc4c8b45613fba0ef17e1cb52e4518cdd15884aa0facb81308a47886576ad5039de31b711aa6eb0813dc18cab0c6d061bbd3ce8492749778a6e9d2e869e715aa87f4935a6778f20b6f2d671a09f52c165664768374de16873882d22f839971a2ccb0d42a447837d7d2a8a903101d85b3c346055d73b1fefdd2097acbd94efbeead2cb1c4fadd0da336c2337003ddcbf57ce7879d84948fe3f352fb61998e14f32e880cdca9ed8689196384498d4ee4315c68f7d704c04458fa4ff7138739db74ba068e9da7b69a7594aad1a0d0c87a089669f987e15df7f5078a954078526b4e6059d1019c015709d7a8d945ed6e5c83a793d8cc67688ce1d4a96ee8f703f3ca833ae4cf4dce51bf2e664387699ef0a0f17409cd38a0e2abad90f91a6f82cb4ee951b1b0550db54c1f21f815233b9bb492107b750bd83d7537d349eb1ff49a845043c30f9a7dffeaa6669c8d7600573373b228607b0062242a7eb220fc7ac9326f5ac031c19352f3909d41b361814db19c3a8821919416355833cb5146d2ab8b72bdc00952bafd38ca81e888fc7c4ba326685db28e50d8395e703ce03c327c0c4719e85874352123945c4207780c5b4fc1adb312e420021da907df59c66eae083a3247390154fdf28459c4baaad0a2d351dbc3b8c06f0fa39a1fca58e89948bbaa04145415e882d7bae3421d010b680335d4baadde2c00e9185959b1931a8a1b626ca8c01772bc1bf2c42d59675223436be8f09c435db2435a1a6d6937ac1bd057a0f809da00a96b1aab69937604dafbf9de688cbe7824ea264a33656c4d0294b9f853949899aa8a1fbef8e10a558ca5afa070cc06339806f5fc54268613c1c9ef77b2686ce880ca57d4b4cd8e31f4f633b7d3873862e84fe97285ea70bb697b69986032719efc9cbf97d2bc23ace9264432811a7b611d5701ec4e064e1e87a25806bfd624c4f5ec9f30909d2a12d0f8e9aef15e82c5ff9b4b71babf4236d179d9e2666859d062124939ba10cf490495016eb2b64904828a355e11362002b72eab410799e6a34517815e528dd215f7a5411b6a288a24c692154e2f1cae9fac0bcfccc374ce79d3937809e03569bab05ebe04577eb505dc6914c546116184c247f799194df59b8b84f74691dc981f2162ee0fd2487316b730cfa1645cb65ebac481e0d432b2d230122ca532b47f048a3908883322a63edf83f989c0c1633722df693805d73b8b35c61f848c156bd8eb7041040bf5a924c5cb5344ed2e0e8ac6a438bef13289d79bd6b9e1c84330f19547e18f075f612faa25d1a1c849c604c04fa307869406ecfc193e0a7628d8b0bbe837e2bec0af4e542864d973bc936b6de3f4ccae293a4a305afc3e7390e18a0a414bbf5e087f459c0576a2658faed638a4d2c32797fe9b32d8c64f049748bc11e6cddf4ebea6cf1a00caaca83772b7b195e3156c398637a9e527dfccb97686c8de0734f633a10eda245664609dfa9bc42b046f9c743ae3f045b3baa2222ee39ade60f5e8c396d23ee4e3fe094fa2e126647d2d0a43bcd4e078df356ea0a1d1afd144386fc7df931735ee3c0635c88b463fab086fbddec733ca0377f9ba51651eed534bb7e37795fc0913077890020fa4a8eb37fb2d7879c8118931209f1780068ec15108a08b222003c319c939c3d110dcf7e7bbdf5f3c365d7fd3033ba4a5a3f972be21904c2b64cff829f5b070ab2421b9b12093af3a0cf0d6e0b1c062d199129dbbf32e6acc1d229f0852722b011cc1332a58806676d19907de02d8518fb468f0bece2aa75009ec9bac4d417aa26a173c4105aa2d01787e12cf6c7caf1b008d022c579849f30133d72a4f21d7a48361de8f585c75f2ee6058322f92777dcf4a099f4e0047b1e70b964cd883bbb6f81e3816a00c5e8e15c01d8c2d1932abe88ceb1ed83a974709fd9595d610e1f5df5bb88403d9a55b1647c739d57cc6f566318cb467cf63853f1ed20b2a48b27e2910286caeafa8a0b6086027aea52c46b63d41cab5c3ed7b1430f2bc90b5f1a2b92b8b6b4bb4f995660f3be2fd90cd8e4c624db51c0d9339ac7ac25d996f3911074359b2ce8ba8293ef025311f23b40a6e1165ceb8daed56cb5829aa19574c791c759cc1ade308111fdabcf5a6fd141875694eab79f3ac6993b7445ca4420410597696c127335195e893f34a98ef7bb33363b2ee802daa894c2d386e617a11a9e7277a92caad352702ca7b741328e4cf2510e0f6396190003c0f32fac9ec8a55d1192379ca7d0913edfb34a3cc645a6e2227c4cfcfef4c1d7bb9c947aada568a0ad74186b75b1ba3a4e472daba8a40551421faeda8c57792c34a63c3dcb7d435db0b3e2bb0b4c903598d3750282c12db10335cc711a644ffd2f8ade3c79b7243311253bbb10f42a85f018f0e30555abc068e480e0dbffd897bbbddc503f23852cff8966a55e1f76da65eae15331d81aee4dea0c8619f65130b1d661fd880bba4cb45813c85eeb019699693561a721d69ca67547197b714ecbfd152942ed86cb23560409ee13efa1d50a0c746d3725a3458ead8a61a120e605d666814fd3dfcb0d20e026b9e2e9b2f3316dc04a44bacf678367a89d18919ebad0288552e4483d104d6d832a5e55aaca6e0757b0471057cac14b0f62c9e5a0c6924f261456d9c463ab4b37940621563f34a05969fe207406ba29526d668709ca85662a18eaf9a183d83dcaa209bd020b666ad800f4e1d683d133ac8b6e979abe8eca9dd00f534a429a57f27ea18882d9bc557c6e586d703c4a637fe07f09784f59335bf99a89d9e9c65222f56a190c97ce9f5354751b08697345e0263de5b1faf38461216e101fb7ca1670f670b5df833c4dbb5aae5b22bd98ee30d4b5d693538bd91452d8e913b688d5caec78e4fa55a72191cba7b3cf74844cca3a1540fc742b863b57631d1f9defae03c291cb5433532ad5435b4b2e1a003da226f30c24adacf0677d0a4dec0ca2e5b65cf06b66a9cfd1c520f75f9ce9cbc6596b5f9205dcc4f11d1143d6ca53bb984987d2719da9eb49f18ddfbc25e2a94caf993e54ff9e1d2ffc396087de671931ab770229892da9f70ae0bd9337afb4ddf248b08037ad40c067f5f0bf90a672cae21f65f08f3e77a7d8a45c709d7efdc9abe4ceff9b6b0ae9aa99d743d1707ca305257ceb14853a19b2f534a6384f4a0bcd914522c2fbf3f3338722177370d776862f939538d8f21ee9d4b90bd8bd0c88e837a15c70c083240035eb9694c374514a895a136df02e3e5772acc3600894178bfc81e11098cc3e10c92f78353c68136ddeada03581c16ff88a509e55c98ca52a9e0e33d091a754a2aa99ca12f0ab620b15a56d4bdbaddfd8ef24551cdfdaf486e57209da34482d3b0fd6fe6dc349c3f843a1c76a3062defb21b9cee47105c4a6255d963e129bfef0a377e249dfd3bb465c34d23763ae400ee1529d7472d88fc967a954a806361402ab1844a75a407c495db536c283de0cc3860d7470114e197ff0f9211c883a780703fab8d202af80bd908d67bfcb90233b232babef44b68507bade7d0ffba219fbfe54404345c5083a5ef5d4f57ad4c1e0f74af7bcb2717b9f4f9ee4606bce3efcd20ca6e2225e5771d1f332913338dbade00d10b4f7a1188d7f69d85f330e4bcd2c8bbfe990e4a257eb6dd076670fc3181731e35b08c53c6e7ac3e7a0f1c52a54c634a7c980966ca334f28474dde7283a98d69e4a4ca4c26806a4b3990d2c339f181147b07443820c6d02a3196e7186126e29f764814d9ddfda438ea62ead184865a83091b237b961bf01060e8f1941ad84bedbc0cb3ec5d72896ded40d5060c2f88abcbf38bbfbb1eb41087f5216e07b57edc6f335350a3c924625192b7c0cd6133b26cf30dda06a5ae9dbbd98f5963e4a2e315cba72104f986189bc70fb7193ef7bfb45d0de59b63978bc1c441a4ac31ddd4f96e44d1cfe03b8bcd96c9ce0ccd703e40c36773b2fd0259150eb83d7253c39094de2c181d6a55de5941203f2c3d11fa7975a24406c513ba1c051d4f33a4cdb3eadb4d229a299f850b2fa227e918f81f4cfe50272b7fdf9a1d9a81fb2ccde3e4a98f64906f2d18964795391346c87c3825bf14c1270e6d12ad36eeac73807c3b24e766809905ad114b14e3f291e0e56aeafb4d7ecfe393d60084722374c3c2a1847d2ae1fd0b52aa68c9e038336b662d7b867678d4c704f801ae98cc1c0dba253974045eade9d836d2f5d246705cb5b566757be9502ad2bd87094868fa65864ebe61b693180c1f126c5e70e9c213d0a0c7137522221a6e7a2cf7163ec6c3c42d2af595a9cd15415ae93cde70f9f4b949c535e5f0cc8ede03fce697e1ab49066bd84ddc38b4ea196d8e1f74f9eb1b4b08888ae3d52160d012a473c5bae84e52e335cc239d5ba45629ac70b16bf851170a8284ed20730426eb6bac1a86262b73616097c57e05bcb43e21850c0df74ffc17427c138d466baaed77f9b1e8693c7fb7ed9b4216ebcce70987266cc5e9b841b74de5a00ea6b2c185c916de5eadc343640583509a6293d78ecbdf774e5b352ccb4fc6dae6c02354c43918f1d700016812ad9c5b4ba54318b821cc76468552b236fb9e3be0eb4bb8b69feecfb4cd22215425e5690b069db680e12247729d085ea513cd83b1a256e353a2e2fd5d8ffcf1cf0e7a761f9240c8e1a46061077545c3453aca4477ca4bcfecdb3de93343e6a7d69721f69f47a5a0e98c4c04df235eb205bffed5cad9ec22ac320a71bae1e01d3b89562e22410c4d90ffc54e586622513249948614156ee71cede842c80af6ac46d943d084a632b720f1ba543c9c680562d68f436afe4547522387985935ba03a32ce33c31d9bddc8118be29fdbe8477953032891ff53f77e5c1ddc2f5ceeec55cd539320b7056bbf9fae7c0f79582cfd141badbc92f47d80c850609888016aedd0c4b0e4b62a1b9d11834091c99b48507105cea1bfcede9cfc1c6ebbcdd85b2183b2f8dbbaf0b35aec58d8d0d84762707241b7ba8bdc04ff58079fabdef8df21201566ecd82ed353c8f5105411bbefb7c015ce1ab1b32a8a7eed8f4eb42ec35178dae40a16c8c2ec30e3f42b747dd1a377b0c925583f0c5bd172e86204c793bc314d22a60cc20b89bd9156f41eb97dd43c00d21281fb807a0ef3dc7f16638eaa198cfb2dcf6edb7f3d467d6cfc09728e4b8978bf76dea17b53c0eff1c841446600a4656b935b16b1bf61f76984c83be1fec31e1a0b02824f4bfa32093d3050a650d28d168d41358698af38f72422d85494bf0385b4478cdc86460c6299089281a89be2d1172e7ccc6c40b6662a648f7bd04e98b181f31a2614fe0bf6df0e0f51333fd1b553c30115567bc5bd7906a196d5b92e456dbb186fc91271ca9c52c2347db21c4ab46b9436c0bb6e427944abaf58f876397c325c38fee0523acbe466bef3442e6795fe9fae6606b49bd094d32a032c74c11fc30c111fa4cb84b5a437215d3d30a6af49f97a95b6edf09238d8fed52dcb533748aef42a7a9fbaab1b50b3ba8e12b6f4474ac851525e202d07fd8d8ddd54344e616025eefe2d3e5e62dd9d1738a08666e4f1728ad8d65e5b3d39e470d992b01dcd8300062e66ed74e01aa4bc7848930e4d56cee5dd6b0d0bafea0b787b15062857fc70aa1f43aa251f60b7fb6e2b5d3ce4642fa1a017a633606421987201bbd889f61f911562239a8f6f7a6f65edf954a2b7ec08db6122a48b0b7725688830be17e0a92e4da36de1206d36f7b43c9bb4a80960dd98524897c182d21a61850092010910c1215962e29a38a0744b23242722e24972646e7a6b768210b52a3da10ad70247a7f565eab5ce4a4e96c8150a2f2a35ad3b0f1d8d42aa194c804b72b422921d6c59e97aafaff67edd04075a09e298ad00922e1b4787e657c1f801a2d0a50a2b5e5a8230f6ec33b45c07327181afe917a75b5d7d8d6814e887ec03f4b3334a2cc9d0bcc5a677310e96a6cea4b12dbfa84adac9adc222fe51f762a4163af0b94fed0dbd8f26283f6ff733e8e402c52687679da8a2effdeaecbcaca2d7276dc1e914ede3105cb3709408cd3786b11b3044c8d18229354e28e297cbe649661664274295f01db6e8a7f55e7701ead1cbdccf3bf6376ebece7464aa4cd0b44e17ab8ae5bb1be790725b29c3471ad816d461c1c37c1bde38833eb106239b739591c931f28fb986d07ef757f377f7cac81a2576b3ae8230c4a6f06ae97e3100da9b13246c98ae0f979d428ca603311bc3cf18695df73d6e74d61bf08e2438b314d474df7cdebd606d24dd31fe811dc2883cf9e8d5a4910c10840c97d9debfbab0b08d18ce31ad41a8b75f42af4ce20644dc774204088c14227447f22c1bf109aa02f8cdd777fe3a0683455f02df859368e36311c2b76bca91c54ee2e7c19990f0e5d85aed81c155946c1bf644c3fe10dbf00744f082e35f1fb6963fde9c416d1d3e4c67e52e75f1622bd9231cb4ec8710b07b70c765a67e3795aca28211ac30f74f635204f5726ed70518eb945ac39bdb22ac22957d309f0fff805eb6c5a02e9c1cb97034563458eb9882c6b51cff38b5c179fa4926a1ffe1a2eb4216c2db8736fe1ba44334bd87fe5ea76d2caf27b7623d3bf8ab9910bd09bd1d03d7a8780868f45a924f050160cc03f53fa0358b950d1f80602a3dc5686e79c7e526e1f52b89947ef2aa74fd2b919a94fcb783e0b8b5b24992e4d197fdcc1941183d47f93b37dcf762e00152d1f1ccbc3e81c93cef4fa758a86552cd2093226bdb341506020ec42a23e4ffd18df28aea802e5380dfe697af1d7ce43697f91287a0c2d2a06f2048f549c90e1c94f284797084421ef2f9716da848085d0eafae3dce80a5088aaa708df5ccc10117f3beb5884603034a1cabca82613185d348288769994667845697425b8113401aa6afdabe00a827e175efd4e603bac8afee81bbd1b7d42756dc6646f332f58599155413b997d01b3e3263044f70187cc0f10f3b40a55cb9836392796bfca817a6bd1a3f2859ee7abac4896fc58cf188e50496c4fa305325ecde34b8629fb5fc689ed34e8b5327018f53bd12dcb2e43bcf3ee4222da54f2d6931d7c4340e98dfb6e21683caceaa5e4c1be5c6ca3d72ee39aba066e2193e24ef28443204a3738308573795231bfc4ab2c9d3ffc9515d6b78a3ef1f14497d7f557c070f66255ffc1aa6fbec75fbc30b87924f361c973b05ef57fde420e9250d2089a10515ccc1ef1e45c4f11d377c2d143797307cb8364998b75bf2a3e5228d10b72368bb9bba993ef043a653ca4e8ff16b33f7b08958e36617e4f6bc2eb41d3e82de369ff9eea17ae04cb7569cdec20c286078ac286b2627d967a4060c6869c7aa1bc12dbcd540a938cd1727ca4fd67d7dbcaa6f49a64ef3980595e4ae81addbb05d19ab283f325104e26d4adea1a174d01bf14a5d9fc726ce133ccb744313f5b25580f0bd06bf5ef25e6ff158a2d35a0c0edb2220eaf349db8c06900669704fbe29b988f4752d7a331427f775316b5d9c06e93a8ca35b26b5fe1b09aecae783517a1ed24ea472415412fc012b5d481f2e6dfe814dd426788a55d54a709723dd0002690a9ea42a123f4862547e9de1ce91eb38ba16dc19cac97227c4ed1d0acb0324037324408934f3ea33a6b760c7e29d0faa43e47b6b234616915745109520e2932309e5b844c281422b6e8302787a5ca160d13aca244da473a9ea0e2d65b49f11b02c206eeb1b610ea5d85a864be6a9e8280330b9eba1182d31097ca2684fa8bd83ff8600f1727877650480b7196949dff049260d8c1f66116a44bcb1615bd62a639c50b98bb6bed5fd2d3350af487fa44985f4ddcd2dfde6eb48700822b6f1c82b41832448912f6ac22be4e6f9b4d6600e5f12ee93050994463d040528792bb62c19ca0f641e8ebc6cb84b0d38ca7d5c314df7cc228dd8ad79c2535bc5b21ad7412f4b6d06a3803a5275a2a2597f1ed5011d1b2bf7adf2943e647a83183d5a5d5426ecb5dad2b61a6867128714a38d9e8dfda92466219c3c74ba78f8101d1b9a5b265e9095904cdf88e878e01e021f4408ee675490633301100231a12977249f5830ac09d7c651cba34211226a164783b59b63d6d34dbfe476916e4625a38a21304f67e1410a76ef0accd7429546597cde1d7944b76ef38a78e754a33e7b0c2509c68c7fb6072cf5516e8dd520a1cc37a6918add3c53e53481357dcad1ae3ff4f8a09ccd4554fb9aea424884b9b1bebb9d069bcb49362ef368cc340f8674ef3a0dd0671eb1e0b2030235c4b685fd307dfd710c0c496b7669b5a1f5b6fe7264637a0b1c1779bb85f92e7281737b766a6462547b823eda23028dd29979aae066c2e874a56e55217353e14bfe628ec0094483161faef6a7bbd81f7544ee47cac647969bc1b6f710f1508ac87443882e7ba2444cec7db249ecf1cb24301f1b45c3c4ba4d9f482b340e2d3a29c1a1839f5e9fae6b72386d81d4814f58c363e9291028084f41332364123619a1f054aaf0ec3f8d14c1dc8a907a4ef891403597be096624e0045a960bc1606720b0c8516345fd4596f9d3708090e1a01dce36643d5378f393ddcac6268a052c05ba4e3e29aa4a80144afecdd0f8bf988c34c73282e9ee077a0f20f8b50a9823ed7cc0ff46171256e410a87f0b088c869d9c12123a646308446041bca7e8b5c3f6625ec3b58005af690594c195ad46bb1aef5904ff12b3d80610fd23f84b29f75351f4bb27ac74077227026fe88ba7efd10445188a0f52d005450a56c09525a680355ed0adc47432a0fdf18324025fa660e63a8f535142be8d414b027d4ec8076f1453e8626d012add11aa632dbc6f04466742e433037d41267b476c4ade82c2cef33f0bfac07e080686e8920665932a660732f26919d0cd02d01cd05de1d8bc96e5259206540537836adbf15bd0b6ff1ce373b173ee7456536618d0793fed66d807ab4af21a23950bfe5cc73ff5914ba52983ac133eb286b09f18230ce470a569480a0ee1805d7ee46dae62698de5311c0491f6452cdcfd6cff89bf223c02914f0b8f1c6a8297b246a24b5c4e314913cc40a3dfc17813974ca038912d37448b9424cae1999fd7204558f9b144b960fbdec683264d38791c9c046ad4a7a7ed68f1e7f8ce9619f67b1b7c8770b07c0a692a2a59bda80a0c06f665a66a2534e5bbf544d7718863a9d726218feb55b196723191389e6ea19ccd5b063189873b9e90cfe1c9a33ad88a16d783c552756b49423f773ed5794b1139d038863c31cebe42a121353a8482a889f612b357cdcfd7e13124020a1834c50546369b6e7aebd98df2eab97e7286479934e5ef3319a926e0d50a7dc1e155c400c59a2a6716476046d8ee34d4a0d81900531ffcf3514927c68ff91956de35a91fdb12d1976a70d429b512fcc997030f715ca6dbd0bb4c50f859149590dc25ddb98a2eeed1a6234a28d8e7dfa9c2d354b9bc9291840a508741066d5f301fcfc3866fb8914bd81fd6a0e24f9f3704d4b8b9a68d3c34967c6d4c3bc75e1d552268bd0164225172cf33448fa5818d9b7d4918b37a71b6544ece326c82345e8d311029381392641c65291f53c5684c501477213fd9a4712e0f38180cd5aa7cc1a94b52ef28ad9cac31deec5b82f3b3d4d6a00953a99d88e10d11228ef1991d8df0b5020a0ee2f4d6d309fdc420c04943204ba595910cdb58c3cdfed85f958e9e2d0388814f6025268cd7a0edb1dd2bc2282aa0e0d3fb7aa035422768eab1f04abeb00b863c2423ca539e1ad783824d894faa744b7729e8eeac34d0f387c807dd951a0c41e785669b42b78d2fdb5f4f2521a326e25654472485dea4b805752cbc59f17e67e2ff3325aa0d7e4db6e2c130d18eea94cc2fe18f9e34045eeb7857c760cc6c4e5061855d281dd348db51f6e0fedef1630a1539088f99ce909a5ce8f823e882aee0c9b31ab7555af5f5d4917f1fa70cadbafce3ae01656c755332e28a7889f1b3a4617f1a1991ce2caaa6a9e802c3f0e97ae8ed401d60cf4f3bec681a51f5974b05c04c6700bd7d1a2e23f513df311b963d09d72b55d1501ae84180be1715700a22f34d026f1eabcb281f64209bfc7254d2c66144c89d842657a94a29727094dc265c07c7fc8a1967ff53260bcf0fb26fbcd816fc8ccf8385d8086d814a70022d1e671adf1b456b5915bbb0342fabad883c9360d77e27ca0a0baae072784a753a06f6d0dd5a6ccdd227e99257b4bbc638c63a4f611d0068d07a2727216a79a90b457bed6422eaca4ea98f77ba9f9f3e215fa8ae0f026f68f34be5f1b5311c2e61f4a17936840963d14b0d4aa3a6ee4cc8b8699ffc66d3a9ec85bf216c7f5018ddee11affeb2dffeedfdac52845f5776e55022318e772fe24900868ebe29895f71670500917d713908175565320bb653a7b0c282af680f1822182580cddf16aba89a49ea62232b9149c3992d9eb6735e0fcde59f365746e11ac2f9bdd63659042e8ce220d20a06e6ca0f0792e06034aadfa62a7202a9bb500b6ea1ab49a30639733edd7e72a1107e101816efad8339b1191b0b0cb43d20585737942850163ddb08a3364ca62de4dcaa4120dc413141bc0b0a759d287101471b7ab52492da97bec8aa083f04f18fa0bd2a0d43d2a4232c72ade8062664e3c402b3b76aec5c0c90d21b6acd1a2401f2157839232d1c5fe3baf7f5cbc94517816f3171766eca5c888bc5fde82210c0697967b0fe226937319675e3d7290741f6727c4cec8dcf11942991870085c93895959fffae11ae108a6796c753277d4987dfd50f027d7073eb4f91cad30d2a0aeb09ec3b30adc4ac715f5474b919287fa3db252fc4b79129147dc90caef2b0da8385cd331454a0d945d00121c97623a8d42107ce7d2fd4986429c61fff462f84fca849672fd8d4ef319426acf5603d9dd6aa0d60a951d045556607d4d656fe9849a249f4fb08c53bce970ca4aeb0f1063a408b3eec7fbc75a55042a5fea39a1060c769c85b0c26cd09ac3fe86b7e14e1b175f83fac7adb60a4c9219c4f0e6e5f9286c3c01a073f1931fab9b94022aecb9fba3746ba8a32f5f54516a33762772e6cffcc72e3e49e2044a7c08be3b26bf7ce9160b50d42898cbe97f3345af3f79e92842d89b1199cc835dba6bc9c48ea95f8edb753cb5064cce03e9bf2103bdaddd61f812a94f6cdcca68111973446db1e6b4fd9f75307c30bc80e5d430191d110b106d17b59d1e0e9003f073d86dd44dd75bbb32b18aa96457d820543f6c6c9b40a222a60ca8cb95f93167d48adc08815e71c914213a326a60ac98eb835468661cd7823398f4786c64628c77bc4015ebfa32a9ecba77bcee775f3635de529ca5198de0214033441f8d519162283fa910b67638dceba515a30d00b752798f43d9f9458f3d50a321d2992071f1860fc6c7abc48a0574941156d6324c664a75f2b503811b0a46e8a5526edc8e2d118e1b987d88fc1b448b2f5de4fa84c3bb40cdee8a9e163633f3d102f55584c4932adbe67c8900fccdd327169e39c5c47961c2e41d3381b1676a478651b8baa6f003bcf2a02343ebaa318849000866a5b566a9e82dc497cd238a8046aaecb29698b74697ad31e54360f65ac36a682c806ad97ca6b105f4a39de94740cfa48ff0673720d9241c5baa334ac12887806ef5067a036b5176631754c993282bb18db6ef7088bb40a2a761dfa10ed2815241708e3a76a6989d7c10f1bd2d8d26aa706af8ea3522778a3b57a02034c407f17733e88eb9107c53c6ae346f3c768cb77b022eb0d59a4a2ba55c2fb028bed46e2bdb82ddf112b37786b63b2eb96a5bfa2653864cfad50be6599c5041813c557b7c09407d35dc6a3853a2ecfc3223762409807a64535534c1b114f44cf9736869472181c1cad82f6ba458eca9323cfcb09827f34da8080e227dc440073b870b25a3d45ff50672434311733d3d295e5444b64f3dc1780fe21a2fd1ab5f1da9ee835499d107b562fe13cf88ec64e39456e365d643a9fda47e75bd0462a6bb5146f84e1d6d449c8ebfc51e4cb05d7ecfbb9f2add61dd24b9753bb5d2196f534595da1443ac25aa4ca69c1a59a733766856e082b84a0e9989809f767ed0d008608090e7a21d8a82c0fcf62c4c2d2cf92ec2554a3af5c826acd941f5f5a856dfdd2715e540d23d5b205e82a0265a14f1c2f34e23a41fcb9fde0fae981e32f5c1308efb8c0862ea7cfa405dabcc1695c43453520d2bbec0055176b00d3b800b0d9525682b270a3a8eb17d8d81726b3d9592c6aa224d0229343b1096c42fe27fbb9dde544d80b2c5dda682bd2bb1f4e757d101b542207f20eb79ff244b9be1f9a3c6e482dd8f51e357d3e2169ba040e7c187d816c1284117585930ea0141ea8ce47c9be11da82cab60fea16f897a8c064f02893fe5dfd8b85a7ba47cdf0b458e608d515109b547936a5627a11216f070ed52d929c24a55ded2fe67152962931ab1bc43b840ac7b20561248c8123f3891615605c8ec30e4063720ab8b511bdc318a404b83eb5ada1219217adb0344f4aefc352d7ebf6b65f815286b051dda8702ba52d04e45549de289ead646772a3d09d65422b91080a7411c87a6808373c3900d72262d4d93215126a0ba2ee5134f6d91b7197e3ad1f5008414a9dedb67bacd4e81659495ccb1417436bb099a07c7b8bce2493469082353d1d64939183bfc04141a08c4dc719fe6043c8f29e6ad9a6814560c0a9da638b9222d4cfebe55fe5df9b65da15819ef5f4b6ac2593e8c6bd1cd9f98982506aa99470e5ddd2779c5bc9e5c2218797db33b8b148016a172c9af7b35afe47e5888ba8140a74243c2b6a038b24c36b3bfb7de3fe66209aed2240dbccbf5d75b67e5ddd1dc427b9b21c78808c4c9209639960075a7b9cd0a2a31b3baa5ee4c0074df164c598fc0cb71ec975a11ae79446d4508efa90e4104a366b84dac388c8a4220099014e6460f949cb0f678439fc86d744d0a0d0f12402637186bc57675241ac4d62cf4c51c32b6d8c868f0b67eb99111099a041f40b823f5049e86656f5a6236c8d2ef1478854cb147de9a06973bf0d945943c5827c1bb5c2021a5dcd35857a1390875b10b52968bf4dc78bc66c90ae3d275f1244f94dfdb1f86b3c8d6552bb2d8deb0a098110e8b99e0c18d9f8f2242784cb2ed8006f8d06ff39b76e4455c4f674b006de7f1e76c5c514100f01bad8ef985034f8b08c049fbf329c49d19a394f8582ecebb520bb57ef908a472f4394f979c1df104a21f387a90d7a6da3d97f703206d35d04fa61d99be772985b6f5e3bf78db10067d28f6da7221f7c22914d7a4c3e938e070448e7e58f340125d6be8218871e04fd941ea3d451caa8fe18f2b0e35317a7959883cd1dad66534304f4e626ff85528c729cea3331de039b0a914449a434d4e3c9590ab44e9a102c6436be52393208f6afb70028e2b63bd302887b4e7feaf70e1f082329f3f3675ef771c7a94fe3bffee58b0ae68cb5a40deaef15fd3ce47ac55a8af489bf6001398e6bbefa6dd8a10b2e6582c216f2becd4c468b2ebf03807cfc0d1f856c485cd931d26598af29405edc3f94afc9ed66edf9639821e291d3470a231b8ce83c7a0360bc807b3e76dcbdc38d50e9e6ec8deeb9100afe26390712b43bbbbf0cf725d4e9bd905e4037e30fbc0b9e670c34973f3d459e6b4644c8d4ef32559113ed692f26630919d0557aa18a923236b8553978631b88dd52528d60e0dfb186d3ed9e298ebc07cef95f60bccf011e0b8017bc4269145ab2332e747f9631fea0e8bc1a517906d96a3ab826b5c00427cce0b95f4e06742e01dcf8b9db8e45d8a86ec5597e3eb7b0e0b54d5b30d38e91f55fec853bd999ff08179410c254cf9a187b7422fe843e84271465c8ffad2d7a295fc96cd69096a68f5c8dae5c6bc4b4a6b8e726969f736f297c424641c74058f553b23eb2962ff312d4cd14909f379d04ad7d9b007331f0df916d0e0955083166e041670fd14ffaa2c5d81e7ce84347078a9a47613c408dc1caac6dfbc3eef4fcfb149c0af58d5cccec90c0636c9764993c0b0c73dc16fbaa79b670ab19ad283dba1d75afb66333e7d417be20ed33a4748301f1cf8b83e09235a1426c4243c458682e3303ecf6939ea2e36048308512f107d27f2a0460921c953f8bc21f8e3a84cd36a2e03aaa7cec1bb14350325958e2a301ab7a293ce4b23714b716ac16ba603389a79573ab2d62554353dd5989e154d33b5197f2bfcd032d73733651eabf78dcc50d79bdaf5e6898596204b1e92d72b99faa6c53053c418d882a64c5f432e39cebdaf644a614e9d8c904c6c88f942220a8de523cc12509853b0345503a7d331a60c026bc96a7ab7567cc2934661166d6aa1c2bc892a28ba56a70500556818b87506a1b64a103b73ffab28d89ac46308468612dd421762b38954586056a25e291ed9c596c942bb81a1ecda61e56570c8305696817c545d135314c691be30ff89af07cb8f2531870b20bb142441538aee11d83f4f0321c00d2964ab9eabc65bfbf93a40435b80175eaa6c2e93a65bd07a726b3a6862eb8a82538c50d71ba24a1a3db4902b11db51978ef8599a369e970ce9ec89c946dbde88766fc414ee8724330c1b88ffc82eeb1a5ae2bc2ab5269d6332ead57d647c49ee17777a7a24078d449e23ba4012ba9d049958d71e5ce14eefca544eb2525d1c5e81b53851783d898fb968e7b78b08f7e41dd7ecd74c547e7e80ee9e9c2d0b6b971cea07dac18814437e4c11f93b6f8a5d05224a19f8087c49bcd444d768f9e87bc27678dd9f243ad1f5cf83ff864c4808762a06f98e6e3eacc3d53baa08ee30c88069347ec7242f68bd41a696fc418062bca407bd61fafe4819626f750b15a1d89155b4cc77e3460f82e55e366ea656dc275a3469285ef3074a611c7b170aa34a5a64b6af7608f79a37fcc4f7a058fff8bfb43d49c0cae476840cbf994cfe9715763a431a8606f000d4f792c0e05e763838ae976ff94420de2a10ad390712610114f3cf25e5c4fc88e3eef27c7716e5b145af1de384916b20129c0cf9640f35e082ee89f56921669debd00fb960e027f9cab1616521798ac13ede777476c4a4e1073d5f02195efbab20f7e8aaafd6dc22b91ad232a1097606eac9d97bf0327be0969bf51e52585a64bc2fd815dda6b1cadffd2fed086353a3a9a59b0f4cbc9e0c53f9e4c10bc0b1394b3f1a8448dc1558931d84fa38994bddebc6a034490a9179cb8c75bf2033de45330457bf64a8f1c209161502f3050d7ef411ed83351b37e791dc08a56223a00dbce2a814b63ee75fb196d2a8848df8115e626c4916def2e37be0f1fe3bb599bbed904102e1027236271402e53615f4e99f0cdb7c2a8de989fd932d1f7d64dcb457d3c9e75073b807d9889a5ee7c8f2e89f03e52f5a4638f69c3aa6a18b5cb442fe52bebcccc3c96d879a4f783fff7695d321cb4f43a3276b79e084fa880cfd81ff818a03c7ce8256fbd5ca8df4c80e401a4afeb0a66173b9a49925107b83c0925312cd99d1c5360116d5ad48556a0aab39abbb6d90eede4f4e3004e88cd0801ab2cba68164e01e667e7d024a2045e8f33c3b73e79d4612d667f5cf00f6e5cef2b41e68283ae3dc8aedbbab56def447b0c02d8c7da2f0dc3246ded85327f4f57746ecfacb9978d088b15a47c836479c86c9813fd8a6bd74bf9997d5efcda5c32b3a87a2664b9c6d6fa3300e66f2ae472f54440c1b265345ea806c91074f6da72e52016cce6539f4ee8ada988a1152a8c9806ce95dad119aa2331d185a0d16f7ba94998593c82391333d94238568a1be54d5d5babef9fecae8688acc16e83758c0403dd10641af6427fc09a42e38829e1ee9e264da2e27ea949c7dba13f77c93c070914e53a11ddd759935ae402cc6c62ef3241fe127d35d0ef12a7bcb37242c4a1a31937de8d7c8f895527a2d61e083fca2b9e5ca506960599a6a9a133e250ca884e6debcb4bac0f32c179a3ef27a3bd95da1100eada73b42a631991b1586d560b69dd05dc62ea26abcf3cb367ac01660bfdaeee5b14c490bfd21b87553056b44307de136ce1f445999125107f6ce8aa87e581f9a78009ad697e996d0d0c1f864af9a37d73041aec926729e0c02227448f4593d9b663843f18208d1729dc44096ba2c8af4081b16400988cb52aadf6e683d191bd301c0ba0bb73fa2faf9349bd29f0349eb93203c787acc56328894bc4efe44ea1a3a0ccbb0508d1f83674cad61a49af8e3fabecb12d416b65f0c9075ee93bca82db388dcd3379bb6929b1ae9c0d4348ba49c52c653c955f4f116e2079f12156df0f6939221f008b619913141f45b56b579eedf516f34afa5301b0de9a825c8c1be6b44bf32c580f302d74f908b459431d70806babacba233b45ac90324bae34343de4e8a8a6cd0c2e5bc6ec13c8a8c19a32b171f088e25b20479119010d3c2db7d122418aefc76e8cb0f3af012cc0cff2d841c5385b6321a2b0047e93eb37dff071163e0a248e23c511ce1b6618822571329bb54e48a6c9ca6db66e0c21cea710a24474e26c78b90da2ec2ab9cf6d8b3e171c1b764f007dede138ddfe41f0a1ab94f088de610a38110ce86ecfe1408f791f8eb9cc25081a5ff494e418ad81393dea97123e9d01e1c62922d2e44a297bd9d5ecbe575709cbfad622354b9dfe653ac6037622f6c35f123f0a40f3b9eb03082ec9e3a317fdffab46504ff46dc81e13e242ae2f194c673c2af7b44f7e4a7787973740530468d536833af265bf9873da65e5b9770b8be86d7e4e13fcb433d783e3cbbc8ba28ba50ef7e5639061f1eb76bf055d0951a157f970de123a17b72d389b33b4d4608c67fc0b217b32e526a7bccf1fd11d9605859a9dff431c85d3ffac3afa8c786a0fa0fd89e64cbb6c448d61988b82989392d63e60462d74e74b5d5d63f122bf877a7d0ef6a37e4e79e8058fc87cd423a2bff5ae33434026da653fb10c527818ad3ce7c2b8a855c78dd5b71105fbe8fd647b5b93a602ad0d4ac0d8fecd04fd8a04eb75aac75cc3e4ffe7603f9060a85386b8cf2a880976ebef84f7e18bc09449c7717e587f55101d78f5e4195ed1308579ceea42db60cdeb8156fd61589ce589e69d361f34f551b54d2c4710fa588ff76b71c0c70b963a9880790b3a1b97076208312373682991b5b1f956509d5ee8a74093b31329bbc2e854888bebc8a42b5b3f5def1251b7f8102db6377c350427aa1b83151592b2bc45cea8bc3a434f1996317ca6dee86c11c0282f1eb1156d9e972b5856487d00532d777869d099001c5ef8a171ff2ac447599d18145578ea0103e20a187169d244048df967ee13c3b94dc5e6ef7175262fb00ac33c68afcfb45d388eddb4fe167c85351f3dda85ec7ffe8a41ff707cbe50fda1f544d2ce0164b08549fdf1f21bbde199579086cfb4295498664e5ec9ace2d33b46ca977d597d0f168e2c25661a58bb889c9d7cd7a2485f87014f2f45c00ea560225d2f6234ce58782845145c10741eea1adb25da0574ad1a602aec7be5813b50a14bd35b832fe47f621820bb8cf3bc85200e1c36a39ec6998a2a4b2b00f5ec0131d6be454a52550ac2546852d3f624b23dcebfab92148190c6f825ac45aa2ce7b8149a59a7e6ef4733355a5c930bd41f26437380f3d27e377885d747b460902604e2b13380231bc8bb1990a3459c98fcec34943cd49ac44fb61e61bc2465259d5406c76a4beca62c12a855f33b04573c4f61827a82c911360cb91b55028aa11027ea2451bf153f35adc2cdec017587291a6feeeacc5d1215f3f691f4e959766431a5de891924a1641ad8fadb6d41dde1178bc07997004052a5d8234d4f910ceac7afc38249eca4676400553f9932b737666363e1eb289d5790d563432ab76298b800edfb6816c1fbaeaf700027aae0f81ca665852d1708a614f4495428ad7f3a54bf98ae45fa30ade673de5fd845817a95fab4878f2f962886b22094a5a6911cc1397cd8134d548c2897ff12bd624317376bad8fd7eb5585d45af02276b0050bb236a103eb96815a5a9acc08dce80150d93e17b1be72c65dc6a026428162390d712c4ec25d86a43cf228dcc808b21c2744b7a8653e143c86cad7008f84cbe2c938a59f4a22b3c10efaa1813d99e54598629a8cc65fc36cce693ab4e1cf232ec6926e4e5a57edc71c46c1cb83aa0da3c51c485f2bdb36e18eb51954eaae3b98cd336d03c02fabf080eae6530a6c8fdf4b48d3eced987a2992edd06edc5588dfc9276de7c37854b9228b5230ddc43b289d5d63f1a69712edf8747b417e0270471cb9f0a5a1952ed637f857b1ad39ec525a5ec25a620845929c7cc370413b5945238c3665e58358812b3d0f26d436da2083d3b8d55b687c131220ce43bd3434b089f78161cd2d684921f5e4f78429517e2a5033442d158c7711ad75513f3463588ee382dd4cb1f700194eb9315d356161753960a22547d22ed78b213dae4e6267c256b53082464ac721bc9c8035b03c12531c810c9dcb825eb0aef0136d16abea49c31329612aba5f4bf0298453386fcbc77b7778f50792cc1a91a886b200c9bafe829652015e40267f6e65ce6ea87230c9aaa6e6c275d6335c04c473647b67871139a16db997b9e79a8032061b2bcfc6323d0fd18696af83a65e7f2d3819111cef071764e60ad946c5d8c7c6d6ffd605e793db8005e7f53767b37a22301fce9c41a5f47858e4eb888174f1a8540a64a05560f5d057d9c87174e607f623bee451c334ba88826f50fa9448d626b7208e1411df5ef0b48d2c6c2900d2f63209b847681a173a51e6920c5abfab9957a20d1ce7ed61ef0f7abc3d5c730d22443e32ac30a4c3a6a87b6c5d26f74feee8048f9d77bde7bf77bde7d07fc0ee24bac5d366351da57d9fedf25eafd1e111c13431ec677293c66b5549b9834876adf441d484febc85eca5d04bc91f20dcc9891880c5a4e14ffd2225251ac02fedfeaa45efd1248a441885da2c4ac69eb73bc104b44895ba017e68c41551e05cc9aae30d71417b32d0189286396f4ba3962a44a89125952ade7c78d22026babd481ca774a43321ece3829b77568e193a00ad2c507f29a952f23d035718e526441bf9a0375245d3e54828cc5cd2bdb8777f4a3516f11f20dd1c8168842f7ebdf87b628c2b17102503f21724f6e7f5e99623a29985d1c6f16063e1f72c215b2c5e1dfe90ed5709fe72179d3ac836809e773b81d96a025300e063796443aa1988c1574187d83add737261d4976bdb73ba470b46b224524074aa7b220f6fef160e1c999881f4a2938ea86d8d0ff7e3e5087b70ee68a34298e6029059e9c0d28d2962da4172210a94c58f88ebd1ad9dde4ab3daf58356437dd8b7924b60310f314bef811c31a6616d32217531e9b7c8aeace26a3461469cbffe44c47a950ca1e604f92593b87c83882da5bcf389e92f21481f572d4242d019e41df2d161f93d4661a2686bb61d9cbef7c26bb2a3b57952bfea704b2ca0bd45f7ef15fc6786c4b23863e9f5b1dda5d9462818d422a9e33dd885c0006a7bdd1e37202c0949bda9c973daadbc1b8ca69dc09cf523b1f761c797c72a098525bb620d7f4fc4a82d5c12e8c9e5f56cbd2513bc37071fdf11d0da38c7222236a3fa2d5b0b5719e5545b1a30e348e985ea7d7a41fafe69704f1654d8da4872e3e55eba09359bb1bed43af174d5339ce8a5c389dfe6a7b92d27820ac616d808957b4be8da06f3a8101ae41a5bec5290c251d0f60307d030e35474b16b6976951b8717989a1ef712d9dba81770bf53db236236838b5747586a9741adde54c46c99ff4c6434fefc7ac5ec8b07c9b25496806f5bc4c8ad3e46c34aa4ccaf476b032d0c0cced98c87c66bb8eab11e47cc609faa2d02558cfb7e7b18b016e865e11afd3fd1a9b0d69ddfe7963b35b412add3c2e38d8fa1c3598a581ce2ecbe26754513a7eae9cc43dcfaaa109d6c3f588114e623d91083f9265e9c1a9516a65deb6910d6b1c8bb0bb2951ce3ee685f07e892228674cc3fd7acaf504079f98f656d4bd8846eca7b9ed8d33828889c0d1313886089f7865ce0aa069a736b7b4dac7904a83902245259169e8eb8e3a75f42e126c732fc1b19f1c0f07d16e160858b0701421339d73902b12ad44a9d9335ce348c84a5b3faa1fb1eac478100c102394a8f6f3cc54267f1b63d98de44dfcb221264bcccdbeecba3b4ea74c4269d0fdaa14f95a21443ea9c2e7800ed9ada093f4297847dd6fdaa8f3bf4e33d00324ba5768ff107ee03614aa1fa9b16c4015f337dc4e6d600edc75a39c1b5db6439bcd031bf2768a6ff2c0f1d14ef2702ec022ec12f11f9e75628a28be9d46fbe43cd0f0b82fd347e12ab6fbc50f6608069bcd3276ee1452fabe0eb4b4b4467da5c0e266526f4747327edd125f946ecc286da4602a84386e23dc921ed00303e6ffff7144c0505b02222f9507dd9598dc411e8f93dacf84c70b03b561be0ac403df84a8abfdd662cbaa210b5059125f6f2148c2e8b48159e0fff1f15fa504747f1c5a8d303208ac6590180da845d1816521ad7d8a2a7143e08b5993703741157b0e9f3a46bdf267b3b80cec8a950d064ba0867f1248ce7dac2ab75735707b90b507529b2d802f60facd837964f3e8a5de57de616e89af573b6605a73dd614be517e4f616c014a2540cdd216c2a2dea22dea7d34e9a6eeefaf52406268acb4813829bbb9dbda2a16b5d3a5c136b6debe7e9932503fa7c66be20187662cfdb692db5d3e789869dcb9f9cf34b76fe3f9b2a8a1ad95bf7ea494eb741a7b932d61d1c4c8bd7956df516f41c0572256cc824c30e29e63160e431f980abd524cc5e885a8ad3b1ed0f411ee60150d04a7ce256a9b17fd1ea7adc43e77d560e7b96767131d48fa74ad719a67c386765384b67bb8bdfcd876abaeaac10d76ff367a0aa0d3c493ac247fdb0b7d0d228537b191c0fce9b333c31184bcc084f9ff68bd3c6a68fe54fcd85c5c220b15afb694907ee6a258e236a4c95b4459e65ca3b64826c866eb7291915d9b7d9a4a617850ec730130fe261f5758b7c837ba2c0e1801d0cd7ae940c89b61a124e7ea5b090c63b308da18edf75612d98d8b1846735997c3c75efe408405e6dda4604084f70d272190dce490c886b00f93ec2361e89c536bf6d2f4ba9f29dbbf59b328cfab7a03b5728ac4007b571aa5d087b9d6be807196776089b5e79c901718fd7623b1893da64d487f83d85085d84906b94837ada33ddee3f6f6b3eeeb62de048f49111d870ccf70f2e87f278f2a89b68037da5b2b519f41740badb208ae35c7c4e98b0080c613d7b85a1a1437bb4694ddcaab09cae0e9c51811f233d2867d8527e804171e58904c1b452b355e19258ff6a92e27143fbe218d60680e9bf212db3412b42835e009b05eccd11e1d22ff496bdbb8734adeb84f17c04765447426708a3243cc7a45e30c64de82c460b64645e12327f8948e6b3144146badba15c6fbdd0de71583e69040ad694a190c4d92133027499c4b13583a18df7002db99abe43c8209881b0c78dff0ee4e922228aa254566c0e8e0267011bde8b4d296b0726d4b3e4cccf584738a32e57773adc63dd164c48948f1c6dd945463e9f9e41e21d636c54d902c37bccd177e9af7bd7376c9d950766167deb23415b7ba0d020534cfb16aab6451bfb75de00622702e64fc7ab4b5e4533e894850317da135b63f9c2474bbe4c810da9041cf32519461a13a23011c808cd439eecf8805b7973948234538620734171c8628407436cea14b8e803b7fb10c26e197591447979c25169042cb22712c1915411ab9a39d8502348861e4570760d188959096d89a11916bbae41460b8ec43314e6ae786bb781fc409d248fb761b5667e273aa14a72d896065e9c3869193ec3d656900650deca408e53c8754d00661762e8807f21123e81438f56aee1b02796a0d4cce753503080e53947b793b9985426c6e4380c49957fbac13b122e2a255cf52c0db8971b804cdfcfe7a436c7740ce51e6880f6679b030392498d038ecc2d86e997fe3edd6b5a3e3d80e5e320ec3df508e687ba5c74d2a42174cb9cd4282f86865bd30535ed7c1d9b723a1e4af251dedeb92e39a22df8a85a07e3b2ff4f305c7fe92e0da7c8e62e7bda0ed0262a15868c1c0e0fdabe31db6379818515a62ede8c2880a8d8d519558a49bb28b71836975557c8cef10b4efac4ed0d8e6b4a3dc433a6c73df4068a6073e57e624664c4993b708531a8a42820a7f77a5cc65783f9009121e2c2f7e319a240774920f0cb4f8b9d05ef3b104c3799808763615cf5932c17010e78bf6f6d938fccdbf2409f2a38010b0a7b4d74c495764c34e8a403b37df9e0dd5f198f92ba82cd496e351c1a23f66874a06ccfa0181e53351e446d76978579c70b9e5b5332e6ccddb07495b30b5e3ba3de2f055b714e5e1a931d2d00b7579946bddbaccd02b4b7475cd3df1cfc8ae955ec9ca0335d109371ecfb9fb72d01b708f3c4c205ab6d94480bf619c6d709c88b440a60ee8753878a6604ce2013fcf405d74b67633d454d9a380eb611f47b56bd7ef898b0b28404b82a5e11d2e2c056734e03a9ae39d27db0de2bb9bed6b3386a1ee526cdd3053aa31a6946591ef64f38014feb3a55f4144ec84e602472707d03c04685869e0ea380633015e873c120cf49743d278eaa21032e9f0ca1120cdd732143c491f01fcde60e196af3e1e06a08eed160cf0b51cf3b90d8efc46e423546bd900edc4411b6ee99f1bb2bb4e74fbb249196eb962df7079c6d48f94e085f179382557ad53afaee7797d14408b44b5f1aa173b39b695722bfaa1ac0f819b460e60f49fd54088e5b583a270d34a9ff078058569c532ada47ed84a522cada4b9609dca1df50a0c65450517919027ead70b0b0d934d91cef0f0429a3643fc40d0182aa74e395da5c6857af3e62aa722f037c6bf91b46b18b740f6417a5896484787d52e3cc454d5d72936c01948360620e9569816b264bee0930b57effc1170e788d1900b3426880680f4a7db7ad742a84d4517cafddf0479d439d820b38118d999cec1a2302bb5749191a5c6afd839b819a385650652da33cbf1323f411a2299cc42eafac05883912fccc29331232d8ef6770bc29e986238b6be73da6098bb51c671e0bd5c056030c01cb2fbfe0b2f550593ee6e507811e3c6a166e3cc2d536967d365e4df6d549d5e6470a3307bb13a0a856517d78adbff92da3a49622b94ae569ead59f77e62aba269e34d7832633c66421a1abb6d50b3564a23fb3fb4e2835ec3054ddd85d4019417ab35d8b04089182bf8402235434bc49ecb0b472b5dbb20e6a5d129c33c4bc586084314bd40e9b318b48ee3fb968a987a14856398615a9fce1682942f3de55f3f6c068e7a894a12e6c287f881442639f962f1fc9d00f19727dd5428cc4a9d8f37b0a6a542ca5ff75866e6add0468971cb453dd1060fb9f6a0120af68d5951c2e390979601b95c8940d4ee968b6f544e2820abb181d406bfa1e7cefdc1fe8a7565b24420e84d78de2b7366d0136c9f8fa544fa6d1ddf965283b8b137357306112e698ef5adbb26104e6d3e02483e7c680fa7a3f2ec015f4560fd38141e5c0c182bfbd6dcda1f0ed3f9db59d3bcadbef02b16a5a623a7d0efa7560b3a9a2a42530d5f8ee199f6a2d4727f742d8e49a81f0a6f49e28e159ca7ddf4298315e60293a4589071d3b823e6b294c3a1e653798e164c5957a10c9d9d0806a7857cafa357a4ea5cf43bf2586bacb82c63915893d3b721737371eb205342272c40279a71f94b86c21423c1bab6b3ad2c356e5ab48cb566b57c663b32ff38a3712e842ad615923fec59cacbe4c10a294334b36c2172c84bd6b3f239bee1278c9e9ba1cb49295098ee984434e6c18a7b48456bd091ea9737541be401ed39c9475fc0fe140df03c58c134c9bf2628318978b0d2d8b8c7877ff42751c714797cedfa7d99b629a280cd6eee15f6aa962151af646a4b714016152894c8d40f26f158f4f0c498a1601d5ec5bb2383ef95bb014665135b8bcd7ed782c8c9a405155c5ea1b660549ac97cd085d3358f3592d225cb9571f8d33d170856d547b39d85012fc9558098cf7dfc7eed09b01a17d9f849c7e3a735953120b8d765559175d22287b6c2b1ca362c81bc4e328045b6d94ec420590191f85cd68fcaa776ea2274329613d7ef9cb2d53b3bd272eae80507d8eb273798f5c4857f21ee125b47ae0ad9b108976379dc9495fb5b697462cefae6d8fa54e1ac1f5334b1df23c2b98d4dae515346dc94c27952e646dd95946ce599101288066311474244d1d4bcee85cf45adc3d78497507b0a2d3ddd92d176ed48f9e8d520068793f8ac76bddd44f71922d368490c50a07bdbd2f7ad102dee49d016d3a0a232fa1c705429c55c642a54adab7405c080b609bf7a83c80aabccccde1e7712170ac1e11f264414fd53dfd20bd2b108cf9b019f0a6a6d1fc453da021cb6b6f7808a4b8a8a10cdfc3e04b62c84c103823dae65e2d1d4c687eeef107f049cfaa5104a0c441ef779ff60dc6b18b00fb3d39dc2f705ab1405bd3e7023b5f9eb5d0a9c0f9e1891bc4b9b29f1465c037ceba949c57dc6c9c386727d7c2983e8dda57c5e4528f1860ad0b87cbaa17f43d0c8f5a567f688e220eb50c15b51232873664024ce85ba4ff3d1037973a03be4c4e79a2453922d97637bb6e95667b214452a1948d5beb38d18a5ef09c0bbe99405974028c955baa91217767d7aa858e12d091d59bf0c24cf9506381a49ec90eeb7b5494e8cdaf0f368b1271144447bee5243b9af58f82dd8ed369e55dbb879c187170dfdb8866f346e51560f025ef02060dfe22562948b55f7a5906e6fd69eab56c7451de580701b5f458348fcf85c0dc993daa092415b965e2497a1793dc3275a954d89dc8edc8ae4cead8d5f88a88cc33bee3f6ee7f24b07c71fdabe8606af678cb5bdb9b06477cbfdf7cd2a82ef02b26bc80de25a213cc28d66cf298d5e66fb5d0969c20419742956f040d02f634fbf2bb4c7e2c34c91e3a0b7bfe4c27948d0c4249a943c2ec9010bb25b923c737d49d1de553be7e36f4bd7b6a55db3121ccbee143e5c76cd2e7274421be68dacdeec88e8f609cbfb18c6c0e40622f299403e03400c5d683ef5c7525324b4b51f08c37b7eea1957f5de3dd1ad1f2acb21be82b6e613d2d347dd184cbc710d6df5e540cc8edfd75daa59a5488fc449928f7c1db4bf1dd3d94a2a67846c06bec6242287226134a81391926a100eaeaf23450f244849215f243a269b504d39c9f0a83e1c43730357b2a1e41b69e59f21590606f74382e62c3d79cabfef678caea223b4085b7c1b29e22c08d962f68b1ec062354c39737208bd5b7fc089e2b38f050a6d6420710214871f794f7ef709629da7f26689c09daf128bd1330d69f4fe4f637d90a4ada6366dd13b97cf7693a02556e7637015f1596e90b551c07ba7677526dbfb66ccb8d30caa1d37f072dd9968d20010ee167cd352a46e0500e9a5849676116e7f2c3c0cfba71e97007645f71857b1058bd70ccbcf3c85a5173f0f90d6f9bcd4582007f1fbebf431ca8c675bb4cb91384eea6bca1b9c9e34301ba0ee374b915304d8ebc77149ab620468bb9d4ba8516a1a59b947f5d228902cb0424a40da0c9f0f6e473f241ee6e0d7320aa7cdd578a04490d06937ed22a84bde4d9d88940fc60c940a8463ed39f63684db5787f469ea9942c366065e47ddcb858cadb433d4875f29aa03fc2702da9ad9585e60caa4a40e8a6203a45dc72979d6367bcfcffb068def823779d7a1d5a8fa7645c3341c46e89f5d6629bca097e02f12b005bf7dec2dcc47611c7fe3300f6bbeeadbc231ec12dc071f73d30a2828e2a3ecf90db1cbd8dade52ca94e4de7b07c205530577054e943e4c63ac797a3c4a3c3e9e20ae00eade6bc1ad56abc1287dd319038302e62f78abeaf619de030cb144f0ddb83722b5b75aed56a20743a2aa8407b11da62fa5253526867a0cadac7c44f168c264adaf879bf052dbaaba497491f8a2d137aa6f5b96476caab75aede613648545316c0290c8ca97da56d59df39df896705aefbdf73c9fb4a0424feec93d3800be17585f35e5de04c4941b2601b51b0378f44a9818bb6a00b371e4182c3f9a7a080fcb192828c5f75e34938fb333c16e2f5691cbb7020e29ebdd2ff7de2b73da42fa9befbd5831ab6aad02197f63ff1fe849d3b5bd6b2bb2ab7745eedece8c63bd97d6b3b7f302fdbe32e79c8182a78d5c7da327ae40a350db796bb057d9ce321e1632a8988a6364b123a388920f327783a12033866430f59a8c32b744efb699d75bef7ea995a88ae26923673236752b7cd6bb5f76d37aef257105c5ea46944fe64a69d215c24bc9459250e6568881316be5aef1826e310a303c3930d3850956355ae29c52e19f1e3a63e5f2707937c16b1585efb4912bd65f4ac981b9c5240ede537f2945839d6975f7facb262d7ebabbdb9c367c3b7700c0d4ea89e109e289a25567acf7eefd6fa2cbc497d626be5bcf9cbe86d6bb6b56a68d94497165dac855037c818c099bd346ae56aa57a2bea6afd5572deb8d58bd6a89071d5353c62f104b2b641c048928428e3fa282622b29b4d1d900b9304d0b732242cf60fac8129795fc1df9c1559644b660c01911a23b1122fb5b347ee72ac7ddf192300c52e4068e1c494511248fac184cc20e4b60e4769278794b222cfcad5b6b7f44460c953f53e72b3062094e0724761cab3ef014ec2951e38bd293c064833088b188089783b47584c9496744f04f11c21c3536c70647208a07c60f26462100e576488ae02082e4a4c75a6badb5d85a6badd6339d1240ee631d5913c7b1945608b03523888aa5163b608440cb458f264c528884a7bfdf3205182b288af507c23142231ce7799e3a5a1ef8df10ab3581ea54d91f189a683ef0b638d490b13034f4f48458e1240a8867e6558cd55a6bd5752661257f58362f8c3b9dcbeac57e44425061d223c9280a94260e5732de634224f5fb30a57384cca281ea22bb98b185bb7b56149ab391a13ba97f583d6eec9795234daf6c257f5852b124fb2f7906c9e493aa93750c9db3b203b45a6b6d01763aa03a44429d291f0288a519564aa4764d38200308e24372a2d483e3010474a28a2851f53889323f64709301c54f930c3b4b3a289d6a560c7b4d423a1900814207488f08b003c9046a968d3c60e48d118496272b15c8e102d762c6083d4460c900421792118f0f74587bd8e05275437477187c34e574f0c5b851410f9f974e948f9f0f8e2c0975807211ae58daa3d3df1121add39f112a278415b6923fa38c34aac35a063948d19201e4c747d68484838987202ec7c6024c0e6bc961e70738c5338542a6247144b4f9ed009d8e64caad3ed534d8a65996e5564b9cdb6ba3866463001e906469a7a8899026562692ec78a1f3254207e46a0aa678e48cedfbffbf308968b5f6626bdd6d562bf4caadd6270104411ebba61ed2548c9e22276a6c15f1b2f12163addbbb001248018975ac9de43065c087a9a3f5dcb8c4e195044e95809200d3ac9192b2525502c000d78a9a165b1a381891f117345e4a48423c719d5d96143a05b9f55093dafcc8ac942f5b2c7564f7b9b15dc927a95ec6a159c935611a636c690af0f0e44f0bcc9a05410fc67fd96aaa9e17a41568554a46d89a4aa3d16a410f393ccc5835a065d35594e50d0e90d80fab9fbea1470b51c0e09542438d0e863c153ca91141538477745385e4e79989c699852d3e3855762186359657f249ea88a5bd7c0d4e4a4a48f04089a2528ed10f125c325932322c0141da8043e6dd2681b0407cd67325ab3c30ae1a5893080d05b070f4bca64e7465b800122c2ce30e4a040f05e0aa86c43822c161c3e4802694c4840a8608277c312888394f6c92d79a6686167987114c0716256418d921e545140b28112ad21585c593103c21b1849b4046ec05017b67f45e20265677a621d3461e3101a9acee05a6ab0c259cb28e757eecee04c446423903024d1b49657575018875e6137f2cc77e78ad907a1b57124aa9cb784e30044353d34c2141a6d5f6cee952d4f1631fc2c141a78d446333d9fdfdddffde7befbdf7e6fb3f9441df21622695ccde6e4c621f2ecab47a81a878f1ffbdf70eb5a5e9dadeb5dd2f00c9659a9d3da23f6161d162d2746def9a0b9c8dd1ad94b3fde278da48a85e117d8fc94236f7de0b2f86a7f9a49573efbdf7bd56c10d8c71cb14ad5b045994a8ac28aefc98c958e4b450c7807c2a3022800171c978a9c51756e4a04ad400c0a835010fc74382cf8671386117a5724145f0a8456937bbc0822b077621c806ca8746ab5aab17520c7bb6265c10c2e912aae76ae502af67aa1456d2a9d8441dd2373f5fc78fc3f0ee8255906923d7172e2171c228ecd8be3128ab1e6ae34415ecd662fb6315f649b33058859574fa40446afe08bee32d26cdf9fa756a61617c15d114dd14e514f914f18a866272ce99e7d9dde70462824ca465da48a22e4cf4a55fd0a82f6e76aeb130c1218bc8d1520445901421b038b0d891f10e4cba74281141878dcfe79f5356b08b2b17582eb4541c8cefcdfe182b5973ce39e75c4b62f224a02751799295275d79125692d62da9cbb4912bbe522750e9e89effe2f31ca2e240540f64f540578fef3dff6615aca20366c9e34a02876e56df391f9788dc6ffefc39fffb5a4e9567957957998795795a99d795795f375f91cc54322a71dd2cedd6f6e77bafbe6bf6f94cac39e79c8fb44c1bd9e5168ebe686e722cc4f0b9c59458e2398a801c3e8c5545787a0491f1c0e767042122a207e6d0695765893966beb10088b4fe184cbd689a91fcffff82d2095d27ca430b6bc625a18a4943cb93cb1566719af1f815a0aee5accada1764d151af32dbaad6aa0ddc4a6ce6ffe68df32dbfac514346abed75d78caf6333efeeee3727e1d876c2a9748891615245198c7418137a54ab503b580ddecdc2a0d4070773e8e95e43e7de275c06d6e080e5b51a7514c5e4e3b5fa7ed9ee6e02c101e2e801cd101e193716510089800374f5f7efc3d4567b6f08eec47585354456ab368e7f96e512f404fa4e1b697f450c67f96516d6aa30f83031b7db3ea7409837a090cbfabbfb35cd2158ca265b90cae9f712d844d4166c284d8580019748b8fffdfbeeee4e811138834c97d3a65d4c8486167409e625cb3288691218a67144a18e542e8d5e2ea6df34c452137c86ee32c5ac8ca06fc1721e7d448ba9d59ec5c9e06281c9a64b6661461085d6eaf4e388650376d2479a98829574bac219a14b61e5455b5a7c357869050d336bb8d4ade4138d56a95b3185d9e58161bd1b68d67bef7d79c15a5557d2f7ab9aa5a60d0683fe9e71bea1d66a37a8118aebb5d93afed98abbbba6f9326d24cd4d8e0f6f28030d471926df9b92486a7937034c7ca33430c5deb31308295a480bd20a68851c9581b8de6df6ac3708a2834b03867438f2d068501abe4c618958f1fbcaa0249675f38bd134c6faff3062ea28855db680c221c2a240e0a838262f760f3eff41e75fa1b8feffffbfd6b2edb20afb9f8cbaf5ff7fe3f727f3fe8d3dd0fcffdb9c36d2ffcf9bb51f99e0ffbf9641073babcf47f370fdf7fdaede184f94c207e3fa511fa4b8d46835f3bf8ceaa0a4d27eef0fc45d7300cf70d35490688cd1ebb9192b2a88450e00c860836e89041970c86dd3f7f3fa0e411974e85920b332831aaed346ae5897c1960c6c7ca78dc46b0a1b272739988fe0deaac71ecd698ea2a62f666dabaaba597a7e2368d60b8bcddcfc308dd70c64cd7bcde89a73ce398be899365209fb74501d6e62ad68daae08634c1a9bb99f55ec2f5cc92f5b55778fb187abe7e88343f7d2cab5c9f8e2c701284dd7f6aee976a68dd405615d4feb94d47467657c7b1c9990b713091a34576531fe1ec20ab0cf0870f52c7482034c078df486d595cb408454d342c649232682512a8c49ba2b19bff7de33662def94f4ef5b7f77ff6fb1dddd3d68dac82126eaa26adc0c40c255c3b77cb1b53d6e28781e2cd346f268619e2ecdf3a5de8df3c5ee4e22709aaeed5dfb31fe70fd1c77b89d4d0d85cd8a130a1e167c71beff3c4058b134c7d7807b0d36af21f71a74af617773f4e28bafd962757c6245ccaffb678c854d4ef7ff73864c1b79c404a4b2ba5a73b0b28eacffbf7f9f051002f8c6ee14503035c19c69956b259d74984e2e8c595898544aeb9e5b49df166ff532570197cc87fb1964f44bee30b53b25a5618c491f0f256bae58fd9e8072f18d6b7b9b2a76392478034a60ac63b4d62a5655ad6aade297004d3dc804dd69232388cd389af1d150c226b12fa7660f62179ff1b4911804863feaa933123b1df30f2741ff97e51109092d5aadd29d36d205a7471b58a88ea898663bcd6e7494ad68158795e3b8721c582db1198f9a9d39446823222eda6a4c62281f54106a0845842a4219a1b858defd8511a84cd14cddace07bddcca611d24c8649dce4336d6453106b0d046484c71881d804480ba02941be36d0d0e4c18f07c2430824f71afd4c88cdc899e1a372f13fce6ed4c10586e4e3483c471a92a926cf4888adf2c0eb399064e2890811d68d0c0d9cc8ea021a414706d0e74fe8a0e9639792aa5aab347ca78d8ce1143ba3afba92a1421365c5ce91192e4210da7102466faa460f143f374b556363b17f99cb22ae18fa3bbefbe3eb63429ee5a52bd34662612ddda52e7d6d9ada2ae3e616f3bb484a63123f0d9f884f4514c59415205a021a5068b0521212fc4171f143211443c808e23c8b9258ae5699ba98be68d0e8acf25afe86ceb4913776186b1ae86601bbd7cf7c16e5680f5ee52056504b5f3549cbed6fe786ad7b63d6d2cd6c16dd605b63b8d620fad34061626eb7db866261de8dafffbf698e70de6be308c49d58af08bc8187e0570b5d861a2b111b1842d13dd5a8c8b0227ffe7c9e5b2ca6d598c44226d346025721950e22d651fc23b6d4ea1ae3d4422f3e85dc61cdddf8c6dc881fa43273b7def7611d3b9ee47c54159778c850a087c4071bc62127bb7f2718c5bcfe947e861fee50672681173d1f0ed2d30c29af9ae10b224529ca92985deb000fd61863ad25b0a1aa403611a84c709153a1762890c0a238e1e94b02179e84272f6f814174794a026359e19b55f1d05961816b7922b6e8144596272a980a906fe5e987ca846cf4d39084985051e5a96705d6a2277bea4096f28ab1ede65386f4d1ffff17c51746d811cd43bd34490caf30a24262d7c5300f65af4287a7dc8fd748b56b50d0099655c59007e33ea46bcf07e42035ae445c9a4256ad56aba9b78fc1d09cca9800d31863cdf365da481a7ca3d50b24d0cf402ef6056f55dd3bbd1da51ddf4e7067e8d8020c25a203f584a91a63ada3d1dde872743e3a9ea5e18430335ff056d5ddc3eb19ea39ea61ea01d6d4f3029a7962b9bc5acba1f21c2bcfb9a2912bad4693a5c8a9601c180e28f76eecf438364f52680010420745a6d59e35a22fe48a4f5f44d98510c62158c5f4665bfa30ec062d7371256d34b1597d9fd6b74483a238dbe8fa1206e9c0dd7da66aadddda7802bcbe316cd36e898de2ce32635338a208452b6858891da02b633378a603c672c05ed417accf73a6ce6cce15215ba5ae0f66c53080529c9003c230f03262f0c438c207136362f6aeed18d29847c0ffd8b7f305f97a3e259fcf17f40d53742beb51c78efd3afe7cbbd21b658ce28a3a0671419b602ea8cb19fb52ffa0e569bab6774d864f4650c6500611e3cf8f73682f2a16198bb88a8e186ffc3ef427987517ea843b6190b02754fad8cc257db6b761626eb7985f9069237b4a177ebe447278ccdd82b0342dda917f3bbe52e2cd62fcc35abd7144318a16bd68b9241eaee4af87551edafb42153bfc5acd3f2b9ad5dddddd1d5b77ad8b5438f993f2adbeeff541e49df7de1b15e3dd7d9cb88f59588a7a52abd59f6ff7eeeeeeeeee37b2f81dc8681a49c9b491bee090288354bcddeb2bc270a3a2316de44d8e0f6f4886ea088dc918e70ce3986dd0b8bbbb3bdeeeeeee1e4483ffbafe2df1eebd31882e348d196f69cb45d69657550a32c8ae08a885505211a3bd19617ca7603667a5f4d1ec849e5a1da23b6da410290467722f4dfe0d39e6ff7baf5236edbd38977350ecc6dc6e3e184e3c67e110217bafe9a68bb414d22523e4ab0021ee3dfa05dda0c422b0fb6387c8931fbf2fdf00b660400161ca87cd1386b3b6effd05d9c5648670dd7befbdb73601108a24bd417948fa3d1c04f31fb8cb235bae9f3a6580049d850d532688ceca5c118faecc8f630d6190a9846f465d52972b158c49e00739ab6f34cb6e4d54da045e6959fc2f000100318995561268846bf5d35472c7ffd748eb888ad6910655c36b27e484140e1947613482464e93ab0f885a45f78b1f9661bea6de2c03b570ab6fd3ddddddf7968fbbbb04371fcbb047d3dbe3ffff5ca9a336e87de2c19c69d577da4877778789b9dd68264ec6d8fd46d125b60025f7f7ffc461982d1719b885f4e238f9312e815d5ce993e1692357100142c1380cf4c2717f5723100163ac7fc462412a532a30264399524baa842bd642863639c27ed2cc354c7f487830e6c2a133f06592cb55372daed5263bece5722cc262614990d58ad1c68a052741472a16ba7409a4d27aeba8559f13d1ec6634668cfee3a42fc2d1b491389b9c6e97fe4d782f1a424d6a3928fd7bfeff4aa33806511c83afdbce8f1f7f512f2fb9dc8d6f0dfb3bc9e656d5ed81b7def758cc4809dbf766202e68bd270ca20eeccf47860aeec62f5b55651568d703ce59731a0bcb07fe02acd9610ab4decd0df026c47a2b90b977015384725c228a5744990b0523eb075290203a7edeb7cb939bc3d29b2f8a81609e92b80411c5f9c56a2dd147b66385c5b54b01a36a27632bf0f1e304c70a1d280e646c8dc43dd65aeb80124a7e29ec12e742121f62d65a5b334251b73953b5567d74a78dc4db5acb63b4c5d346ce60d284d93d13f210576b2bf4e87a76d6da99aa756cfb69adb518cb58e89bf153b0589b065031c6f72ddc182d0cf3f0588dcdf3c888afcd682c8dfd66b0dcfbf4a3b8fa464fe37bce7e617ec7dc68d8bfb82c7f45599dcd5ce93cfbdfbfbf63f1d94baa04df30c69e5bd0b406eaee982b65a94ae2c339a0b52ce369236f5650e38c5e17ebef9757b186a78d5431c214bb12d6dc7db7eccac7e18f31d88acda719c5bcac8be44e1bb9629ca40959fc47f3ae25c9316da40f6fe868260953be9d1bfe8d448cb7efbb7d3fce4472d6df2f8f62ed30d1fa5e5363fd2dc2aebfc5e4ea461386174a647e030f3747214d780099a464fe1663c4c7b491bca1192347f6ffe98a08e7552acc1873b7dfeaef97dfb4df16dc1ee65661262be834555a09335f9ad4add3272a0e2d7887dece2997c4207c9936926686c88d6a8da83f0468da482aab992157db8a65e603ae1b6f6cd233427a3132eec5cf04e96df3698a97167d5e0cea9729190e9dfdd1226de69c35cd4d6a47dc9c33f6379f8c762c97eac8ca34abbea862161c7dcdc4a03175111ce4108441b14edccdb4c9dd6e5cb9bf2a38a3b7f426148099aaf5a7276e259d703eab6f5476e232c6472c422cdbc52e27db9970f18dff1cba315b9940d7cfd4d6ef064ac5063386c60e287c4e1f76dc499cefff0f0c6631894b60993652ab6b2df145b39ab8894d49cd4b810231a7926a9574024105336d3026e2749a20a8d46bdf29a9062802431900000300184892344ce35a720014800727f068a4cc6c1c0e8c4391301446411403310c06311002000c053180e33c895575436464ab58f4dcb2c9dc7e6658f54a090f56dba56edba1b6e001b971b7ed01e6f89af44d80e1d1735491b11ac0667742ca1b3e11ab83348297a1451fb9faeeebb9fa9fe8ba6c9e9202ab39dc53d522816eeabc75562b38f6fea6aa17b48dc4137536c0fdd1b5374d00900daad7ee2afdeb066095f83e22f61d9e875f4563df19bb69171b0238dd2a8592954343c9d77f9cef50ba45c0d2ca44e8c4cd034861a4b711a5011751c73b165b1e4e13c7ab080a40507d9642b357701db6948bdd7688aa5aa26f89337022fe2264b946a01c5e5d3792c1cbd4992f7b7e03ee7b650016705a78080f03a516c4bc1a17472092e7caaa3b4680f3a0e94656503a67eb5c2abc1004108d378bf95e874881474e5e58f36747977191514f34011c2a40bb9c32db3888aa5e1a22164278c1021741b8b8a83e9812553cde21a5eb67ac64e9df79ea55a0462432e94aee2f40299f9da19418cb2f133ee2174b642185172c78bdce31c524d48b52599967c31e832e5de10c248b040b06d303e2a22c249b8eeddbce3644f11382315f4d78d3dcbd3fe3f2113f56f1f013ff620d7762b1cb6a9ec4e88cd2b6c7562a1f0cd2c0d7fab16edb7d3038d73b2932a9669e59f0f2e97e2ddaaa69721fb21529d1fb8d08e4d86d702016a0190c2912a198208008203a6db30953b4371f4178b4b9a896f3128a317147b7a7dbcd89cc896208686d2b70b5a8da1135d41564f2a82f1d6e522070dbac2acb04f45fbc9ebb94574d1e4f0ab2bfdcfc3cc907afc905b6ef1d144690a2ac10db85da1f3d806e4dbbc53a5990f9893644e76f7d95ca22932530619849e6942dd78ebd95205ff46f47d64b221f89ea505cfc79a10f506ed203966250ee3bb0235e504e931a8e5284e64185f491711436b73be01caf58353f7de7765fea97dee0ea775dc83b01f003618e4a1b97261008185f23375e26a7667559554e67b28d7596b0f2c1a79f50ec8bdaafe06194065d618cacc3ae0ff8610cca00b8fa0830ba1bafe92953b3b0021cb657bc989b00f2f3d4715c29f627d6ac00f2c55c629fe702e66c01ab64165072284f27b9c68114dd35d1552e41ebf08b3efa002946910489627f8cc7a32d25fd3f59ac73344ff4ea51a122a859808f1179db68a9483d8e4a4e0a3e774e20e5f401872d151f0ff27e7cea172dfc1be3866527e85ae134a1aa8b2d2d61d7aa5f42a92bc585ae07af278566d650542eafb9523075405f15bac49634746b1f44a460db5e5f296fb561040a12ee9a713b057a0fa889417a95ce9a43b0307e1122390a1df94e96c7681b4bfd2614ebcb6aff490ff9e59bf91891d76d69b94bd389aec96ac822f64537bcd86ee8d1d7522965ef708cf43185011317ea6edf8695c8a02e5724fea1cf04206412050e6544f583007ac11b0bc4a5248801c779929a951059e423e924e0cd011a38c6d3f3e0cbb4db2d95976a3a7284ef699159701420f6119e7d2ed93fe541df1d20fe788916e07d88ac14915cf2b51f4e2c5dfe53dc16d460092ccb242a72e975aa64ccb3deb1531e1746862c96a2d965f313b5643deb06cab9da6b99080ef1341f5c38a6dfe9130f2698b82cec01e9162d38e932ae23d141ef66bef442d80336aa485d721f08ccdb0dc2813089c5c4757aa0f7f43d3f1149568c369bb011904b4e918990fae1ef15b59a908bc4a8575cd291e4e7a1b846f6e867706322ee16b78d5bbfb6c9b1eb1cbd15501b2767d243de8901ff4cdd7a24658c8613df3fd35c426622c1342615718a5426b77a18fe765254258d9cc152e12b1ff74d3d0c1f202f9a4aaafcdd2b92575eaeec3593d8019e32c9d336a9ab3c2be80407a384ff33a9610d8c17e4f648b1fc538bd8c1670c433902154383c8657e76f454d913f503b06b0fc118e441d444167590b4f23a4238028878051012b9c5d024a25ee86b17da2693461c7456dc2e13a5287d5aeccb08d313d5495170f122624b2f05ef5320766b04d4274836addc885c8557b4be2c5c10e8c4448a1b547910233a528e29507595e389fa29e2274609dc5d31a0685650886d822239c919086aef06159d7514618a91c8212b9fd484a2961d574f8795f01ed2f5736e15f6c261d6cd2ecbec37aaa3db23ce80842286c1730775551eaca098d7a33fb461ecd19fd28d5e8103535314ac8f226441828b2a72f10417e0f8090746b3a780ea783becc4c11cfc22bda57270bd69f8caacc6d165f601d0c610df4d58685dc44c99b21a64ad888c05f745caf5abe38f9c31bc8a37eb0a002f55f32171bb1e8087adc152c47d67c45136886f7c83006d26097208410393c03c2eaa8a07b866494086f3b868a17a01e115c8c21f3b0c29e1f669b659c9579c38e09c4de25069c537878c0be927d3beeda7e7d032d4eb44a71f203b99b3321a1f8cbaa00ea103fc238cb256caa98454d701f2bd9937c5e56ba620fd553350d361bdbc6d5ab98ece381f66ad2ff6b259d5d387013607b81eb91a71cd5d81e27a95e1df9d7633cca6c6fabc1683160543fce077a87f52d2e6be902d395f3789ad0c018a0b2bc4b265ea9133b09c7638668ee4ef2c010cbd73bea08df79543737adbafde1dedc57e5dee69d60fbe6897da5ee6e707589553418f1c342406ff0b764e427a2e90235f8f5d71f2402958b6bf3b853471681f36f8a4860d9358e8fb54fe54e8759e0333620fb16d1ad477ed5a4e65a791c91043665489c32f4edbe28cd46e5ed2d28539319ecd3c87b22f0b6a941fe61a601083a9a1c503d0c2d14152285b2ba8a076fc08ba262ffb1168410aa8a632e1eda7732d0920ff97016d8d8fa5b3055e2d2da8dd20ed940097b829b0828a12ccce95720f78f96226a06f1d7fad303da19d415ee27ca0b3a432c6e05731b058bf7a8d753a8eb7fa5e733423872542da22d7cd7a0d3daa6d7ac655c8831014cb3012e038917f7729de4edb35f895ac38e1a74584514a4865b4ce02093f590be33f120b2c41e86be358ef83f6042f5c63b39136b0c5356106a31ab2224bcf693db0168c21a8c82548b4ac4f2175e89ebc5a4b9a152742e74faaea7672ae72c5da76eab5c87f346c70097f0977a3e9239ac0e26ceb1731bc23bee1d216fccf78d0d0ad441c3e981d382cd7dba60f42a7de8709a07510e515e54e5fff2b7ff804a128bfc0d5a3b36c1fd09f89285bed6465bdaf587f4bd58c1f1f34c3fc4ccda62b477950e2ca248fce3a16f09b316e37e13a6a5f6f9b062c5c7122bbd3deb4c1d7bc0ea21e0be163eaf625bbe5dd09ba6fdafa2b2d4cbfdb12407bf03107245b718a2b8bbb1f7e958f1c386aa9643deec4c1f6d83aa3dfc9c2277ba33f45f6f7d7670062b039b1d0556418d6e02884463cdbd3144b81547504517a2aefa4d62f7b0742464ec0902db1456fc8abc9026bb4663c28434ee7355bc8179fff38631b16105b961ef84f80cba9b21d364f521d41943dac90782aa0e26998dfa6e765051261b7fc6c9409760e704e691399b153a51175ec0a1d4c9446a9326cdc791d959f7c98427b3ff94840cd0b58903a8702ff8adcc8565bf6a96afc73b18ed70cd1ca2a921428c983558e2ca0ed3d8270a1885ac875e25fef748dd64b11639727b188a7ddc837396357a4268efe6d3cb797c3dbb309f50d91348c7711a5ca4e5e3a98bb8f03797a2328a26f8dbe633c04e9afb0eeb08cadb6133d434ca4896189d0eae3252d4adf1aa991c17d6d08a0972d0f848365428f86222901a7274f83fa63115e8cd00589583ce1cfc67297a3e071f66468f80c780957be3d09bbbd3b7c1ee433122aad094d7d776a257878f832bac02ea5dfba676e5c933f734b412380aa22480dbc2b0320a5ede15ef00da3dcdb36ca07582f8a71a464c729d8bf5950a97fc8e5282c5016a51e994494ca132a89adfd467ea04a7faa864f1e5e3a3259ffb9d7bdd33afb115f2881ca908c6e1e93afbba0f987433525ed0b31d3f63f770e16ff9e0a91aa348c4cc29fdc2fdea6d22561b7478426a7b2fc059e8f335d741351853be11d13761e39f50a1ddcc464585f62a1edf4fa1b02b3a26468e96bf345f56de9e8e6a93adfb717149ba61d9ccbc97270759c1448e000fddea56cd9caee467591affae074b98810cca5e37b5d67b58193f9a1a3ad84ffc4aa87f50134f57b42814116dd95fcef119126fda89c2a6a0412e1436054429caa521e630996a4486ab7a8c930c9a37cf3e277321252e5497791cbe7a991240564120a7693eedb9e2529b06042fe6de4250788cb4abe28d221adf5dfac086b88e882e48a0a4540ffd2d02628b2335d49f4162c890ab22bb127de32104168adfad02e9e0c47ea9793bdb2ef81b028c6b2362f51c15de586964344f70fd9b442491d2aded7c20c378d4fae3f4910e5660a57d6e0626681cf11792e5f3fc764078e6577c1afb89bc14607c66f9821cefc20d956580e0bfca92c94a0e872a49775bde3c86637a7830dec3da82ad457248169b0fa7ac3522e379f0139f5f9ba36615872dbbbffd583d0c062d429f1e4addc080041cc7b345399a0b440268e87f8cc2457d437684075b66e6515f8700b65c345494c3139dbf7f8c53cbd3db5556847a494b700e76794a183f3bac9e2b8c44bb9f6b742a6146e6de7d9933876387136d349ccff872914751af1615320a80ab2e8a7df0c52218bcec1bccbcd3edd06390c61e2ac65b894b16be71c6665ce19d18550c25bf6d48b4c6fc52c3a77ca1c0b849ede0bc1891134d0b2ccca058a237d9a3a9954fce6bfc45ecdb27446d77c2c663182fc938a2468b8140bfd24cb9a8b8e58e48b9f1915f29abe9395c900aed61bf18e6835ded2e295764634fc7fc272c2bd3d24df693d1d0be7ee78bb123f7096cc6e569b9b4fab3f4bb64cdd3feeb6a0b6c5306df06fe5249409b50725c5f4d252a1acdf742b6b7e98be8f2b3db7788e0963bb31f9907ef34e624d9adab0df9fa074e9ad2ea61984a71b46bd0547b961364221e4521084b978a667df5e8d9cf42ab72a35773a1d181a9ec3c1b1d268f77f71cb333d7bc0f7673375ea60b7f420579131b700cadec2779673edb4d67432acc730519c08b285aa05555728fddf074be3ed73c202def8d8f821f81490f29f57063694cc87455490d67019507b29de4ebf17bec2e3ae2cd49c4ab8bb78e624451757b1344b7cdd6e231da9a26663e1a88eb6849b4318bf46eacd49472f4d60ce1f985fe48f2ff60a665df495e02c348e8cd16ebe0a1f980e8056b9e3e378f8db39602581db1e08a393fa7380154e66a946f0950486e17429ca069700bc17a707e19a4c6a2d0f8ef3e1f17814bbed5a6dccaedab6299836688459ddc016eb53cefa442c8ef8628b5c08e105095e04e1f2e2b18f8c17c2e1639f8d1da7dd7b7ff5b583f16d502ff32b2c23da884dee4d998f38e6b76fe0e18864f5ddc55ddfd14a32fe714920febf268004bd49cd11b10e6e1151a1f792de9670dd9168ce20dc342f98a9b6519d78025fdd3043a7b6558d93614c476cb6ab760f4b254211c4a266a266d255fd49111756130b05ab0a62f27c917a0fe2ede0b527f9a075732c333476d0cd999dae2c4a1a5b761fa1cebcff06b803255ea845599acabb012cb765237a98b591082ca5ac27d8069195850e056fa49042daeefed376f3b334a6d8b663391a17dfa3dec228af96839f6361f903594f0cdd57767f5a1c9547d006623afda3f34b0ffc6e90de996db53702958d8af0eea5c3944b29e9e74cc28a12dfbfc40bf3b9b893bd3c59c81ab40f7ea427613487b58d8be0989cf9fa2620c75abd1d51c7d3088271d84e8632a0abb7249e7ad613848b585c53f7c1d925b39ba5ee500ff8debca1b67223417e89cdc1b5b92dd42c0024e4f8f24268cb8e8b419f2c5d7cc118fdd78aefe816c1b7b4e8ede44b35310bf2e6f795e3f7ad533711707197134d26183401482370a6468a2083a9107ead2b6fa987af6bf049b1689fab4de6e20562acf85a9f3b740fdaef7095358c11cc1779e8519ee9f3fd2e354f57b3b395a0c3a70f03f0f3dc10389255498c5f4dfe482be5db4604d5f2a0ae1d694e1358fc065aff8317c6048039ce44fb6719a8551521f6c91af668cec693da25fee4e78e0eda928ee0c41fd6c2a1caf71966208b3baef54e3ff89a0927feca51988371b9a5934a7c008e2dd738e9a9f434d591e5230facbe87275752a01fae54a83816aa6258e0d722731d84c1e9f220b027074c2f98e3f9749e1930c6fd7aec7d0de5cc834203d395df7ee6c637c4186dc618ec87f68312a98bc1cee2bff6b550449f66fe27f02874f5f4ccc0ac9e15b930068d791526d54307f6de43f8c330d098625d974102524736909bd6ad31e4d39cabdc5443959d9955d2012fc44016e08726406227da5029c6c76713a083d48a6a3176f24dbcd8f8e80ba7470c695115cbe0882d150be01a659b8d2f752c321d24973d91f8b389576e20b4c9648d8397ae1726e899263568bc04dab9c6b13393b9b751bc44aa2ddb72b1cea60800449ec4b360374f49c56d74faa1cebce0b3a730ae33f968dc3d3800ee478fd977a056250dd20a85cce795ebb9a95738accffb1b6866ee1bf1cdf1439ae05bb9fd4dea43b025da024f4d8127c8bd1b3e2dece7a48b97302c730dcf49e6861154daa860a4d82f90d2207b48890c5140a61a6fd2ccaec0a9753dc3227ca1518343f49b699fa970629490e2e4ecf07d670ddc702d5fbb0c7057255509076e38c0552efe56b2bd40f2f4b54f2225939f9bda3139dc0f2e38b58366125186eadc09c2c7be1509806207aefc6d46f266bc8123ce42b95b5125427c9f784ad6766cc815b3b9a3e96bd846a7ad77289627c11982d265154a87d74b118654f06a3847f2022528f043f6e89583de7654a94719b838dc3bc671e933cf37868346d1904d2ad7147370d824e7fa6532f80f460210c7f9ce1165788d915d84c38552f72593acb56c5e91d342cfdee6600540fe8a94c04bc70aeb19ee688660bc6279921d1c56618791a3720aecc7f42495d6e6516cd0e932a4c6377e85ede34d40e912e9809fb4837bb69939e3de1597c5b7821670e424b3c4c2027160369c49dd2e0f665d0138a6949418ba3c30426f492a5c7f21ecf7806196167de887fa961faca441a6ff0cf45a237665844bb4e090797417a46ed4f8cd4b50ad1fc7da14ff4d996150638f8cf3ee76c069770c0031465c0a7f798494676e209367670d47d2832b9ed5490c32d8253693d301e9648a59fb2859212460edefd062508b3aaf1d8342bb6708d128c5f49509a86802f42cfe3581f58bd1a510f775bceb29d1dbfc44dbc64105e9bf5ea43e41ba2962bf1e7f3f490f43d743eb5d651214425a3006619263bb1e47bfcfc25bf9a9dae2a1827cd712f50bd3a5e4bff00141a954422a72ebfd77f4f5e63d73098ad899ca0fee88145ffa16ea2fc15bb85231d205aecd41974b5b9e71ab579e2b85850a3906ce5da7951df71f5ce0f4600fd260a22c6e8233344804c900055f7a93e4e229a6a7d6c687f6b9af574746a6df2c2b5b7c774baaae711917529b70381542da3bde5edde4a2a6c9c8b7e922b74c0860a7d214c5cb3532498a8b00740192982bf2b1d5a52043c94c3f93dbf5d482bbb2dceed6164d963f7208d833123ac7be34a621a6efc19bb9dc4eadae91d8452d0b952fbd28a742adee8504930585fd1e589ee25a8577c9d0b3fe0411f6cfe6fb208ad6798bf90a5d905014224809a621e7d748bbd4ebac2b922e749925541901d8fc28af44fc5fef6f217464c1514159a82442fc66240f94a28ab87f1c15fa0fd65caf18bee86487b5bc598a97758083c3139a3d8200da6ca70ffe954e4362afd751bba642adef02de784d0591a9e9942ead2e49e8afcced52973d408f158d71994e3f953d46034110f7ff9f5c494516e9d5ef1b38bfaeeab024fe04e2e349bf125a592316a2d901f8db76e2319b7ec6a36c95310da980a7948b656611ee0252a159360226f02b556162e9454750de246ad2e107a367a4c2b3046105dd5600c89c55eea39d6f784118028768f46c31838da171e2dea8bf004a88b055cc001170d5d3bda1705f3aff4c8f0cf5d93121584c8e60ba7e8a8df75e08ca81be1f98dabca9012ec6055310836c149d02c52941a06d9b34ebe576866101a10441e7a1934b58d81f700e4d3fdc1d3d025b86298efd9fe1a16394a98828a089f2b6e1d96757f5808d04add095498bf8307ace5696e2f543877aeca7b0ccffa2794bd03632a133f592382f9405043cc0302797c15138dd7062325242c6c93e617fbf9ad8313bc586ce90101da8b4c4ea022fb60e90909d50101d6b121ec5ea620ce621682076d1ff88b0d241856e93a2a4294a1154732914411ad7fef4d35d682b2952f8a58d7dca21714334a966b317b50410f2f78438c402e7508a6d544c0d0f34f9aba8e364fca2d7f0c587c7b256a7e037db641324ea5d0255731e25cebe504b36dac2288caae239c17d638188f55332e6afc817b5342ce03f753d6eb4112c3dbbe1819105339d3fbfd349d876090beaded1c7097e1ef29bca4b3ff343d81c3bd0e2f1bf4ac43fccb6da9edfeaa3d71f3556ff972430a698ace493c9dfa51b72f31aa7029f80df353809341f3e081677f627c3c1ea885221fcfd488259b50cc14c5a89710cd83ac8bc5e0cff5a38343e2bfc20b66cbb536a95c8f4c758d860df1d3243baa5773d1606bd18633855e5759d05164cc8bf2d8165c03d5bebd63c513cc57e5d77946704f0a95aa114d280a3b9e31625ee6594e29ca11692fa442c8ef8628b5c08e105091e112fba926b6a4471f1a913a79db7297e8383efe367b905dcf0304f257e158130cb0d8fa045124f6e464d996180191d221e3d0e160120b7d7de0a818bbffa0a4967a8ab8ca76b437289345d1f5e5a02472898e35a813d98fb8dc7404a71627aa5d1b70f3f64e6bbf465fa7e49c1af5ac7d7b22b5bc2bb4d40bebf088b580fe962427e16a25223967a9c068f1653eb062fe3c4bd50667e4927054eb29eb8c282000ec1caa0894b5d7ebebe92b1c686bf11696e15e88b931ca0565e8d0c5ef0903d462731128ee366261840716171e5e66e032603bda4fdec79355aaf99a36fae561859bf19d39a16b540ba787c2d30d50978ce4c416e4a2343d6db2886a0d9d4c9e7dcd9cae85c3568335d3e137754a177330211d606e87a8bf7783f77f504a6fd5209cf0706034db0352d3d5196a638242d8302fb9184ef019b724a5570760261f61ca4c6a8ff3a25b963981fae70b38bcb469c686b799e90828ebdc6db31a3d16082403b90c2acb0055085fee74ba0feb1a39351e0a01be85e9dac7d3ec7ca975680c8b1899b74ebd549527c71280951141d32644bed336652f656b15a756c4827bdb1421cf9cc48357154b73075c78f41693990f3db59a89507554b1e2a88df95905e513a9e16d4918afec970960a63f570e62e034282d06e1559de22464d93a2a425e211091a24412eae71f6c7552ef42c0691c44c40d1adaa10db444576823311d4de0d283aeb2fa20541113b477973aea080138201055c5fd38740646c0e720049e5585eeb891ae6da37a238df8495826925a95d1a691f0b94cac009e395b449d90963c6de04d589058acf7c57403c4f0b215b0ff08bf195c44317f82dee28d361091aefcaba77b87b2056e47e2efe1fee80f8ce3f44b60c347808b2f502e2733bb0b37371f3325513f0ba5a6b7eae56c498950d5dad68fd8b2cd5bf6218a3d31c4507055ca7626fee04869a156c6428d6a96303047ad8fa765c0470381b23e626dacd4ebff60d2dd490cfb3a04dc8c4ad0f87fd84e54e02aa93ff691153697815e2b42e3fd1e1b38bd0fbacb757b6320b610f0ed790d5dc166c9dec47b4001c2f9da0fe8c9be189ebc45f08cdd5b598a845783dacc82048e9a5335cbd3e02943d875e857d8915ea2ffd3eccb20c39b9833bd6fc6f019419878486cfc31c6be926b9ddc362f5987871e441dffd204ed405026a8f972003f3712ec86c5d55cae152646639d4fe669723ffb10219ac9ba7794e64c4c38cdcb9355519b6655bef7a2906a5680b84048b2676382a84b7f9958ae4965ce9ccb7db2f3d2350f742ae9032873f1db72cf54b293b66499e3a663324b6520b4efaffd0d5047723c4e891bf5aeb5b7347cbfdd4c7d9bc23752daafeb3931a7cb7076a929d157bab6030ae425ee5e22b80fc214d5045d21de871a544253bbab28425bca2677f5027eff8f2bf9c647f281da0f7552972741c42683edc011e452c5dd4efebea9a36955d65069980f6f29f3f2255302125c88ea4cf26f477facac7337c3981518313a1ac9fc4f0d469909a1d5121d2940f9ce1b84eee1199919810759d988ac3fc163c3bdaee24c402d3cf122b3b7cc5175bb89a35973228de45f39fa7ec4b8249f616babfe09f25e7cd6e6495657de9f858fd1b1bbcbea9a06edfe8414682a965d4f9cefd7f54ce34a1bb1c0f2f61e7ee648325572d1b8e0a8a73f695d200498db42d57477edb1b9cfc5cb6c0d4b275150b003352a578f695af34374811d1101c020f1e9cc5154d2f3f6806b5b96e978853eb3ef86daa87823345f0e3fc20e138d74aeb1f493b7418c2a4fa6ec017a5f98f49771481a7d3ca6b1b47a8b2a9b83cfc526756093fbc2aa3e08ea07083f1d5115565c52a83a058d95fa72d10b906065aff8312a0857e257db9e82217f647eaedee4eec0c8ea230d4639f98b97d194e34bb695904131069fafebf9c849d5f5b6df27dd2d391837f394e9ad42fba069c33560f95061a7ce8f73fbaa67f7da1d1f75b994642337fdb3170b82c97f0d86cdc21b65315715e0c0e5baea029cf681d00f4f0b515f5fa9e84f7904210c2c3a83b20322ccd9f79ff91c61c0e8057e978753e8df5f7923feaa2e67c9eec3f511c1c4513987c68ec41ef8758261c9fc940e3d19d4258fdd9cfc95e07d1c60ce470d365433e831b30aa4702ae3d4d706647ed3c26dae174acd557920ed9c341ea328283a4e402e150b7a6abd55143de939a45911b66389654881664880ee3b461686b016e1e452a86ba630d5b00c061d824c9213dc7d0db832fe8cde0731740bfcd68b60d75bf872e3183cbc6118a79e9798c892b2ce65c6f9c86f2bf744b810803fae062dd4881e44d2e96237f2642e2bd98d41b0ffeeadbe94bddf935736abe8cc36ea7d653b4cb97b454316f173b27fd74c960ed06dbd5d57e3e987746615995b41ab26af409699e5a6d39bc1976a6dc91dd19651a19114e837b3104c90a8005e47aaf85a0cb63489eba3817a81b696f9e6cf67d65c1111158d7098ac416bbc9ac4e824086ca6e32a688eb6dcbe7682de5824e83b55b0be811209b0a1fd01b1368ff0849a010a44bc97f39f67d12deca4f6ac90d5d410449a55ca80859fb36478987da0fef629673151cce37f123b508565021f2c486758a762dd28b1f1eb39bcdcde14c5176711ed9bb737b227cec55a3a3cc5cb27c9dd2618929888390a47460a9a6e5a8c3c6ad271d0af85fbcc99700dc4d91b0fee78d2666aa972be288f2a3f6305d7815002f697b02f1baca9bf5242c57924c9124d650f9de894f5ead082cc4dac171f6aafa9f6ee8a7d102e36a0d426662207c9010294a023a88425bf46af0bfd98811b1f54337ce7a2b312d4e38f91a8950ccaedb049d9992007671db849da02c39f046a913ab93675ab82a531eaa018fa38b238f96ec6cf6fb3762f5f8db47c3e589b02308653d77e69bc728130861d512a672003d39a44b7287b51388a4ecd27a9500a7ef9f1c086d4382a7ccc9fa1aae9d082e98f062845890e8e2095f2411d64eb17aee226189154d99ffac32371ef8235fa853e6c51d287ab43f68a82a4db65b9932f5acbbc4c33bfed2a78fe3f84af7bbb22b5f1f3d948a900554d31a0942938cf17ca4111d65a88889130c81048be9eb71289443514371fe90177e07ce1e1388f353447418fc3be0af12e6c064eb79ae12110a453f5e5cf4be577d511a18fc6eddf45a700a1786fc3ee817ea5a980edeb766c59f29a68c6b271e984be466124b42d4eb0fbfe235c1826132e122eed115611484ab96c551752c4a0f036169068d18f080d05e8dd14cd025743c83d659e567d415c399593b534c635672ebc4f33bbd9bbd00facc53572295a0b330283081f85fe32c3de4c79bd3b2cd016426136128631064e6d077ae0d377a73d75ff255bb001ca821ebdb8c2bb00ecb19d7033c7ff6b452d3dcace8427e402b38d07d9fd6397ad2d2f2c9e775ac7b8974d1efbf9df95a15a74baf574050e0aaec26de0bb8ee040fd431ce9d2af1733915d4558a62f415d01213db0819f0e9b81b0ced361824a4ccddea411f0d692c3a0cf043ae08950625b90f9d9c50b65ec6f131a182dcf51c5b2d462806966386fee1f20eea83e3379ca968ce2919c49aafd1b95ea2b6d6f70aeaaf4cd18dda2f9295fe10065802de78effdaa2301b95f2d116f5ded22e3a5cfd0e01d5659d2d8be1a001ad4d2500004350008303f612a8e68cb1a0332cfcb0de41f824fb4268a844ee38bd25ae212b533542f7e0685e229c7a80f922a4c2591a5c76a85731f0ce88554e41103fcbf3ff8273d1e3399b2cd951f3bd24611f51f4d20ba86be13811469fde8718937eb5bf079428251c7a7848ef938c229dc764f416c89c37244ba8471de5b079e0f4527a3d4e169ea777e74c0837c1e35910b5bb24fb719d8b5f3417c1ee847b01ac2d386b9e8129921b14ed0bb9af87dd23a24c744d108ecd50645ce8891059a4b62d12cc3502fa0bc17a89d02d1aef815a104c93c2efd0f92fd34fc34a16c6129482d97e83077b1e481fa16515a684ded66fcf9a22d8935a337682b87a528ed19f3eb353fedac3b1ae73b65423ab04f315eed0e7c8f7ef198ffe3ba0e42256c81b057f14e6bfbb79aedb283804b358731384104be8f7fdb14af3648bbc52a782f2906291f53bd723fe9b6d9cdd4723db472fe36575479ff46f7f970bcf6edc7b14740700193de418016ea67927009207c0a12c7c18f7266a7b26a5eff000cde2c4154d21ce31372b7c97b16318449dfe92cfcf6fa29056c43034bd4325f1af9bb3b0cac94e50c9bd7ea3ee1315c3b813465f01754e7d769f4d24928dc1c5376be76e9c15455816115ea4fba0922947032a9e3cb4c17b7ffc3020851f5033a8bf65ec6384f19a04513c54f9cb4006d1ca01427d162dd8a0550b2228b83fd5ad5f12087fe5ac4282da3841c03ea4de45bb3efdceaf83804d84ae5af39d422d68fbc341e5c33b8da6daf926829928c47e9427266af59faa1b7259709813ee4ea34392e60cb48c93f5facde9f7be65630d03a5c6335bfdca7ba7ca52869376e633fb9b29e03f5798eaafe52e36823976e9173bc743796eed4ff6953bf2617480128cce5989ceb44f8b45110c364a822024c47ad64aa954b93cfe405632189d766b30555a0ece712805d65419dc33dd7bdc592197fc16c612421811a0571647aaf3ea94560d8b8eba4865be060ef0a0857543f098f796a08dcdf6ef750d0ca8c821ad935d09fe33437bc20cace549d2861fc2c57b53eb62e968836868334eff52ac14c50e218567175ca95255a57099c1dc33e679b64060d1371d38dad8c6c35e36a043d62e31cf667b4851806c7ec4cd3d02fcb66e2b9cab3943b11fc8729831cc0b72ed14bf9fd671d3b3c0139bb3e4c746c3bbe00661457c16ca5836bee53afa32069b3cdb2e634bd8e89dd1cd4c7d660b842274dabbf7acb1b164e33ec2ccea27a19bf768666e4740639a5199b0996f3528f846ebb070d6b09dafa425a2b3012e10149e5e40bf4b9f6cdbdcd368dd0e504e5689c4b65b66211888f5c4e557798f11c84074ec4d700d4f106ef7f5d593dcc92a86bc74af718b0510b09705be515015065e63b1c3db799005cc4ead26ddd5480427b8e159f9f99535e623b540e8f4132462aa7691a03a0b60c3222e35757bcfa6c0cf41a2809dbde6f0d96c95e077267113ddc1978f6f9213f242e468594bcd7632f27296dcb2b88a81cf0224125f0c0e6ec04ea4249ad598a475761f1dd9a4fcbfb0fa4a811addcf61258d5d901ea9eca256119ac0aeeec01d4bc985514a5e56e5e3a78f6d2b15f9dc33f6d89e04beae6dda5cbb00212b3c36ae7a0d8f52014ef058d05d4ae69126b7f548b5a34585ab12a4912f3c4b86819857701b360a4aa7cf6c1614d6d918ba4c01b098a81940bff339dc9973a37edbfce5370d1f54adacb94ef17fa60424da93c32a923794204c9e081061ffd3c9b3474b97badc54468ff78e316a6368693a4b4b813f48bcd195a2b359ac7cac34a1d19403ce57631b70bde237427555d893cafdda21d44d35b9c6365af69b7611a2972f05c5abd2677f3b09dac3b972ae865f0431a76881a527664ab8513ec5db187242327028e06916b03360fdd0e82b11ab4033290f0be70766b397ddf029e40da99814cccd246305ea97b682f8fa158841f070b234e42d58de069e0353637972622b694600d419d735aa55684303d428d3d34e2b5ef0042b1b9c0ce2fca6f7e575cbbf24c0e86da8b895689e0b37be15f0fd4f217563b0ba056233f7a928573171d0e3c0897b5e0ca17068a2a252fdc97947b40829748745b04d741f724ac17b19ec9ba3402e42079ddab21e182bc471fe7831cfb4907f0b5069d7a8fa3b563cc1333766b8ac01cdfb880053aa64a1642945c9a7ee41b3cfbd2ea0b7d4aa2c13a82fa035203c36ca52158ed566486aefea154dddd8ae4192e7dcf007113800e680b44e3eb64b7eb56e7085b74fc5230cfe7c74f5959b7ee9de66dea8d2e24032e2b3803b1aea2e6d0545964751e2e34740a5430773b893eae669953447415e06eb0dcfff468e90e13e04a9ec53a777b65846966b8ef52fe4d73182f36f31c77da07beed8c941678a0e8a6eaebdd693a685739a89769da04222506f6a0aa7cd1338d42f59cdfec0e8d128f52d4b9ff563391a813296351500a01b5813828318f5cc48ca0642ff04b2369d5802534488dd4d81a3a701349695a3f9328a78d8375cd4eefdeafc7434f44b2226899c9b3f85cd110e830f829a56551648b52629e8edeae1819bed1828f9b5004c1ff6aa5e025591c91a2fada8db6f906a259c96e7c27afa56bf62c372193b734c2805ee980e2d8c5f730935cd18bb8c408191529f345ae0ce5fb0f15d278db054e533ee824d61f3342bf4a6c9060b0e031ea40e0fb5a6ec65e7d7e43ac15354912c89047a45bcdbd701b97e5848881db2da72c6c39fa65d53160f3c85a7ccbe202734c66cbca34ab2f150543acc022c4c3cece3c6a4b39e74c342924e0d0d8945039fe80df2cf235c697eae67789760c9b8c2cfc1e8e2546464dca28e9f901688653d091ad91d64d56836fe556621a2b6751b036be6bb2b21936a58ac5958b51344f26704e054f27349291e28d8feaa0355a6ad7c959bbf520f4793b118b98b11630b7c8e803fad4a8f7f849889cf4137363c02db06a5344600f7d48782d607d43cbcdc7777d01f5146499b9b00514d8af60278b34d1f45d2f601b855bfa7b66db37fd4dd3faa813044db4d6a3ac6533b2d729f6882cb33332e80598b58095a12a2879f23c8166552c3aa2354c13a2e91a360d61b8432f6ae400bbe80b9b78d1567b15751b5ad424af1e0bb851b36438fe3a19e4cc9a48c063d8941b4c4267a04824fbe88b84fee4e82ab900e58282b295a29285153f7ca4ff31c02b1006cc95116f0912c09b42bf4c7df72cfc0c1fbf4b801c99aa8aabd1e10f56f89e89f27f5fc4288822b2da0c62348e5c98198bc19a4272477ac723a7b89299e5b7299fe080b76f886162e0289fc9eb96010206eb861f9e58b31053734cb58f1f86b8318454e56b97635968eadc0616bafc0c95f5ebfaa56f593fcfcb2e8b945a417339a6378f4b137fc0a65e15e8dd4f53d881d863fb651123e45660380dec6cb17566d0cedee9f461b3ee8c30c6b37529d3af1da9e690c1bba680b254b566e4dacd16956cdc92905896f35b9190e2c98d6dbbc9160822f1985cd4ab3fd3f5422d5069544b56bb200dc0352139ab11e2cfa8d4744e70cd0cf0cb6f15173e34031b51289d3de6fded679dd98ae02c3bf9f10080d88f30e9b58bec596677e9d27a1c9124c1dc85192066bbde645b18ac2bda8ea2b43f27a609d601a865307bb0e3b7b0b9865b285d241e84531fa52dc6e5139feabdbd736830ecaaf1af3de2bb13aaa1ac08f5601545113f7c2111ff01452fc7fafee03eb062b0fbb9eecad07c2ec593dbf712e7c9d6e0c1efc38ed99b9debfbf79ab2dc2f58405def6488b9e46e1e39ee6000df0fba88318b2b2e06194b3857d57baebf4bdf0e73da116056db3e9725c04fbd734c99b8ac5aaae401e29d553644de7a8887f969139582cd8c800c424b5cb9da17016aa92557be1753dc2f3966dff52a6d90d9fd4f2c135fcfc9bb2494288dd9884507913d119dfcb78fdafe00d94c45cd9fe19a04ab3c2c8682babad92800f4b0a4a31efa70a4ee010dda3d4c41560bf934dbc1d43db8f82f8d23506d8e9eaa1ace2a6922949478674591cf40032bb63ca474a8fe3dcba9f99d2cbabe770483c97ed6cf60959aa95e20a39d305935eac2cd3b51e1c29052b09930db8da592dc805b6ebcdd45127c2a60253e830d54953ccef815e637083036a807d91610951c3d78e31735d2772579ad6a8d2369e12cd63e7cac10c951785ac8c60444c17aebd796c920bc4d8bd9069b9215e1d0096612b4eb3039eef1861939b60ae915c22fc43d73d22f2a4ad01cccfc01ba7985554e2092d49b0336af8c18de0a55049106030632411495ac824315c1b32c7b810d18ef01e68000005455bbaaaaaaaaaaaaaaaab61ab28b4bbd782ef679b98eb2f192d43c252f42a5255ba694524a52ca4c053b053b05ef47eabdffdebbcc523bb24ac964bd325fb04cd9976953722c7bffe0fbc7587ce167d6f3eb59f66c7bd23df79e804f191b3036dced6ed7e59c0d4a136744db9115c9c96aca9c51351a37f73b870f8f2fdce469469b484da7a65573daac36d38a4f3e40deafaf7224f4fcae5a04b2e530fb20991d0a16946acf8af167be6eee7d938dab3d08bc7664ed2b0131d874d2404b70789ebdf7ee6e77bb2ef79b7318d7bf300a5d3bb2ee79c0174506acf24b526294cba9be7befdcefedeeaebe39df5f96a5766495cafa2a6129db5badd837df7cf7de7ddf7ef3edeeae38aecde1f7855dda5ce85cf65c005d6478cdf776a3f3bdf7de7b62dc1e9b1e126f0f10dc53f4bbb2078bc8b7555393a8f5f7ab978a70f202b8bd17fb06b7efce2418f7dc3d214ddc5dee6e77bbbdf3de7be7bbf7cee4b67fa7d98eac4e1a284fb46407497311b9bb77fd65ef05f04546098c0afc7befbef3adb515d39bdb6c4756278d5bb202fd2b5e8a4b87b2b8f480d3bfaff7f0eb617f22655a19e18227de60b835b5818b57b5064b84450b2fdc4403a71be9d02deec707442db0a006ad720285f0877684e334e70525798be619184dba546e7a33c72303a3c9d66b343170412461f5505630309af415cd8d15d317a4418d3b0dcf0546930d37d882b65d8adb806129a95e2295c582b70a5d01abc6a382b18d11bc15520a52f4a9130a5ccca22758b2f2a830956602a6f7b743d71da31bdb2b814d07089ae452f9bf128c870446933d1d1cc1d2890f10ac4111ac0d866089c7784ce104c1075c3b1a0f2cd5707c3c353b2067c501a34927ca3dc368361037a881a50eaf87f6ab4f31cd400a3314d237249a1e3bb226412b181de603f6f89a555e50511f36902c974b123828068c269da3215d000dcf027e6fc70b5f5580772705f42eb33289eb789f90260063430e5a0257c8c59865458e1776728a874aea5402ed7683a6f02a7111df0d26bc08184dbafeaa4280458b07052937be70498abe07184dfcce5e2a5e315e360e805b966ab5014a4b1cc52a2889b133c068d2dfb6b8050c7ffbbfb2ac59e654c09bb1ec8a2eccbbe806b70e9a00295b93b8e1932c9724256c5504184db6b186697700a3c9e6354c3ce85289e7487ce12803a8a515c02d8d004a351c432c9e13b87806d0a6eca90943ef2a7578dd23fcbe7055dc888a3ee51f7a97cfc32795ca273c570046933e0c730ac0cfe3bf4bad00e09254febffb4f1a4dba3b87a76185e7547d222d5a681b3d96bef726391b2cc168d2934be5ff4ab1e9379a6cb73a188236dcaba772cab0fbc7306d22bff40b5b0d5e68321bdc3863e93a462ad684caaece98bc094563282e4b092e2ea9fc7f979c5959309af4adaab10c8f842b625e7d09d314de49e5ff5fc9953682d1a48759b952aea80ddff049964b9230bc3cb26ece41b2788e502f0c85f7b18164b9dc60530fa349f771d33cb29aec97ddf049964b12b3be57b74539a45a51af35b8e1d38c7056451431e7ecea4bae90887e529f344fe77455200df772e7bab276646d2ba9a383c79a9e73eec2a57d49513e9c6a766956d525a9fc25599bc7f00d9f64b924f91acc23abef5b726f729cfc7bce4be86836e73c862fd79cd81c33fffad77b1fb2d681448a66a01a2f183531546d588822516c90b0faa1b80b7b3b1eb010928597b37b20566f1425612f6658a1280d1e3c82948096e019a1eee38857e1b7681b1fab278a23c203d1ebf278e1a0638a8f8e12a1c18f22e2ebe401e4394a92783a708c1b254048afccd05862828623b7587a2a6e91c33f563fd38d199d88543552a38a884314d5e28ba16b5425ee751f8d1bba71f4a65b35f292134e9ec596bb4b50443b945117414f188fa03dc521241fe41463fc19cc23ab0711529e20ae3978034d3e5c54384d2abe6a5858a4c488c23838c5001ce154565d72fa97aa538ca430342eb0271899c1ba1d3941270318355d88bdd5d13872fd4d099dddecbb6ea97d64550e28c4daa0d0979f2042ee740545f1f2d3a3a3f7f0bf31fb61387f3dc57c9d4a3d3de5dda0e4d531b9988d4ed445961c0a31c44bc8e3077a332c75423c6f2a4cee812c30ea168764d4b5d5f782c1a9b2f7f235e5a12a8183d4369315a8d432558cd72d3e1a2e1817158e89ac4a6204558fabedc912e71623e765020514524861fa33c52a12e4034113273402016f76d5ba2a0f0ed18f895677cd631473ae779b79df7bab2da763056b288b2aec459a4f0062708c0247a07869dbda8cd56a8be9f8b0f181aa089d68c63c9d4d1eb6087fc8da5eb4f42bdf6a2f527c7d56d1c5a1c74351184c891f21251d644db93449094960e5dde6368bad2ae62e90ba7022c3ebfa504d496a12449991b31382df78a5b21d2d3591dd4397fb13ad1eeaad57d7e7813872136d7ebd779352aa63de17d01a9ad409a11a47364230670baf352366e7dbac45fa162bee794447961382510f587e21a01c823ce855a95437625f577930c4a4928d57ea573426c62bf50b8a57233e492e3be1320fb5eb1110735704d92a7fab3c81e1ecbbe6e17beae98db44dc87131c4c28f886821d5827fcfd49545f8f51e67afba275514b1d183f3e3df4f87bf86451fdf183104e93f0ccfb068f6b9b9198af0eb7d4cde0717fc7bbf5e01e93f0cbbe005ed7580b02c77488125fc10e93f0cb32876d216278cba08bfded7a813fb8372f387e14c836ece05ff9e6f2de568952c9fc107c8bfe75baf7c457a91ceb7e654f438df1ace83894f92cbcfd75bdd6a7dbdecbdf75575b3e0184feeb2f44ab4c900a985d5789209ab3e51ec9d2459e6ef3fe255cb4b70b39dcd5a5bc5fac33bb1d6f22ac2d0f5216e87a7ed4be70553abb5148105a640c1d0eaa2110fe355ab0bd0480f7795fe3eb7c0f9506da3ab5b629ebc55719e3b530ad429ece0d4a081ea59c3a90688182b552a1410bab05b9146c35524c4d79deb62fd9149a7b8e4ab35d5492dd9c41777c42bd569aa527c616492ddae6e87dcd764770fa73c2f869879bbdb7be79b01ab55916da7c268c045a353134c5e513967726f856dcc93e551080f71fe3effb62b8433843366bfee435d27d15ac8f6f77d75c191670c8f5f745f9cbab7f6ee191af14caea8a92164576749318fd7abb3954225be979efc46a9a8bf7712a92054b43ec62bf5a6afed5dd79da5e2ad6fbc008007258332f82ca2d16a0015152867088e4a5475a3a4b5065eb5a6e5bcf68db15535a99d9c2bf52ae3556a47147bc4abd40e1a9f68340d3d5ea9373af146bc526f6892f4f74971961a334aa9301e8ff0d30ba9232f7dd7bff6b66db219ea6ca4e03ce79ccf56788a3398385f99e9c53b5eafccec028b39efd66c9b7394161a60574a60dc7b5fc1ec80b09844988511323b09514f33bc9c24a477cccedb12bcd65ae7228678ed4a298c3be2f5ca6c2ba374e8efdf8e387f5fe4f0e0d59b9b9991daa00acfd9ab419e7818af57652e58b685a212d5c8832a30a2b6f8a0bcaaab5fdc0729b9243f36a9ab41c0b416c620352efa9890136fc4eb95d9d3095bf11baf57656f7a65596d8078bd227c117bc4eb95194d6ed939cd3647f0974a25c9ff6e911341b61f02ad7344d71d76a1f56dd7ddee765bbe416c1c93aff2ff1d98a2535970d7142b1d1f221f7edfe570bb7e73ad5f7878245be85af65a005b641c87dcefbdef8a1d2f3c03345334b334233593f5d177ef9dadf8265fe5ff4a1512edc80a5454a2b2e4a15fbfb9d65d1d1cc7171e41d78eac7b8025236474f8cdab881b5f59586d47d6341ff7320bf1e045198459527f72424c2eb7123435d67626c386d6ced9f968590df18dc55cb5731a809262cc10b17011c5c22ac4015ff3cdb085f8216fbedf42a35c170bb3fd4265fc3e219ac262b0750011821eb22533bb9f102af80b1da7c1b22b7c8dcc1b169c53e05f04988cd6a62a2b5c74d82c9d2c9decb6c651f13a879f2a2ebdb0a73f1924789045c8728555c89276d3420423075d6badf5d118af5a285afe5ef4b4817e39806b5bba15354d2c48516a562d5c9c8b2053a289222da926ee086b88d7ae9696d8c72b965c5c7c72d4807cbbedadd65a6bad8f5a7fe4d75aebef7b35ce5fc359450b967dab41ea3fbd07d6f4549d08fcfafac9125cf183a6a00576dedb9a141b5935259c52d07b18af5dcfadcdb5f60076d4108049725bbdb5d6ad9f7a388a3fe2750ab393f613f6d282cf8ac5d9ba71204a73799971b696b487d7e73610dee30ab37b7fb18075a80f4cd6cf7dd082a107629113765d0990d7aa0b588839ecac70f12a72923f1038c4f967110bb44a0ef1048adef69fac594378db78fd722311134f7e16c927f9af1dd0222743b8408155c7415041e9d08cd755b9fe16031ce2753d81436b34c1c1faf31a67a98f572f1a5121bbbab6b5388d371a52ec028b9e6522c8f6983e606e36857c472c320b210749cbb29759e038bb3babb5d65aebdff9e69bb3bd916dbcad7073e4a27335343b23f2367ea63231045fa251786fadb5d65a6bbdb58f57ad379a2962fe9c732ec2ffdec575fd16b4fffc9f681cf022ffdc6f20d7a851a346ccf2b66dd93666599dd90adc855cfdeedb95ebb65d73c137fce56db170a25bb961a615fef89c50c19a9601b2cabc989bbdbfdf9b6ffdb22227ca657e9b917dd73a3c18f8f71f864700aa20fd48117ebd87d685f0ef99ba12e93f0cdf6c65e0df4b15458af0ebfd879f24ff7e8c1882f41f86655eb85870733314e1d7fb97b811febd5faf80f41f865bb032f0ef3b408af0ebbdca5f8811fe7dd3af30869fd7a0df91df8f05268e0037fbae770031206d133e0c934843f2effd48117ebd0ff114c2bf67ea4aa4ff300c020844aa2852845fef9f82cd24ff7e3afc48ff6198e64d847f3f460c29c2aff71eb22cf0efdddc0c48ff611866c408ffdeaf5728c2aff7ce401610fe7d0708d27f185e711be1df3f3d8845f8f53e45c984127e88fc309cd90e5b306c62cef17aef5719807506e0dfaf84805c48c0a90c61c43599ff090a6b2ec1b7236c8fd7adc19aa1b498c8516c17b00338c601eb56a7564e48511e96867506e1fd25f4c0e80195159ea543b733c564f6c6dc222b10203a11a337349745c4d4226bdc1a4ba0657fe213e5c96a75b2a69dac7d42f109eb8cae26b2482b72c23663f69fb0d098fdec7f59f67bf6c3ec5fb2df02ec7701fb83c07e9686fd6c94fdac15cba6c512688bd072c85030bb457e1886d596deb8900d4a66a4a746b4b499065105a96f59502a9d2dbede7b9fad9f66f4358d0a4513f39a231079641df6a8e698fc86049451918c9fbea7ba6ed5b647aedf687dfe841754347e6a4680b5313b5f8d3e943a9da09728b3f3203717e1d77b9de4653fb9457e18d61a7ae73baf6db367c50f7f627060516d53400098f180d37c5a42eb295ce1a47b020c177bdc0c6538eb155920198b2221dbc3aeb127d6d6fc995906b3e896a0115d528914990e28404a80722fcc669827cbcd573869adf5bb21c3030b5ed065571f65bab74887a074fe5e3899da46adca274caa89c4d7009a121f43b0039c1cd3181c0f6d8cd7294c0c2f43d6bf1ff3a1090cc123c22e068fac4d12acf25006f7fc19ebdf8b8d114104544a1cd21a0994a4a638206a07a39523bea826b81f0f8433622100926311535557f6550511011a0f2e025e18a4c8a80b4a164c90f0038f18e15961040cc88894638c8f171e7a1d732f1b07d98697515a8c222af961915bc2eb741c4eb35df409f052a914819291ca0fc3ca18bd3d61a9546add0276b6d8b66d2bb26e58b62d090a1bdab60db117b7edffed276945da0d7a67ac8942478ca629d5224a636608699ac6f5d65cd3415ffe7eed6359f6c337d07d23433c12facdb0897a0500151909cd1db9713137d78a8722353423a42e1b1a1ba709bea4bf0f11e264c93af3f79eccab2a079fef968e9d395f5f27cad647f87d0787f97de1231c44446cfdbaeb36b921f1dad532d652f5b7f4f9ecab4300f9e52300db75c5083b005cb13b8fa45e51d03a7365ce878623d586c8bb342ce1f8e1f7fdc59f7c95ffbf673b21c70fbfef1f6648becaffdfb3198ee1f8e1f7fd671d3690c957f9ff6b1648bc70fcf0fb9ac91d2db8ae59d80709ae99b408bbae19d3c608ae6b56e2ade0ba9d67b1390df2ee0868adf5f799a3158f5c1f4faf87898d8e9de23b35e41003e16e88352b18f58a6a1ec42e8e2113885b268464f4d5208eee72331a906da652d46e981a0979f31d7605bbee9df1873d69bdf599db5f2adf64a8a10c77dc46f894b9cc08f422804c54204e0d3ebcbbb4bd31c156233f0cc35d73de761773ce798cb6340e27b6152f1a96aa2fb8e7d90d0c9a16e0955bcb1672716068fbf001eb0ea9461d9353f588f840e41775a1d306c5c3b35dcf7bfec8acb54e12a2c56148e9ebeac00394633480aab3a60b94f0886bf3b49a387f64ce7f308d28001c82faa4a8a25aa015c58c8089c05bb17445458ae919e93f0c871c6dcce0688bf0ebbd20d988e30ac2a8e78ab30957c4cc3e414e2a9f200d2f66e69b0bf29ce1dc73ce3939f6422b933e3e492e8b6e7af3404f0d43ca5754a8b483cd99b38d995fd0d662a6058e10081accbc37cd39e7b5b9d8ca4ce7be7bce5defac838645f6e59c738e8461add439e9d99e9c53d764c43d3e492e7d0ca2a7ee2177ff0a8dd9e5fce51d22701639c27f781e47e25f5b9148fc2bf3d43f127f53eef2f91663bd3e7614b8cf482ad9d45de13340b22923a22be4cb5ca22f0fe65b88344be19e7fd2f9a7580a9bc3f8701a31a190a77a824cae30a60430b524a40f34bc3a001276d0bc5c7023b29b73eb7a4b910279990dd16c4d916d20eb98a103f1ed0166b238ae46d0df83c4eb74e685c592bb6c4e912416a37c86c4a810eed0e71e1563745fdff70ba83b8f268b0cbc40e9d91a11531b539e726ac498d86a983a430dbd5ef95f1318870d8f0e0d1263342ca632a84baaae60528919cb7b33ed7ee47e4d59c31da5c01a3bad20ba18bd2940b335a94b8b8c5de2e5224d2288fbc3effb5d3af6de5d6b6a530ad75aebdcf6c54c7bc4ecd3a170ca060af563eab50f2750c8ccd050cbbc9ae32a3f9360716fada950155a6f6d8c572a93964760cbc762e6599efa67a8c4b32c08b0ee6e2de38a01957192e187e7b5d65a7bcd75313e79f51b07749262a4c030cb116b5d8845c19145d95cb890e520033ba6e3593fb7be3e39efbaa146a500b31e34cf9c64678e78edc291115bfd0ae043f6de9b1a731363d48263c970428daa523e3c4dd07507e676331433b5c2f52dad189dd57006ee31a19c35282c8e70a85abd0bbcc85105a17af2866ab0047627a716f424924bebb1113c0a03ac6a5908608698f40d87691089657726bec410375eb3be6088f26f17f08190a7bf7701e01a89a666918aaabc385387a858a89c29f23ec84e002d0525e965e10da5a9504a61a2a6472585fdabbfe33018a2116342a96fa54f0a58e7330d6a72f1c5ae30363b59722d602aa25a3b12ac44dfe43c3c8bb26a84b0acb4a7c9db5c7cbdf775801c787af99b8a61820a87f9636248056187aabcadcd5a3abc9637a864ac9df90a0d4c6e50003319000003301c077224489228846d0014800a23e85484a460482218c8827140180ac318068128868128866120904228ca83ce491eb753ffbdedaf0dfdbeb44f68ef77a4fd227bdf959a4f680b9f97f40becfd9e645f6ceff3a57da2addf93f40bd8fbbe3449481bdafaa56efbeeb67f65d0ef49fa425b9faf52c91084b47139569b08dba7ffbf06a3fa1ed75e9c445b5e550f4c821c062fe0209fa7beb99fcf23da83eb379eb5486ef667b010bf3268d4cc1082c65701362824b8c06e1632a3f16f8339ccf9b28f1b9a058f16943626a07da7ae0ac127be325b2f15a12f974f4c9df6a2e63be6b2089642d8697f9864364abf00a3d6ffe191d9d822aec92b9a6ff2c4c2758bf72d780d585a9b81837c6c47c77d87924391c131031be19a6e4a0e5c09a0a6b72ba3f831ad12f38b666b41f406cb88f5ba85d2690c58d54133ce8e4a88aaac56f4e7b6860e7f27744b08999a03c7981fb7ef0e36aa27b80df298f0504d2c50240d63521f00a3e343bff3ea2bdffb148f1919926240ee94bda7a07d6f3c825235845a220a20ab1cd88807982006597d887a881ec7497dee857bd01d608512448cf97d64e8194c7030785b7717f4f4c84c63b4aff136cfa68d26b698ca3850b1cfe65808dde5342799c7e1db7d4ef5a0d046fb627dec34c657d941fd3b8631ae4b6ed9c1af5c24156a38fd81484ddc9cb9e758c57abd1c7cc2e9360761f9c036014091fcc7fbdcd3118b96609ebe84843b6d82ae8c94dd1ab58e67c9f6449de5c26103e0adef6a482a5298ca6186eaf79345e1ef93cea811a147fee101c0b6e855a743a32151b19bca504eeeb3972ec33c68a546ea10f8ea2e8eb0933eef99f8257cb1cb42e80732f0672f79e1d74b141c6c6378e3175f1068c48404adaca11c297eac92812bcce42dbaff48e64a255b7b57ee45e84e8b9dfe86c0ea5d0e86dfb0b6d9a007182cca333990065146950453d171c26a0156d8163d73c2c484f59c8287a1098fae6a8f8e8762e953aff87e4baa0331d640555b9824897b2f9a7b93ff5b79805aba3b60f7734dfb5c75cd7d8b7facbc83e0d758a9b5b1b02da4ce98db4e543ec2710fa931669bb12e376ccaf103bc3fb0e97c386e6168fa7ee5e24593c6256162b1842787a4735c0954c7e0de3b44969f55d4c5382bfcbd9d9768feff952ebeab17b8eefd984625cb38f17615ba74ff4c9db215d2acc3d1685a688577d6773451ed34911eb6494e47d7a9055529c57a1b8da68d56d864ddc072800cd7bb5168c3741640808f417a0cd3f16415f6eaf834f71a3dacd63869e5610143d2dd72a4c866082ad7b5542262c0814856cb05249e5824f5806c9abcbe8e67ded5dbcd07464b81c079637d642e1a17823ed2c1dfd7e8705d5f6ac79a20da087616ddfbe9ca9af774a7f6e937d2d48624ee73602f8b0d582101cbc286154207a5bb5b564bf2c12863421c9f5cb25ceeb6ac176521b9a58274860d8a4d28fcd3ac433f2d58a2928c4c85fa45817ccf3e1af3addb1b5c83e16b7655e75c73cfaa8da1864f19efc5e500bf96b8829d5d5e809b2dc838ca126ab8fb671ec1633e2a071642afe1b348bb373ac33098d48a060d01b3c5955fda375b04314e01d7d3dd2b00df1ba4390e96c1c8ba2d63a5fc9846c001f43f6021e3b9446c7f18c9d1e4eeb0f2f7aa1a11ba446e63b617f786ded0030161d06b8269d337103aee063443efd6737ccbbfd3b803e6171598c5b2d7ca73a0a26aa998b9cd354e58560bf2786941e9b9aeb5d2dc051ea87800aa22f05c4937b590f6a621a11cd2680ea1e9bb1aa13ff748cdf5421ebbb20bf0de35f8c213937232d902197d7ee4ad4e4bc5ddcc3cd6638d389f345b48ff447118f08de7bd9786cb394ae148c7a7f0d830c874d013d568b7ee69332998ef7d814722a2afeb46bf3ec331e074b29fd4eeb75b57ce0d52633b1ee3e2b2b0c44687a0143697f1563290e4d4dffb9a807223f7bea6c4e6328fc31bd4d56ce52a527c024ef45e280c4fe698668ac89a6a7099c5e4ec8f4665b06df2325f7b7a3b0a81c48a2b249e86ea9768689f1a64ebef247873a2f1b9393db96a7e0b4b251ae39b16e56bd42733db6a23605594313a7fb3cce386ca087acab7b4b59d01a34b2811261f6da1bf1b95468c9dc864d029ce3c50d545948e455a1b601a4d89057d9be18659659d1ded2086ba31b9c9fab60d48869ba57d34b9876a97fef29c7eea38a1a7aaf6ca69ec53d67d388fe927a327d95750a7241fea86f6c2b1ba3389b8c58ee1ad4d7fb979ad0149bba27781ad85811ca02228d29545f6edb4162bdc80a34c5b9f022d64d95567a9f7cecb058b68d208a015465ac179c854a90642b805640a27281516eeda1b523b6ce7e70201b4d407a868ec9446a877b564e3f2cd04577fd5f5125fb46c0210a5ab66d0130f15659e11adc8f4779c53f720e224899c1e49b8959b3a0271c21b11b95b4a7cad7c5e44bdbca002bcf8021a9ae14f493f529341a54fd74f9910690f851fbb33c245d3a9d6d9d21a42aec3ddc8d7f43b7d3a0991efb78200e99665e776ebf66c28f4fa3af7f1c6db7494db95a1286e62887e6b919d216fdab6bda82e653be3daf15c55ca2ad1753149b4e361357736873cb5f533d5942a934046b91dcc7c7666a1c0ad5edb17994cdbc7b624ccbc0d477f9eaf4be5ba32191c166888c626a243bdc1f190226874eefa25291b63ebe517274d8fe904f3b36ed07accf89b320ce7b0b2027e36515fcce3beb9057eb177f1a76f4e657794ace520e1b507c142b6bf100c7684cef073115548b474d03006e6affb6e61691c33f5c2c5377867a29530cfb07fd811b136af9d2ebaafa5be3e850f4c89615fcb830a7399bae26e0ba7bc004380b80f235623a343f8f0c221147ee8d4cf9f890ebabbe1459b09e28c1b57e5787ad98b08c4a681f88b765de6d334cdaef9ce6d4aa91a1db28a572ec84a1c71976592fefa03499fc24fa44bc7fb87f44d85fee08a49884f98b1f08d8363abc24141301d0a035e4f7918895cc5fbb5e942a92c85f9b7c9bb9432988c56b1454742d5569336653426b7b04c5a06134eff477c1a27662d3fb5c73cefcadc1f8a8d059978313b62280f5dbddbbf2f312852af895989a8b222d32f151cc9d6010ea24c43a1f960d4e46b1ee792e4af7cb183893988c6dee337e1bb8083e5131c304439ea7c2fe6772680be117fe28859244c8753bedae481f40a76be71241885f12b022154540f232559bcc0d811714635b0983b468269fc296f570b871b6ed5c1edaa7f9bd560e871918f612cdc9c866680b735f5c31ee6732821567fa233c44b172964ac9ae25742e1ab90d08276c4949b29a0e2db6d2c3f88a2684c2fd42407cf883cef91f2e0de921c5c1a46f33ae88260c389c4c4bdc0e33b2bf97150ca1b574548681a721d6af4a3dbc07cad61102b3a408432113d1b4eb864d2aa511062cc575ab462af4e1d89317441acd8fa02be790944a134a533c5808a20deef82d65d4ab75415cbeba541460b22135b1ce2e1485e952aca5f48b76122f28806f2222f1ec80a551d4777386d9940edc39547ad5c4141ac253bd85307069955334510cd8acbcc4770443da1d32f0b9afdd75a636cb4a8c373f82e171d7cefa3a37bc335d05c9a004408eaeca347f72a198c86bf7e1099beac64223a4efb68a2b0c17aa192f10f917cae095d553ee6c42d6ac11dda156c79afeba4232a822dd6dd61c17b7639ae946565278fa4c1be428705b7544711d4cc19e6ac76c8204f1ee696915d398f08576bcdaec257a81f8b75bc0bbd2c574721f7fcd8b70cf29a0941c1897f92921bce4d3b4e72451fd5ac1a84e2ad77cc494504419480df0a3fbc99f06ac9fc4a2f2fb1f947b5941f8b62a40c911ba435b79ae7391b962a13d1130c3f1718bf1efadc7edeb45d24f6d9b24c26ec176b07ae9c29ea2a959a16d6f4841b04f2f2f4ac71bf0e40c2ae307b13e20387b24ddc53a7eafa478b1556c0c0520abe8819f66c066484e688aab1be9baedc05476e83a7cec9dd2e1ff1aa32a6f327b5404838b1fc1fecbdd7a49481a184d45bcbea1fc01f19bb6fa329b8e8701ee63eac22a3bf53f7b9a7bc867fd528349350d0f540ccdf2277a99593250b199c352e7edf52677a6ab0085060e7fb8e9234e8af938cec00c2b684cff955e2ecfe049f180b64560ecb97fb15f4709607399371eaf61f7d149e519361e4237525472ae71cc510937ece9f0fb4ee1b2eb9f33e403a4ea3a940db48da307e4e8e6b189ffcc50add28689ea62f3697966d82402eed47bca26e71bf88779f6f28ba3f43b6e8ade2d7dcc3d0d2311c3b2b487e6f2748c1ee16cfdae4c80f3f28d34fdc09aee4bc277cfc7c8d3b59313a8959e8886a589c78f2706ebc77b77ed5c27be9d0b0150300c9a26372d6c401bbdd701048989af139844be5531fca9da2ed72021897395b9b7a45bbd1ee52af3e5f44d5947b115dd31b7dc5f858c726eb52a07f980e76b14290399cd3d12f87d22a14381db3e1f2eaa51b8a2d2f50b12958a84823658c88308bce71e09483756857b48b00d9e791ae491d7bac632236064e348b1bec516b0b5b4f5759293bd3704609c575793947b97f405d765601150e458084b152a12cdccc4af995a8069d5260830de1b56566d35803fee06df4d06d4617f2a6c244e8e82aa68bd0ad11ec4eccd55b18773c7be802d5b3302b7408396d70931be2fb59e3e84e8090c4554432ceca1e7744f40fcab06a1a796b495ec8f51602bda5bc19d5cdaebe9c8e40ef774b1f40f58fabc9fe639b33a149444125193b94916409e4c9c11b4c0d736bcf2e140a2a7df657fdea490888b934403cdf965cba074856fe85815eaf097f7d4a334f4d1a1ccfeccf4551c6c8d48dd15f28880dd754db78fe5a3f61a7792378a382079b3babfaa1cf70a3127e52c67645ddf201365568d6f120c864d91dd67b9c72895f3eef119fe5a4570cc5f107b8c878c17983ede3801f42331b87115a5f05da021ae5b1c964746e944579a9e81b3f8339f537a5f4d16846f652e8ce41469a76ec0e70a7a2cbd38230c924aafd8c827774ae778d8e44e358efebd9b2097b173dfd3c71cfb88e78652d1d8e1fe96607082d703e3de65ce31f3ed1fcb287d1891942aebde1597a3e5217e89dbab8ebb9eb5f6e81ef0ad1770a34ba57aa072d628b202cf267f9513f77101d3edfa69bab2aa42a3175cd012a8c76bb85eeb42269a139b8910bf3ff8a3ca4744435a4da8224d79b148ed343978389e21619964643d2bea4f361be9e927a81dff0882fd8878aee1eeb6cde8a75c9f04a3fc86a6dac16667741fd86fb091050f56c4cdce6139514fa724b89c9af3ea62d9f00bd0ffc2be3d598a5c1108eab8a021a78140faa5665acf1a9c600546864e0687b000f3b03b93a5ded06f2ea936079b5427085e1e7836171e806392ff0cf0eea089e17f89ed415f7ea3ef44c9c81ab35c5c35e8984c33c4d6f2dcdef1fb8641699e5360fa96379f17da6b0b62a3563c2135677becb814cbcd1e3a262c3d1a12d78c8dac277832b8509e724027e8bcbc9125d9ba35aaa74a677507749961300719943420b907f90f3920c68a8cec04b028513eb1fa24c3eaa0d7f6d3f8e89127e85aa1b3f103b61895a8672a336242b52e11c445be3a0abc4317e59509676a65d400e9724c06c8a0a6737283c63b036f7d5b121acab27f9890abf2c5b0ee58a1c0f8c548bd6c6af227a081dd1b5bf40c14e542fef1b35a20d87a2c97e47e8213f74224235c50bce70a8c5126aad823e35f82bff4050e3505ff48e447f9a6ffd8f0ae7820a9f01138a11e6169f819646e863aeb9dee7fc4869436635747e0382fcc9209486cf89445d13d99a09e20efcad4c9edf419483c4b8f904c3cd8cb0980a2c8c721de5c5b60380837fa274d8b4b08576645fe8af30b8088f6ae55b93eda5f25b1f03859571799832311e8fcc56591d66e0dcd6886f4b21b4b7b9755bef935f0fca76fccd5e74bcd1aaa41ae1db9c5645dbaf5abbdd5d8daf0e1ad676858175ba8c8452b2c38806581fa6fe3e34f58d0b92d3471db206b2a60f8568f565b865ab5fb548a7105ca8e02f8722b387602f9bf541f859bf40d78113097a788f2448a5c120b40b8bebf34cae1fc879a00a32e2b2f7c0394e554274f5fcfa48eaaf3f8279f00f1fcf9c75114512c2895018ce7d1d219efbd2bd0eb81c248989b79c2aa90e71fd5ebb4fe1edbe81ac20dd2845077e48826aad3a44bbbdc98fc29dfc03ba7595a323eb420d7648020a6cab116dc8b5fce8bfe54f5f963bc816d6fe8753aefd40c3a2cea34ceb1fd1d0d65f6656d40373d5cebafee5b46e4daa0bd528c6faccaa15bcc7168538e7e1550c1cd3e7f1e4bd3eaf768e03e90e5e064b3a062921609905f4233da0378375787fdd58325926e84d0491b986205e7ad0431884be98d7411318597f8f2448a5c120b477869e677a0dbdfcce8336e992fe1c41a0425215e2dadd44cff8277af3330f4de290f43f2201b5b42ea25dfb458fe730fac11d076ad048d7bd078e030609a10b66464f021a7d08d6c13fcc7106569b28b9d5fa506c03d263b120fdd8f7811030bf76dd85e3480b2142076656cf0db4fa10cd03b6d78fb5b358a1de4590d29608e2f45f3d8406d6b7342b430171acea696ec81abd55100dabd73333ca5eef5f9724d8bfa83d504b65206bf70ae2723aae663ad1960a49953de1eb65a20f5d5fbedeca51d4c53a461bacb24baba932a59d657f49f8f688d3b7dfee0575f98ab010da5dd34d807b195a54dae3b67787a73f654cb31785215992f71c6f63e733f450294e4333074bed599b9ea9bdd5e2855326ce4c4fe375432a11c82cfbf0c5ce20d6a2ddbf36bdb4ffc5e9658da7e4883e8b4753c5e3187cec231223eaf8787dcdb15e882262e5b195afae6539d508def12fdbc7574af91046db1c1d98731dda307e39c739be99a728df1b41366a3cd3fbf93e42babcf1c7a1e3263e7f8f45c1936135375bde79d057cf73f7741fd43c3d33320bb84ee91567651d47b5e89159e52b42067ab8dc992c3bface234113376fe49179d8c09cacd15a1ebf619a3b5fa8073f41d41ba511f56838d4f45233223d28ad875ed2c4cf0380249db7035fcd2307589817d5571ed6b1794aee0fb06ac4a4f4a6a0c5b5091422a1a3a0a2f95bae7a1fa44b10a8956bf3151811912ca0d8e049a458f957bce4135175cc0befd3d753f64c2dd2a424c6d0ebea817222a8627c1766afa5e9524851a53b0bfba83731c40abf07c1a727079393999e1a820ec8a8879b100086c66cd569f9f01b462e7908da42538fc448c6fd9ff615c86a1e720b92e06fd9d0a8023f506ce01c886204102cb38b6e4195ca51dfa940270cea3ef62a36b5985a20d950456baf319cc0d5b1b8fc5da2acbd662ffae3010ae51a786251cd4f1906b7b10058865922ebcd06f020132308d6415673fa479a2066f2571b5fc875dc37af76d1d36c9e02158646743e2046e07b0a45cdd950dedd98c3570915f75e01afac99637b3ac5f9bf3a3a378aed14a2bbf5e1a554f70c8c32b2ba4585c1261a3ca1885ed63484b8e8581ea863b2441598d72340d65e0d6e717aaf8eaa30b211aca4438c28fe186715655d7097f2394ba2b055a3387ac9093ef5e882c3e50b2bf60eacf7ed86ef67d9aa9a61ec183d55f01ae2a26c29b515c65a8f2805788f2c7c4c4b52db0f445b5d99e55187a73cb966523347aea3b40b4edb9a2db911042d4a3d99489b270dac4ac19090d457ccc4ad5a7236158b49d3332f354ad8bdbf62602e7a276b8edf529b5d6eb01f10e88dc6cda052ae37129085aab6408d9f02133799557fc4478a19fec23c2ea3e24150a793bfe717feb06ff2af9b5493c1042ef90ece614821490184fd74a6bd238a8d5d46c75d012da7e3088b2471663d96ae65563281f581c6d2d04d67e09df80e8740b115c66ee111aedbfc168e1da1779d62abf1c85a66695c8453a047284e96868456b0df340f00b5faa97ef56f3d2c2ae9cd3e9bb752bfe96e412f32d97e976132f657f82e86143ad4d3944720199ecd9b8dde2c4833c2fd97c230d3fba2ccde13242963ff6e3e95bfba52f7df1bb8e30ba7ba1199d177cd49febd8b6bf1462ff1e3b1b179c70a3bc26662531ce33879a82ba1544f640bd50e3e0f72a2828cff5974d75be7f2d818953d97c07ada9d44cac80763f873e9adbeb9c9b28029e7c90caf005324a3b975d64f67da6849c655f172b1784bef80e5ae1c44650323a56bfa32237f1ca58db6b14f6f40291116434601d81013f2ccb738e90d277f2e82bc3138f0d82c3b3b4fbb4d80f1f1bac663f042905b92c13013c65fcfb9fe04e2e7a606a15775c88b543950af428e13e3c9392356ae9caa97bf77fc15122773de0ef66f492e5ca15c50ed8c3d06cc6055018bc2193362d6fbdd595e5e77768a4e012f63d9797561687931130695ad4ad75215fef4c6076921641624591a6d742ad328da281ee49baef3f046962328a40c7d86c7fe65af2c49f9a9561906de66bb24247442a87d774c9147c77efe11ea9bf3d5b2512a4923026e766d438ec317d29b9d0c45222440214dcfe9c20225aaad50801aa8701877fd9072bb440b5f28f0e06473b27b6eb6a073a7dd6e00e881c22585a01c95908cae051428c44c4cc91dfac6696de9982d4c56757b4c419995953098e55f6034d585895c0c86bc583303629b66c6f4430c64d100629beac6bcd658de0b56fa372d47d26b5d7ffd829e3b0d44ce68f70daef4ba4160580ec5062894522e7a3b19cbce29cd702db65e906bb287072321348f088291253444e0a4b25f5c011bcf4a1b194d24b324033d94fe0836b24a944786b0f1c09eaec21af4268a1c33fb11188c420e92dd0eb491d81f52e3dbf620c959c79a2df0f6a074cc376392733610fe21c798b05e448967542f228b510027b9dcaf074de244dd19874065c44a494ceb0da3925bb30fe7981d8151b7fbca099b8e9898d8c4461947241395911e267a205811b8009f94816920b40127e9fc9804ebd3538512fd1b118d968b681f9cc3c333b32aa29063823702b35a906b591c66016f21c242828333e06bc3c3e858cab00cebbac1114a3a4132245e5a6e42504f77c101f0928990854c105672062ae70befe0dbd77b505c4613245a0d7e2e6971d0ddff62ed3cf293e3197e48c8fe2ccbb3ecc9bf1d906d92012825cedf5cb52c9bedab099a4a2eb9bb4926e9e6c619008ffb242de727f8d46c3198593ebdd1c03a69d94bc0907843d97fd6b5421f00b36cc3e51eb540d8df4288b9cd2ac991e3486fe61560fbe148d28f59ff1e71dc1d37fee069e8a453ca42e7bf0f5a55fd4c95887e0f3102c898c8357a3781f81aa143aeacfb1ec2386e6a036647c0594193ac326018951f8861361886f2d27044f22762898b8fd46a799190155e097bfd0cac81a288ffff94d949761033b30995068ee705849d5256f9bf0c00fe3c131e376a66863a64e383cf782c4330cc3af4bedfe0c9ec5f14d0eba1f66d64243840534befbac00b805f7fff1b3edd52e26c99b506949697c2827a02acd55eefe33be88e2e4485ea0b12bf84a0ae2e7c8983aecdc443cfecf62f4c0a4ac3dcf22b52a0525a28ee1bfd06033a14b2fd830a5f8858ab5961f16e0eb1c75b5b55d28fd9082ea2ed2a3311c3aa7ad51d042595c14bf006343f909c1dc9f92735ff92eb44090014d184c554473f33b17aacaa482c6152d80bf451586ab0fcf075223f505f796930dea8319d19e46fafee4c85d4964e5f7dbbc440b015243ab93d3f0e6274842446d2ab0095a4680a8e0a7881166b3f6e53c3183d34e94935c4d31254cb0c12cb022d5a44f9e7c881eed92184ff858a0e170c52dc9f877dcf8bf0c667ed87ea82413920d2993cd02a88b7520950e1d5d193328ab3b2fe6c560e579a3c3933338de880a566a290e43c0eb7352e071ee5df447a38f05fc1e025059916696b2c0ce5cc8287669776ce486cd74d5e333cc1692e7072a35a4a7ef8d92db80eec6c59b28e55a3ff9bb029f235789a58eab6e078764ad06d0a254e863f761120a7684c3e74a1d84796e0c0d68ab36f32dcc1580424a07a4156ab686c95eee2a02bd3d4c50c307cd2e489992661b4666329f6035b03241f93a942b7ecc626bf6e9623d56b76ae99061c80cf006c051e637c2b0736891a51d2ec279ec5e5b92b440deab189a17084c841744961994cb8855777e8364a622b1b5ffac8b555ddccb7643ca0961bc7709554b258522d2d346f826e61e8980af9f489f920c8e106b50918e04d51de0aaac0b0e99189b6d336336f408c4d99a9e96d3621c1981e21b54d0ba01a6b7ba14c6606cc20b4c59c1d9817c8d98a23145fc9583483f9e714b755858c91c86746973ca7f811c95d11e1569a18ac14dcfc5f07119bb2a939918fbbca92404ee98e65946ac593d30748cefaece480b7cf3c1331c1ac87d691d66a6d65946d6e1290ae8c0a604d26597bd45734b905d333ad58935b0626d3924493e18c6c7ae947510da9d2897d268ba6b607cfdbd1346464ef24b39de32b32b1cb3f76c69ed25fa1e2751f105e355be5a6a2a8193e0eca9e9f9af24b186f5d0e1a1424d32198f8b19caa986e800ac59d7e6200deaed146a22ce2ea171079a24e543c793678eba5fdab5fb15c7f65417c60ef05b59d1c38fea9719624980ea3d321438caa18f3572c70a9753142ace4eb4a665faa8c3fd9d7f516727954f1577094000e5040a10c008afe1ebe147a5172c7ed129aa1334e6caa3dd822c6c3a2b22949552c05b28bf72b6133e88a5d1d4b140aecd8ac652dc24b2b5beb757a69596b556ae58c02ce5a6586afbbd044d3858a9516af303b0f34185e83a5abefbfe5f46f19b27f2fc7c763e4e4c5779e9356ee5c01ca033d490c8ac4cd1a2a74728d57d26e0ac30c07bbd8dcd45f4510243361cd7c0074731eab40a6971eb62df6fac81806d1ccb20a44b4493d790bcd9a3096f204614156813e86eee0f14e6e7752de3c96ee7676aefe1dacd535cf26923f451c63ab6b54bc9149689a3b4650d7dedb95f30b87799336754ba243f3ed590eead02597618b8a1ce5649e3849f1f28726936ea9ddbc0f3dadea95698a0b161f9ebda069a69614569ca7840eb4bca0ab54d1cf8f2a3ff461e8f5f3b9d87237623d8317ea8e0136b45cc03125f405265180caaf2d51568688b7500d72bd85382986205b0110ea61d5a4060464c188988ee57871c8e1e340a27e1776baf2f38a304b42c7fad071663bb91b1b77339400f7c247d938b8c58b94a600ac9b0258b1f49b9c83e71831df93f9b8dc03172e4635ab3aef8269f73f370a2f9dbd41c8af81f48befcdb0b955e6f8024505fa076480692280ea2bc93822247d72d8db1062580e4571320fe083197d1c7f716fff070638e83489b36456be9f6f5069c243a680fb282386032b6e25e756a9377a5d1bc903e497bec8e5befad52ffaad3c959213b490bc3f63ad2e4f2d1368b7905ab40dbfe52906c19e28328103e6e81e3775ab4b56ce80a197a4a2905ff98c518577865262d42a2dd6323bce2c1df615dee0064c0d4eb9dc156fa64ea80e179374cb8e0281883076952bf63239d64638d90800ffb491ec95c2fb06f209d69664bbf10f0ab4bf04d4a6ce887bf19092ea06565b18b48e814ff13470957dd9617c50cf02997438596c52a15579c0916557e9257074cfeac8882ff41616b5b6e3d3ee810ab1903eaf53bbb3c059a7303a55e5e3dc2b6f75f8b5d572af63eed9dfee976cfce3a1586eff5b7cf6a58eddc210802d72899dac984a101789247eaa77df4483b473c0db707991771023a64df2a7d8cbed454a2d7eb0acfdac84d6696d0564b313984c69b4d569789a0fd7a74a34158440e6db33806150afe5e28171aec000e8c2d3feafa08ced04fdb96b29222cfde67833c85f6f4340a407fb4a12d4ccc8117b59622a21ae123571b43a3897533edd8f53ea3bd483177c2d4f216b076fa6d2a02197a52a53165ec1ad169858dd710261958e72a8b5a0116ac5237e88f8fc957ed2da1cdbf4d408de6938e84046fb79433b6c38eece5d20996fa9753a0a571d21aec22f2de2b5058c2b77585cac9110169f12175259681bb90755e2486fc343f839798e170b1f6e8405d87ee254b476e47a24701b3d1a2829823e646fd383122dbdace71b26776bca7040e6f3d3bea45dd427d004a0729cc98b1623c66d1e5841bc52411d7ac205ed29a2ff0d21b1e2fe3148a4d5f437c0348bb9f7497fb6e86f54cf8da789919d46a8e3fad4ac54f3c954c0097abff02f222404c2bc7dce5949a8dd44f287cc371606536a2c78b5215201c00bac18f0cff5adf8835287f39c6b8fd69937f18df13adb43d0d004d9f22bcd526df1b9680a9cf2e4dc10d1cad07e6b383d95becec2dc790a36f2281ad47a5df64827e67e96b1131628a2c9d74ec1f6abc7e5ee569eacbf49359f2e9697ee86fb4fd096403e84c9122f497196237e32e22399d3bbbc03887d47e7408cd2dec39bb1e48e5fb50aa0069ab11e1b52c1607192ca48ebcc06d9f1dc33f6c093389d5f2424fb9464061ae7351e6058dd9b7e3ad3512a94c25f4b8a61494817fc510e9be5d5942551cc56e5a38070ff14fa4ac27dece612dd1c293e0e447cffdaf18d47f0696434204f990e8144b653513b110be33c633bd676062d10d9512865313bb71d6f2c424698707f1ca2aea956b177774829fe909e8237d1c12c97889e48559f98295133a3c647f96a050dcca9100fb32e920efa603c310e7013c72810e51c9b8c34c865039226afa8ad6e10a2d518ea2e64dbdee70f1697345cc233cd68887f47ba1cc46547d408878a2eabd01a64b7419096d25cac652d33226446ed5daff738d8a0b63919d257310619afe81cbe148c9fdc4991b0c41c26691b800633f251ce8a6767efc3eaac9c8f115fc17cb83f9a99e2cc836cd864386cec4423252e5976f70762677d38b936f8a0840110f3b49eaa625f26ad725df6ffbd58bb2d67fdcbdb88fa0e2592fadc4964295cc82aad0654de8a38d41654ac51f6c6752842a77328aab54c926f14a9247d7f61ea0d99084888253f570912436321714664bf874c2a764200c0c9fb4303815bd2044a9512e604b8137420274e1a7d6501afeba0fe58d8048b1a1ea6c258124226278e457a60d266ef13a14bd0cfa142cf481612c78a3d9197b0c394efc9c4845b5c892e01072a73eaabb3c3c99895dd23af27d07104e2b54e5e6d48fb51d3f35580b0aa8bf12ffd3a639ff75989512048d747dcade05efaf24c7ca9b43c6e8e9d59cd0fa2db57a8f92d730a37ceca0ec253db1125475fa38930f9a2de718ec2e0f1a9b6db7fb0d307ee92215ed9b1fe7aee2e8a1af43e629e02a9bcd4cab427c66e432a0f10d599e9404b0f770fad11691b280a77e616621002ac8250f93e6cd80bf0e818e8b098aa3edbc5b1845360566082d52f12267c30e654f6edbd84a4a7fb83e939a3d6b7b5c0b23bb51535ced0a44f5314b17d926a5d2e750553050b6bf4481ff0781117ccc6036b508dedc5f52bbb0aa43d87b33e8648a9c83bd87bf4c51d59ee4b9a891da84629dcc6d9a42c43ac2ff01f55da9df990126a6a115a3f520d875f03abdf653958327ee111f2a657c02a292ff02b75be40b3d1c4660aba3a4f93aa626b207b079db769de72e783c148e2aa81d340de82839e53cc852bdd4485d9055449038f2e2a824cfa48d742b4e605206ddf82f6b74cd2f3029bc30112f21da2e89c36951a8daedad9d0dd0b0b64784ad0666c166a207b2f1ced6af598ac93ffc4c9aa54967468bdea157ceb069cdb546e714148c1d7b3d002f446cdbd5bb38ad833d3a196fe4e6c2b5366f7d0e3d81f1d409cf9b9fafae0ba86d670ea85c6a08afaf6b3190e22a3e0db590abc2dff02d84224f2704dc22e81ce900affef86ec842d101ab9e348ca434d0dab5d5567e71f11d21fa8e7160544bd7f9e32d53774eb6880fb5716247e1baec052909e487749d3e639c2f9c7311640ef6d2eaab4e3aadedcfdc4c0d3581fc1ecb5c2bf7934ba57e58c866c2ed31bc8727252b6371ac2f0024f5e69a25b4fac3ec9c3a4b6bacde6b6a77575ef4fcacb133a57856aefca5ed4a447039a593b59855871635f1e6d6e57acf6d8cdd12375ee4710cc0c6a1968a3633c449110125957d0dbee88054aba19b61cc727d36b3753a94fb63b978f16231843629c6d3d8daff2a723968cc2580745348ac45d9d1927e9692ac4c5d8e8efc86a7c56a5cc122a67a7b3ac5f8ef485a228d4cc46736dc9770ad7f68ce8dc3957394fab94b6cf294dacb0773ab34ec2b4befb91085b5268b6a89fe991d8dc975c5076c104a7ceec1f3e541e43690f868f28be19a4eb249ff2159c90ead4f3a7c3689a9f0c430388a63638009735007f3321b76a308ce378667794d6552e4f1464a7c2b3fbd8f0155a5c49f04aded81b407c57da002f9d74a9849cfc0fa7542c77a3c0b90cf694e5159965806a27d7f0d486f7f4cff7ae420b8558405dbd494a219cf077dadf337c559ec828fc47d02663be54c18368e04333b63c4383b01441492ce7c8081d6439b2138381a74bbae4f40c6353487af8879da9d15e02b053ba9193c33c31067790850362521f26fef936b4a1c25e6278b44db390761befec2769775b809dc54d598fdb59de117d02586f0423b0f3d45093d0b69052dbdd143bca4897a4c6d4af65936f95097532d9b1513fbda5a1fbc24719ea75c6bc4181d395dac5258b0851f47cf5eb93aa9a54183965488dc0c6a9d45bd8246f37a582fa252945a579542141875121891b7239b8c5e1c7f667d9605e6d6f007a86bd33d22dd818aa9d5e7579b9e7cf734dfd62d5e5af12f4096f8a384824f3f608ea1e7c0a2bc302cc488ae1b35e183994dfcd4b62147bebe5d7404fa0566d62084750a85da3a6a8f222155dfa09531457487054706e8b565194ab84d1b8aa87298a2bd26726442a510322bcd2ca0a013b98732faae656dea021639629a92e2e6824c8c09851a8900331c21cd70f82990785bb2c9ec219573298f510b895c15b05f4b681c2261b9ad94e90e37fd1a737e18ad22a92971e6cc93a1084cf2155914fcbdec0ec8a5a5986f5a70cc3dcf4ec4d621fb9c4f6cd71bea04798f0f3fc6b17bafdbecc4e1ec3b7c24618420f3409017ac7e02d505010b1ea1256a9f27c23cc1713d4859d2c95a6a18228b60ffa1875915125245422078f1a3188810300000e59882c3bd9410c201818001b80d92e6e4811fdc7d9843e33c3ffdd04edd62ada96494ad6ee0203c702b7024ebfad9bf29f31294a245b2bd2f04b894d2fbfde905ac652daa8b6f43287a0137f7e4b3f2950e90a2174d8f3c81917ab2a119723e858950e79a196a868482dfc624efed563c2e99270ca184ba7343a1b7df39e926c246dfb6061125427c630ac855d6b10a43e0d913b22a5ba3422a2ba31ad142829dabca624a5bfa3338d0e2eb8792d255c30962fd81c94d9b2170643779c5f6a92b389697eb7caae6609e677ab4c66068200473c705803658e0f202cff3b03391eee1c34043b5ee2e6c001870a6e0876f0ae6007b7ce413b1a64601184602c147f0065e22c000d37e010aca0770e38168a4930e104165c88a10c66de6a3039592333f09edf2c113ca190e382a00240d983293ece2f35ba9cf5fce13177fe787f7350847c3ceafe7b73e8d64f66eb2b8bf1d5c5e27bda43d24b91e7836773c7b483a2d4414360e8d171e75e70cfe4ac5b2d6d54761a370407f486e316f3e2a9d2f61461e35ba373f1d6224d4d2c8d8a3675b6645654f624d3c1ce8c05b15c8348c46284d559e3c05a7c8d797175557135558da8d12da7d6892c2013584025577e5677555aaab5293229afa82b28a7a7232721163d4d4da697a59f13a51f90695e921c52db91d90a30a32c29545112918aa11f15372960847c5250f82ca37bd0b9a75b101a50d8cf968f548f120f91336827474726a76705a767e4a66b03a30957a376e24a539da192618a91c240993b2f342e3c2d26784ab4f4b1d8ad64555ca46051ba4eac4c5894ac2881828467a446c4e9b2749a0043fe8020787f5b3fb6beb2185f5d2cbea73d24bd14793e7836774cbb1d94ba9d10187a74dcb917dc3339eb564b1b959dc60dc101bde1b8c5bc5069d31111b275d7ea5c645baca995a581a1699d49992d9549c9a2589eb1e69529969312962362ed82d57d65bdd6bacab8c0aa5a54a9ad252d6956148be7aa69c5acc249a1c21199ea4ad54565a16ed69eca3c00eb408b03a90d9634906610c580e782a605cc0a6e5228b811f1e84e5027413682b5641904600f683920d5c01203d205a20af07434136022609372808d08b24ba26e4496634d2e3300ac00391dde9f6531241a8f71ac63c6ba3ea4908dab81b9fa915b53f62bca90d254ec2f6e6fa2e210d9935d71dfbd677f861ef24e3e9c85d11026b910fe3ffa0b6620dfde50d9900df8851b7e0bd13949964812638f74df5c0d7e0d33f74dd7b421800f4f9990d10a924f9af4e5a5d9d6e4a2f744d90b8466ce850d659cf0dce9109d60c148e1c7c6cb8cebd2f6dc7ccbd8bbee9fa4ee7fa762efbdf7dd7bcfe6bf8b8a74327d13e54decbdf7be39e7ccde3b726f627f1b104164872c6f1e570200d028bc00ae88ecbd77993b7f24812a5d1e64d38248f6a66d692f6c373d59ac3aa8971f0c49634c3532cf2bd7e59ae7471d9979be04e10003456af982bae29928a145a56e2b0356e50216b79125cc6b6f19de9befbdbd366c3c86985f286bd2fc61dc435a6bad753145a8438933e0d396e1bdb5b5a5cbb066e932ac5b56302b4a4aa36273539bc1f935d524d386d4e501c9a39aaad9896996a1109ba9d79489f6a457e69c5130cb25d00af065a3c1cab37bb2911a5595107d027a2b31dbd306b58a0a4b51bf69d71494910c56e3860e0958e99fc3793cbf54375afc2844314415084cb9c9deec4160c35b5f3509c51dd50c0a7a23475db967d44ca3e0648576c714949e5331aa8b1d2d0b7aba5b46512a58bd7e8213d4e1288344d414c618cb528b90013b3f4e45324a2f725d9b94c55438024861c15935ff60a46e4a6ef647bd69c144e131c270eb4f75fe30bf54384f4fa54b76b4283b7e756d2a1358e159e55c3c99a02a0102388513a7b4b7e536a7cc8be3d22eb1285a9210a02346f7a56442c7454df96e2e756e2a7778336d313ef2f313b718f8bf8dff5c84f1eac4053f1a8d47213026acf9d79b7e75e02f897f7eb374f8923280bb73a0a1cc871d76f9bd9fc7b0f0680caeccffc6dfc2f98d5a1b993f62b98ef7cf182aeef76603b9615c11a268fdb19bf919941e018136c662c7d2051663a67175c6dd8959228bda5a04eeeb71101133190bd117a8a4164f4a8dc482450cbb1c961378c31a4e69d65a6b3ebf54b8a1302e96ad9d5815e10c58327935cd12698cf28fa27156512dac4f9ff2355709f8fffffff93e0ee70f6412b8f42fdf7befbdf7de7befbdf7de7bac2b28086fb8f31dafe8105776b9a2ca646be1c44e864a49ec79c49f0bd3759c3cba675347cd18147c45e673ac3bb583d5c705f91c2107cefcf0acb2b621511188d9ddb4700a2223d272f978d438644d0d1ae7089b8ab4aa2923a5bef253790103098331b6f50dd989c671c9c595d796e692a7d44ccca31f67cbb2476e2aa845c0f2f1a86db0791a5d33412972d7b213680e51a612dbe89dd19bde5090788c61b8d9a627b8a2e3f6a14c7547949ac78a9975c327d226d70cea1a2c9886062a35356279ce209b1c962d7326542fa86748ec7416ce1fc8e486091048024bf30f7c639b8bf30bf576dd6a9d26e48abfb03e63fe88f7dd735838e40102e331163a7de03ee7337c4067ae99e1403cc7841b301ee3786ffe9bcf990c1940c61db20c131139ffcded161780b71d147861ce7531ec888fdf3b172b4c60c72f7c03f3deb94504f0f48929b3fe8213845944476436a5ec2a28f698712b2fa24d1e0ed03449b0520ba4aa62cec7aaf4d6d138abdec4b478cab8d00da42ce4cf95319240267b72f3773fe7a210476c187e61350fbf375dca90efa4cb70aff07ffefb6fc861bcbaf4be9359a3dc79f2bff7de7b5733a0f7de7bb7120b78ac210146f918c7724f4211da3f8d0164ce8d0570dbe87b874076fdf06ecb38f62cb9bd877f6bcb3202886361d710845108effd5a280f00cc3909b54645e3cb1fd606113776f285e1de7b9fd970437c861aee75fa35356916ad967948d51e69989cb4a8618c8f317357bd6b55e20f2fbcff2e80d8adffbb9f67df8a1e36fcc223bdf7fe7b67e1adecaca9b89d0ddbe890c52216b5cc63638633543d4f27b36bdab5e7616db037644d673d7a6cefa44787e63cc82f482687c8d75cf3cd390f0639e71d5afe90f550cbf0731ee49c73be7be79c73ce797fce39f7e07232e9616d46c0a5b5a65a5f5463acacf4500856bf13b71c9e99124acde7567d0a698f56b8bdd65a6b9da662e6d5280f589ea2e50a59080c1907266a5f6a735d203b25a75a575491ac9e3fcc7bdf2a22f04c61261cf3e182c25449d1f69428aa9c5c9914286a84a42fd32daa90c896b213395d5567965821506201415843028cf2318e85227e77c88f620b6584981ad9614a3bab96297b090acd1bcdd8092668fc61c32fbcff2e80b8ef1db6d18158dc7ec3d751e9e3c55c2392b9a5d55349083080c4ae7835d41328707ba207547d6952a167ad623bcadae1e316871db2ccf573bb03c0338617141765446c84951e6bcb3364684aaa5b0dab31c5fe9c7775101780114ebe27ad6a9559a4ec4d13542cd2de98eada3a8860ef90e57d8c6361af22f52e17f4496e2150f3ad549cc1b74943e3566808ff6f6fe0dfffff7aeb036fde019cf9d7235cf816a5ef98ba1dd1c6adb5d6da18f0d623323234ef6a0f945fb8c330ea5877a07d41d48fbcbec6ef7cc75b66f1ce90c88350defdc4ffffe6da1e210ca415dad41ee61b1c5b5ab44984164f31402d6a6270863f549ce7eb32cdc459c550bed3848b8a1855654bf9bf5f6b9b1f0f87237887b525199f845f18de5dfbffcfb7eef2631c6f2df3adb7c80f829c23f901d40bfe6bcd77168df8bfafd9b8ff7fad0ec11dc32fbcfdffffffffffffff7f64872c0f7de6ef56940f3574cdb65d2824cdedb6dbb2f71563e9c7e2911d1222b1ffffcfe5a407566786e797ba953400d2f5ab4370c39df3e725573912522792902f39229cbb144a569a8ab854dcad5cb53af2aac6a47b3696a5c4c4f645cc48f911f7cc5a08ab0648850e4dc5ee2f7fcd336b38a459579d4311a2ee028bd4a8548bf86846004a77531a0c2010876120c791467c2d1400091cd24468a848242858c024108a0281502800060241a1202008832014072118c3642886911300c93dbe43781e1e053d0e85d1b25e4320e9ce459f94650dd4f841267ac7151c6b7e9785dd455fd8e572c62d03260f3a97a8789ee8a29a4e8d6276dde80b2a83090644eb711aa69a553c32dfbacee705e4529bf99af360a2910fe420f4bb0cc8b5293a685efefaded1e37131d566f69a9d815e8ee8c15247138d46a641527afe90143b636b80e2abfc0eaad649e3697514d306061991c133216489488b6cf3e306527bb1ec4eae59c0b7dedc2064bf5c4a0689fc4402553ae4c3c2265767ad8520f99f5e107096d49effd8d8e70d129ee3e6979636326d126ee3e68643a630e12e77dd7d6bd300579f426e4528364407c8b00a406cc5f3ff2c422acd45771d006bde5d1ad7464cb291ae7ae99bb0efd1a7ce03ed654a44e1cf45e0a066b036da4b7c8f5bc49f7f63608217ccd05506a02b5a27872d27c7bfc96cbabd1e23f30ba41384cb781d0c69a91b8e82db6407848ba0875270822ce3967aa0a445a7c8e3c4b5ceb0861ee2e26d358b1204b514b1b7a3da8e35b52b0b374fc91319f5340f5d42e0123362bd577afeae9ce5070a7ad3603a9b391b4cf9fd0de4cd457819c794fde36d55681b63e0cef12eac85b4c7185bed8d37e5ea22a88f70baf186ee721941f4d686123a86a51e7df0044bd2ca377a5283a9656a89ba784b424c8028fbdbeb54bc9b0c234b132a3a8b8ce3423a69cf3a7610c2dbdb9200772f99787bbe9ee4e858f3d00e05cf1a73bba3d3f94f9787f89f912348253abcdf185f7b209835aa76b69a47c937372abc8ba4d90acae2e5993d024af5e3420cc814ec0ae0d77a326b0ad10d88cce3e00ab917ac96d7385d06ef2312145a4674ef80e75e6aee37229d1cf02fa9470940f036ea8c490ff82ef0b703acc05baac221e4e28c4087bb363aae7a6ac0db71828350d0c0fa70488e5bfdce00de82b292843491a1df2d12a71a13f99014420f2ab36e199d90b59be39a352de680be9b52a83af0c0eae73353943e4b967fe40aa2ec71fd335250ae9fdc4b9323be71427db7811267aa0b1ffa657bad4e1849b845700d198fb2120479607f82941020c4fbf753a2be5b35be60363665f8b743db239725556a57ba6baf8daeb7a877534b1535126da195733dd1b3bac08251f36358cf11efb6929931049eab5b786ba38fd316d0627a3d766208bcfbfb6fb1fc6328e30765a7c74412026dd8ddf48aaf7fc0508282e4a576d1dd7069fca472c6c190f37cff85bc549e8382dc4d3af9471523ab207f6e7b514d3e51f41f868e197d5564bdd00424020945670eb94d215f03d49eb54067abc53330f5c768d49c8265ec9be4b60ba84d53202ea1a501fa6ea03ac0a057b5a8373b7e7efa58ef02aa0d99ed802867c8b643ab8470638ccbd9d3cfb076a99fa18cda75c5aa0d9266718e60efe9459d2e00527911175c14deac969108f5c8dfe1f3833cc3c413efd4571c1a890af80ab7a8e3e3eb6dadc731f1af18b9e97bfb6966877d71bf20d5ce6e5f2e209e1a2aebe179f3ef24778e086d1ab85d0d0f86123d02b3f4a6ea3308b7e1402c4a59523f33c878f29da77926a7b0ce60e7c63ecf4238039977b7a44cc9c7416ec219d8d6844e01ce8fa01ec250be3fcffe8a70e033dc28ef3642c6083e0da7a64948f6d513c1e516fa2cdc13638c0b582e11e0a52c02c2f4db8841a5390ee073678521097d0035a9660dc7a86762768bc3610fe03669e7ceda2fdf97de0223f5f80491634132c92e4b0c5424c301d8d812ace758c11b58d1dda9fc89a4856e60b10001e1989628b9c5499feeeccb2c1668ff5cc4077ccec82abf18cd21f14de662c291fd147f17acfa0453d1b878fe01ed8b65483ec5cb03e8953cdfc6744fb81e027e87189ab38cb1311f11c29adc206b40d55f8292b7618dfae2d82f2f412bf717b1196502f345066ca1cc7ed3643a477e20ff8437c629bb1ae82963d221855de47da5209ba27d07707af73d1b94991d39a088913e43522b8d91d5ab476d68ca8ff355d74e8ba399ba5ff9bade98f5e00194918bfda47740ce97cba785dc42c0513d540df89131740d2c231f969091c386b9c51f1b8cb6299f23e02dbf2037942e983e224d2aaa2ccf29dc546603a9fe39336f834fa384d291ac287830b36a0a4641c372151cb9f5f02db3aa03db0cec5d1fddffb93f93ffed193ce810170884461be134d44092791e0382fa2bc105f27816aabadf600266e77f7134006dc53d428a9835f291f0fce706d8571eeb7c5b8c8fe1378d47de1a17d59fffdec08565468c0870930da09ce0b73356dbfd679eb288f0b98aa823c123f44d5ce51716e28195b4e625b08a81491310ff82f99a6ea60753e1cf87733bba9b4162221f35d0e071f5cc5f8c4a258a332c53f8d8e7cbb8723e3cd309b09949e309878c10360a0e4631bc913b3c875338f5ae3fc1447baddd736f442226b82802f953911f7ff9751588dbe71eac5a8e333c02b9697e917cc76bec42ba7050a76a592637d615d537886c046c594c77f94cf313acb2a0a25f25b6e850f04a21de8b35285b2b1bc6f3b6ec61607b957a5d09970c9e3f57892720793a85bbfce84b036449e80044218deb007a828f460807a5ac29963cec9f300047bba1caca4c4fb854aef34de4e0e60cda9c1dde63f52b891bdc72cd08f4d21d234e8f84cbc3d7249784f8f5e8ffd0a2e9ca986d81da00473e5d528a2f6070cd502a91769a9423294d6ff2fdbc6d8e20a8dd997acb299dc7787d3016b4b0288dbae585d5a2c5c6987dc54a51813cefa188dae14134ff9426306d4782cb6c3ae9b6235a031d2f9ee377948184fac01a0f6c56cd6a738779d0ca675834c16075995d5c1367431527a931a226b8a5ab86deb844bb74b19974a34085c370dc5437e43b9c0f257b7048e0ba2b78a78b81be953befe49ce245f5b0d473d993c2707202af3b763b5e3334f050ed9c4f2f59f7cac726c4083110c2d89d08a83ecf35b69dc228e94e019656e5295baa4848e43df2e2fe889a602b91f2d2885c339e72b910f0bf1801550b7091260ea7579b19898fc09fd179ee493a4e202d35fe9ae4a1e6f11d2b5a9a8d53b026a0d654256fbec6d8c0784a7f4074ec853aad788711c2d0f64cb85d1d67487016fcdea605051462b88d33798e07eb4f87ef82ca30ee810954fe16e4fde9be2e1867b90deb9516e4f07befb0c0dabbfbd0ce453340c7dc2c18cff534680f98efe73fb8bb8fd3dce88076b5de6ec0dcd86e80fd899e0d1a5e0527b79110dace64b924b3654f52e361194b142fbc4f24f958316cc3aac13feb68a24a03f4a90a0dc01ad60f1ed7037b90ec87146480ce4db7e3e25f18cc35285eb2c1f8ac8e6182e552bd19cd4bde64806220ee188ae4f9d1380d023f0f83cccd179394c61281bd04339eccb343d712bef4e65b48504b92885748b38739145dadf50886f6da5ccf6b8818b48fc685025d49df31b027ad9a7b1e8b601620bb782a01eca1f2aa63ffc49cc2dc00c53f6222a00a711836271448c12afcd0cc331edab35c74c46e5a58658aebd231e8c573800eb83705feb3e3fd1877f0297f42cac96c650abe7f257d805db3267229705c4bb3bd45d43e6f1a5c222c4fa402bb3cd7e26c59271df129f903c1150cad9b419cf1a4e011e1e9982748f709411cf3dfb221b24f3e2131fbb84873d27fd9338e8a04479787332117891d45f5f0dc12a72c5e417d235af4f6a52671ba06a81dd145d725295804f919c06fcb515500b396c9b8313813dce945ec1b2d130153775dce6a3b4a88321e3d6101f12a4d1d54521b9454fe7e2e02c08f159bcdc77a20c67a8284b3c8c201ce4702d991d9957c4bcaec0237fdef0b97777d112bdb2e186bc1437b17d2e5b1101183641f1287f2aa517416aa08f8bc596fc3b4f61b93409c5da7e9a184b98290d0b6c247a5e2edb6ca4e402ce5353b46df270601d1110edfaa32e6a26e60725193550ae36a2404245abdfe3ccde5c69049a0307e272cc5a085fbdbc48642d8eec7826c8e29a39ed3d255f1f667d3aede7fa14d3d2665cd2d7fcf12bdf9c1c654baa59b72d584d2e72d29208ed43c92ed8aaa9b82fbc2d009e2de9a944c779022084abf17c084266b6f552933804088c82944ff93c3a8954352e4a3718ea17c642bdf6e76cc0e243469fef1de01a4bf6da0c8fcfa5c9a92aa52df882fa856c1dec1e5ebcb1878e92724923c01ff23514cecd41e5ad592cd344ab569e6cf65356e2a9dc7eee35485a0940ff688672202bf79cb3abca67ce1a56e2681920b8b414f0872adc25b967dd606140873447485860e52291c04ea7fe851e28997d2b6889c9a20d608fea304e2e265c3db36794d1eaad8384aa1a770c3401c205c0019ab9e14edb6cb3f2171f6fff0e8d080f2cd82b95a66606b982caa386dde09e37f8910c58cfc159cf795ac55de7106bb3445569c71201bb9ee85840569879dc87a67e04b17e809f8b467f811d9f624ade8aa929eaf27a5cf714d983d0a6c192008c32dbcb3a1217c1740842de3ef9622adcf1e24af8865713681a4277d6d24b37bddb217674dceb1a76579962511168a5a6a4d32308b2e5b8fe55991c7f3ece9344d98c484fef01b5052dafd7e67d9fdd8c08c02fcbe9320fc3128b517e7cc00ba52fd96c995ed3ecb9e82d225ae4311bdca6cbb93f7de932cbcf654dbf6c06473d6b26cc0be0b90696ad013eaf8250b707389ec7fb9222b13a0d8a15e0ee65c6ba9f2b463d0931832c2eaed36820c98841a402cb74258119bab38b690c4646567ecc34834850e0c369106d20ec16e6db0fa040ed44f9b34d555a42431983f2cee6c9a8254b9601902774dc37fa5a622f5dbdf0e57278a9487c884ee63fc8b8d292a92125b81f8c092ea46395f3ee56e7c94201bc8d426a4aca2a4c90a5cdd39748a627dbb8a453008c75583224fa51b7f2b0ca638611076ce0633646636b6d7cd058075331b64c2d675b7e2c298ff754c0417361b89f0e84ef83d7c3225a362f9afee0e2aa697853d53f89876b842fa6021446430e8d79046fcffd8321c4aa41a920db85e31104ef8143848f640d282838bb6f6f3e2268c18d7ec9e298c5ed54bd9d1735cb3b81e446775aa9ccaaf2bf1e680fc77b74429c224f23d6e4c22c325a52a4dd9591f44c7a77944df6e91f61dbdb9a2217c6e6d6ab1dc6cec96e809b1c5100a230e2a31f56939403a377ceaf3551c75ac6e515a79ba15f11fffe737691299e8ba6f76b97fc3d390b862aa4651a4c0592ec5a5a99515c400714b6c9169b8798c4cc5730859da55e91a99506d9d830a3d3101b999c329500f818aa41cf25c253d1f1d0e33b7cec3a9245eec106bc99a14ef7c72eeb1a441a0dbda2be47b768ec28e4fc8aff03ca7157235cd1181121542aba49bd4a7e25b7f1ab510e8284c91ace23164972889a1267021214c43e0251b8d3d9120486fb37d7e59e1cfacc88de7cb5d60ebbd9696bf80093c39755c6dce39cdfb201de0fa1b22999593bedfc4b4351a2f6d9ccc053f9b001325ee25b261225f1714aff534d10519758d905d009807a22301bb39f5b2a95a3e94474799705cce5b5463e4b2343762f6477993c847b3c5f196d1dd1b3aae09826d2cace847afe8cbb11f03ce78609ce10a1905f28db48cd539cd2139da932d03fde565ecbc4921c8e9fddfa6deedd94a20afcd437b82633f54cc802d43c4eff00728a202c99193913492e9e3cbb193d43a2407a56ae5cc6f362f0c578ffc6ef696012c307344edafdffee8516dce99a14973d26fff7d9808d7f35ea21ba13bbf3dcb0009b6589527f0b305feea82976d5d958b319ee00a8f00b7ae422480ed677c5e7ab5139fdb6ce83db1290a802b523b0f9f4f14d15bb592f56ed5339e8ca71f8bce2ba08ba125a11ee88c34855c7746b54acbcb27458d744ed0f49d42df8ee3e2855d6bd33e449cced0d19adf254edc7cf058526206f161de777881197be47e997d211412945750a6bbdf08fea6ebe5a65177d1bd77994790853e97686843147a29982583e66915c9072ee62077442271d71d8d098cffd7297c47ae8690cdd55cacfa99b4596a866469aa213cde0ba255ee23cfb1eccf617923549dc980d7461c206d1907a517d7a14d02bb711861df5e535ddd1de652de1681c0eb34009673e67916a84bc23ae9efbaf996c5b4ee45b167a9e1d00c38b90d48a1816fa683e327f7c280ac301c2ee38cc1cb7c876438c6aa98d17a27e20cc30f5d2e0e156c45d62ffcbc61b983736c20481b535cad94f5f101d34cba8beac52f8f9136755d501dfde6214580ae647d8e2aa48d1579e80b08fbb92951f8cf8c8f4713f040ddfa277435c4cc0fd8bd17f390dd8af0b600452b8100e01376c38b14dc6de3eb78462bf991150c4126488d86def9150e57c030346496256026bcac99630641582f5d0c13560d201ae545d7116c4e576e0e76105ffa47b823d8a01bdc399eda18215c50fda673ead7a0bd378225f1c24c397d08451ec39c81bbfcdf4b9c14565217c29b988b71a9c225f66dac7e81c18308a0e9d6fa38d14cfa27563eb8810781f1b8d28f0ad3530991ca247d5bc9bca3cbc7dfc7f2418ed6d7c716c00b6beab6bbf8bc635d1fb8bfbea335f671ae45995ece2e6119c82edd8059bf6dc3531787b50b5dcb49d6e5e3c367b3e035c5658b92c443ba842d34f84b1760bb07974eea03ab53cb323fb864c7775d55cd4f547080ed70923b1762d7235d3efe84e23f5b735e6a7a3e5824554888f90327313a03eabd904bcd80365f52a92845e6d4f8fcd654ee477377dc4891483a91c86e11b5ac141b2cd40643775bf4b3311fd7950206fcc16e83d2da2ec2a5bb5abb14f6812d8a03f7b06f5cad0e33b8bbb13d511dac360f6ebd5dcb57ea8d24c14bab71fc82d69f9a7922b52e58183fa95914c4dea9f92b31130c4728c6a0a3579cd819e9c7e4522661ea737de03bc410f8c86f24300d9536139b81842886494103d9efd611c31bdd2f6dc0c74b80306842b221b820c0cbf7f7ed27dcd755bf64ff6d1fe2febcfa5af7c7041f03f929ec9bd437db67a83fa47eaabed27d6bfb69e813d037d9e7db6ff2f811879f559feefe69fa14fb0cf459eaf7d3b7a81fb12f55bfdefd65fa24848f60f96deabbf50dea8bd49f565febfeb23e8efd14f44dee9bed33dc1f29f8a9c857ba6f4c3f8d7d02fa26f579fa4dee47dc4faa4f55ffb43e8b7d02fb2c8fdfeff906f703eacbea57bbbf6c9f843e02fd36fb6efb06f705ee4fbbaf35f9c3888fa19f83becb7d337d86fa63f6b3ee6bd5b7b69fc63e83be4b7d3ee73729fc88fa59f7a9eedfd667a1cf609fa57ebf7d83fb81fb72f7abdd5fe67c16c047b0df67df6ddfa0bec0fd69f795ee8fe9a3d8cf61dfa4be993ec3e28f34fce4d40786fad0f4a7dcd7babf7cdf9e7ec6fda6fb45f6fdf4e3dc4fd5577a7c63cecf439f80be497db6fd96fb11f793eed3d5bf6d9f629fc03ecbfd7e93567834e27c02b3ff521faebeccfbf4f7cfed23ea2bba5f45ff6d1fe6bed47dbdfac7f471381f83e3fbd46fdb67d417513faf7e5d7d6bfb28f629ecf7d467db67513f667fee3e5de75b367c1ec2e4883c9b270849a206df4037b159f2254b4973e28075d3808eecef50f5d9d35f6d7fe3fa44eb67db4f729f8afa22ee5fb63f1ef341447c40fad1d47f63df03fa0deaf7d76f6f5f65fa9def27bd9f6d1f89fa2aea17b97f5df345b67c88f483ea4752df15fa0fe8efb8dfdfd88afdfec4ea010a8865505a304cd24a0733f40c74ddd593650fccd63ab3b337cb2f2000e204346424ba93262b02c6f1739395b5dfc7517284332657c030101c1d6e5785fb4f4dbe731454f443b58df5e7988c71a9987bf3e83cfa9290bde5de7b6f29654a326505a204ea04596a3f3844929f0734738e43afcadbd65dd66f77b23ae6602bfd1bc046a82662118a89abdcdf357e2d3e107a0a44989005649a2488540113fa40e8d6382f905731e54abddb4bb783087db80d6180c11dba42374a183e61679717d3d97dfcbceb24f7b32afbd84843bae43cf03bfdabb2ef68fa9d46ee40df5d16105567d0c6ab5c3275f73b929d636fd317c85f9234bcef1d1cbefea7e77d70b8e5da4931bb78b44ef219356a405dcf4ba0ef9ed33e6e5b94366fcf83ed1b49abd6c79d8ff27bde3fed230e5faf1f1c5aeb83718c9a8d6a256944dc837770682d6e59e57ebd2cf7d9a8efdfd6bd79f3595ed82e54dc4a1df319fe1d8b3cb7e90c62f6aa2c85cdfeb7cdda304750e69ed6eee58d45b972b262aaeaa5a9a9a72a3252576882be06f81424a79c73ce9bbbab8347ceb6cf3f3fd9679fa42f6f8ea09cd9ef0e9a9f43fa3982b27f9c77e8ce8d7b1e3ca48030f4429887d00f611e431d0e4db04272fa0086911cba11e63766211d3cb2d721f3d08d30a10cd329a4690c18342b603993e4003564deb0206508141a58ff1075aa28b1a932051b1608c0a58632376031f3c60dac7fde0c38183dc731679ac86de74f2307a3905855f9628314501410858f1e439e3451c10f9d1bc90b1c2256aa18e9a124842522cd9a224ebad059d225b161c7f20c0624b9ca37ccb4992607630eb7cfbd24697c604a152a1b9444fd5801d6df4c9246047a706287ab237494b6c0fa3bd64a31073bce1d10bc30c9970db7304b7c880244098cac100636a74462630e3b26a7721480091429c8db091318cf178dd7ce2c21fdcb24f224b51ccd106192b4ede43866054d0c8862bafb5f6dfa63ffd6ffd2344df33c4df3bc269e17f4b296db26d7e379e0ff7c59cb45d1d6da4c11f39b5d8e61cc04f186db6846a7e5ac4091a205c70b1c2929a5947213001ccde8c43500521c008ca68680b20394f5f87977b77f6fb9a57c7233ddb3dbb6d19919318b1431413345ba64568e668a50c9e18ba026122a6a054a8c80c18a9438b07e33059226394cfd908187beead6b6984daa1e35da0e41685e8e627628c349d59fa097b5dc3675b02c92a270701204082a37725c3e518131424e1534fc60f2d1769b2e4d4a29a978a9451c2126c80d505f6e900293545091041a226400628aeca25959e1041a904cbd81c9353b0c31c5143d748822881c585448a0208a2e3d40612487144e40e3e6489928b22ccbb28c0b97258430c35402364fcc4cf951c6ca11262e4cb1022cb287219afcd76b6e1a6db9dd36b9254be9aa000d2660a3841a27264e60320e1d2c6ace006992260a29a5dcbe68f14018345637045124cacc07ac6c802ac114a5364aa2a266d7347b415423a85a1635b65ab35a9570c1d62a71a82facb0e40b9126599cc0b42544307981489a372ac852022e48f8485a63822c53609a10578458e2480c8a0a4e93c733edcedce236b32c9b940e90a5b4fed72a628ca66974f6b0e69c934e3aa3a6f7515153c045d334adde609134c920c40714aa24e1a049851eb4bc3901489a2cab5e8f9c74ce1ae49aa6cdafbec0e253020d74e048191264cd189816c48618ca34e1c5479536b5d65a6badb52ee18305a5934efa53eb1525c4744e8c8071c3062acd932e300d4e0b6b9c50938316261db45ac57c9a18add62834adca27a9a6695aa5d26a05ffe7cb5a4ed3b48c62c0c7171fad879681e2450a94116aa8ec00a18304941927374328a1820ae650252186ea882f564a507a12841346e4103135b5a06d4ec9b22c6365599665d4860c89cb9b17ced0a044136a58806552ce9c6044082a58d2a040ce962a754b0a4fbc308133c40b9d28b54492113f0831a3a507184d9e9122843cc19929d6922398f4a8820a26638e80654f58a84365882545a4c8b22ccbb2d7192772f8643629a5526e9a1694c1a1370438ace1020a413e755b39d83ebf6ddf069f3b0c45c8e738e8569286947218fd383a0c2965955b9209a554ee977229cb259f1b7a9d707b20faae4b3b296a2f71381f68bbf49bfb6da3ef52339523941aa97c418e506a9424c90ffb2aa94e2f7eee12e873ccedc89fe776de3027638ec6a59c8c3f7577c73e04836a6430a806d0cfab7194ea90997ea42f796ecc9a4dfaa0d458e5f8dde0c11deb62b071ea87952f73c7a28c3256fad7720a1bc6a910e4eb652d6e34afadc62b08e4b60de8b91b6e61c772c4a9f81d510c74a38351cafa523e91e4441da8279ae408f544111a394239112477ac577c47a70e9d39759bf7683e5125aa7528a82f98ef434495a8d63ae377a47d47f33bfbea6fdfd1f4ef3a84ed3b3c1ddc41f38ab9c2ca67acf8ee9fdf91afe076e882c41050386122888e092846baac0152c6cd6665bf94c2ae884764f9fa3a70e5151cb7f9e69b7c16ef3894af75dc5df1fe1d693173dd8ae7567c7f3756fc8fa464261899dd17902526a23bcfbd87f41d87449bf63e40dfbd7feb4607c356b8833b9fee67d6308e1dad07fbd0a759fe107ded391cddd3ace1a1be806be11d9e43a4cc752191d7f73b591fa9bcab0793e3b8797d3e7e9cdc88f2cb70870f75061bfe1844c4452aa25be3f4f199eff3f43bf2f1b9533e77b9a15a5f7bbfb4bb317bf746d23bbfe1a437809efe6703e8e977afdd2ebbb3bec61c87808872d7643bbb5ecf04d3126d49e0f559f61c4f7d1bfabcf73bbfc3fdbc9bab074e941368baeeb91bf2c8dcf69303a4ec2e458094353c820064c7f2257743ed5b7ccdbc7937a09f38d431f30816f4e7817cc7ae610e9032c5dc77db77d72ff74037d49ef5f48b80cf774f3f0d3afbbcbca13f873fe7dce73bfc7dfc3cf0790ee3f0f90ee3d0ee0ecf126f98f5f2ebc0f5f2fddba0074f072dfed64b1c7ad3c19d29819f9faf3f373421d7f9fed56faddbbb94797bffa9dfaa2fe76fad2a792ae6f916cfcbcb83e3c4616db57e5a302de89a1c7fe3161bb8dedff58ec32e7338b8cf3a787df659c8b9d721f96fde7d7dff8e74789e9f859ebbdf0949c81b100d4520bbf7f99f8ff135bf756bf13b3eef9f8dce3edfdd987f2e8e1d9e7df008fd74669fe190e300c9aff6db959a8b9fd773f7d3f174b072effa30b3e0daece2b660fdce77d4fa5caefc7add9facf71b6eeffaceafffd70db7fcfa79438fe5399fb638e5c5ac93eb294b17fe2c16efb28a2aaa98922c7ebb3ab4ec58c7eb22e5f81db176785ec122f3fa5bd18fa38533499a5853278d970cc449228a1b27351861c4d005258cf8425776d66ee72b20420879c2440569061de6a0b1a20414393d602c928833e48d8fa6285e6c52163368944e000411b0f9724aced7aa36df3656d5fc67d579a5b7f9eb54dea62f98bfc9779657c579bdcac1f952babcac4d6e4f6f1894a95c72b0adae7ace73df412cd0876dd559248d8841fdd066296377acc3924916f1be23df01a1cb9c4e7210880f84dec8852ed7dfe119fb61e9d3c7e0d21fba34eca770dbe6d7ba5da775e6959ce3569d372cb7772aec550e4aa0dfb60f81f086e9c6b3f56cad972efbf3316e8fc105faa11b230ca10b04f4da77ccc5836d0f0e6db883ee609aa5cf6befdccf2c310e1f1ceea0b93ecd0e8471d4ff36a81807f7345b7e6497508cda72c3961ab62cd902c39c73729d95ec5f8e515b826459963535c1a265dbb6a62258ae5827515890b03c2c2ab4bceffbbe966d4a8ac2e2c37af2ba763c79af3cb1b3b3c393640e538d1fabf65059fe459afab3f6c4865bf62f43ae88e1a1082184b002f3236756e0429c16e220b982f98be04f02ccdf48d2d8acad91b7fc2198c31a317ec43c620b10e6f737e6d13f3b2afe430859db41b3d386c1fd0591770d0d881cf45f80cfa81de7e1bfdb87444446e1e631868394a886cfd026de417367da78049a331ce4a0672c3865da6e6c1863fa077def0ec3df3fafaf64ee98be9893e2707e36e76ff8fdca9fe18e2cbb2f40079d66d9a30e506849d3e6c89724531c9314b2f0104515228cc0fae30603a57f8452f6dfa08e8865ef9cd8f9928a871ca1a8b8647f05e40845e5e3e3f705c318924edc9f07190b33e4961fe860c4fed6ffe51ffd379f53b2cc764d2c0f2121301e26b8688049916f42cf10b0d6f38874ac9e96603c4cc820aaca13acf5266429882201ac85a5b4e8406aed7d903b77ef3efb2e647d76c30ff2f7ddcd11c4ba2fa2b0dd7b479d3ff78e3570f0bf7f01d6eaff7dd79f0dfff94632bbdefbf4be591e8680bcb2b52d757158cab1d13d29c7f69304264b297f56d6c86ff8dfdd3084a38a3b1686c00e9a3570b0e36dd141e69fbd06bc7e6dcf3be6e06f5e15a125428e0ec838d6e849be671b7fcc5a0771b1840d5dd18d0dff45772c3e7d70817e230d58deed30e7d49ac7e4e1cd837fa40f3a38710c07713806a96cfc90088c11e46fe42e25f63a5b4903e904513ef810640c1418a08091f0020c47201142030e4eb0feb839129bf477d01514a9cbb5f4850737534a51e918981d62b8f2c4253a91692a3fd09e8fb1e8c5ef7c8c4503f8d6c7584480a202bccfc758047efdf9401f63d1018a62bc8cef9efb188b2e087a0fba000132621cc00030c0021060002f04a086e7fbfb8458e4ead13e62112b167d3f62110ab1e895422c5a118b5a7c7fab108be2f7f70ab1c882ef6f20b188c5b78bef6f16621100bebf5b884501f8fe16128bfe3e76a187f4fc0e0e9bc84e91d6ffe0b08de4fe791f1cf6119faf38ec1772ff04c261c3909be2b09124e9a6ee391c760cb93fe2b065c8ed386c25b937aed3289000aacf4f6ba7c76219dfcaf20300001751860537461937cb6f71b72ccbcfa40cf9ac2bdf73dd186558b5795222462b39ee4b3c5cb50ad19d8874cc899494b8a4c5134024f46da5946d95fbbf338879e889be7fe2041009e124d7814d9cdc763e06778b30e6c4de3f7cd8fefe8ab425cc10273a4b6c9418d11f99d858774cc66e89f550f0440ace0c4941d65880029b14c440f5a64e0f37666a00b2848a26ac86e8ef9458346037837575f69b8793cfb574e7a494dacb8d7292d229bb00503090a08c0c52e0a024ad5c6989e3a66acb126590705343ac4a0990334e57983001153fe8505501ebaff646f7daf67c95dfd1fc4e8995dff3f3caa5a51b1a71b763f3473255a00f5f9c2ce264fd43b9e41f22e59d8a430bf4598648b1bc23cbb6fb8ec9ee7770b859e5aefb1facc476ee61f219ed33e6ccb288cfe8f9f69e9e9e9e9e8f30baf7963ddf15cb705ed1c1dc8a3bb26cbfab92c1daeeb76077ac63355a3f3eedee719e77a3fefccdca1287a59483ddc34920240df9f4c445667972ab7eb76a61b7b2b773cf0d659adcefd59fdf545de533e0d8dbb9d5752f1994bbb08655f49895dc36fb8343ebb381396cab200f1b38d2c4264e1040be046103aa870f609a9c7c597ae2129dc80ca579e2248e1460a04002e204993628cc6026494953f901a44b6afa41ab364f4a80518eb70a0f80aa21093a4f7078220858af19c3c313535461642a4c24d231275252be8fd5b2b6c5fa6acb1620d4fffec33cbeaf8f04f36e0b10bc673d0bf360bdf748b02827892338d449f2420a563008a8206502272790382209587f65ddcf637dfeb15ad6b6585f6d59e95875decd20aaca53ee0c62aedfde7fcffabcec6b56e577b47d5bed6dd6bcca41a7ea577ab052cfbb115635ed8aa0f3118f93ddc9dbc7316474c8f1a3802ff910c1c8350624b9caf5b37f073b8c91bbefbe3a9aee63ce32ec4ae360ff77e4e2b5e1171a8652bfffc938199f72327ee5535e45c6ca1b83e6c8d9cb5bc129cda71c46bf86c31cae0cbe00c2fa87ffdd182b7f66cfe1ca3eb374bad47903d8701162453e65fbaa9af7c56c7ac7c98a51464842f60f73b8b2d328156be1aa66bfd9a419d36c524f21c9f13b0a0a12522787b249c81c19450c5304c30210b9898ce23422ec5629e7a434cb6ecc974e4ea37dd26cb34952322cb338d88fc2c324174f40bf36c486b2c98af462ccbeba74f2194d7c463635db3571f2196167b6dbb8387da3acacfbda79d995997adf0d99b39f73d6ef86e7653c4e36fbe8a0772346308261df68efd84d1ca30c4ff2a7281aa224492b3ec3712b49269f11f1cbc130168cedbed84e075b3b281bbfeec91a3995289d172bad388d4d765dac9228a35fc6102fe827eb5f35011038532ca12a67382ca1aa95655394d1dfe560c38ec9a6eefb59681390b06e1a492a95c312aa42c71443517a1887457018f159945095e787ce941dcc529d1c764c36e506b3344736f98c2cfb0874745688945be1083946191eaf72c7c5864831b6d8289f5c606a8445d84bfb71f00f8bb0f8f625ff05d838042ddff3a20c7f0d88976f481c8da82e5e64eb606c3b9164f79438678c33e6f0f28c1a55966919159ba96643d554316e485acf26aafc5ea3237cda0226c767e53886492a1f2d4d71bf5becc8f43486a986216a74679240b4e0833365d5bdfee86d1eb12c6f38a3dc91394611c921772c471cdb479e788a117295639490aa8a00970f91ca9e5d4cf627f21971f3aa7f1140f292b718237ed490aabce598e9c7efcfc696a9fc4c3469f45e514b68b2ff01723ce343294fa711df77de83b2f3b55eedbbfece9d0ec3aff63b574adc808df5be6291f711a9be1769c4a1d7ce5e28021372fcee3fef632cb2dfba31cab038ac51a3114747df52ee697d49a38628e5da8102b287c3f85a485fcbc0c6a8a5ab2c99fbda5f8743241a6fd839d2cf06addfb75adc53eeed57efe30645393ead1fd2d7b6cf067dae05f7f46bad3874efbdb07e0d69ad971531088a72fc8843107fc7966394a1bd76351a5b77b26ed8fd7c59bdaf61fc4ac4461cb3e7ddb0e2d7b08bf83187afec613742bfe4d778832e783d7bfc0d830e76ce6e0d079d4a251bdfb1c718290ec2313ec7683bb7dcdb12666e61e6c7cfb48fff92c6c67d7c0963b3afcf11cc3176378112250c0bbe8491dd1af46a00c548c2e87fba8084d1df1dc56748dc4dad34a744e827a13f840e50912ea9a021b00081956076d02c29cdf18689eff48391cb2f399cd1cc0f31640e58a9430eb71c89f0724c2205f20a1c5c8596225cea68517a2f6bb945c4314a0b14394605901ba824cdf594414c32e70c01000080004315000028140a0844028158300b72204cfd14000b74a63c62582691c622811408311005210c40310c03200c020c32c0200529a2dd00b5141f2dea4b26862c5d72b1f1b48ea30f815acf1a6ccc567002db0443d31b35b685ab8adaaa94aa5229c3345961a58f65e94a5c02019403f48b5dc32c5b9697e134ce1117515cb370790bd7d971edff108d19282fd573b4bcb25072d94e02bb8c12398223c5d2f5e75de0cad0bdc5ffca020c57d0731ab802d0661ff69a906fd9aa7156a1a5b7b50a7cc0f8aeb22c163a7c129ea9f824c5c58d6f71311e94b2df3f288c0817ad79e1b1f110f655554fee15dc38dd0f9c547ee2df8fe57a3bed1bec0b207875f17a84a1a44e7de4c136a917780c742960366526be435e9b100cfcc955448322301cf457ac71d5dc88667d861085095e2ffc5c29fe97b8aa1ac7c25660898e5255c8b6de97c843f2e51356accc18feaa980a7b05109eb6717d58c6c90c54b9d10996eb69b7b88dc1907d04d21d7b8e77278316ff942151e709196074241179825bb2e8f834a3c0897d7efc158489af3fa7620851e780173e2bab7c73e480275b44188064272a5f422e0e8976551d842e521a18a3ccb196fe77ef723b446d13b14e76214f626b7de9570004206332ffeb1f82ecfc8a55425b2414cef45b4e2efa92670944430d1578bed4d9ef0b165b5c450d4b06d2ec0dc0081223f40d32ff8ea51f7e12bf703858df286cdd93ee61a86bd5d90511b85e7abaefd2f299790bf1a11fca4c252688df64d01f6686846873836cb49f8970e28d9b072169975b16ca6d2d5e8a1ed0bc15e766bbd53c70502e0414b58f35dc3a79c164bc796faa1e59201ae76c705fed0e57b91399c44ae98f6b8acbd3804d238c8ce37510b2c4529901bb856587e2ebd1e9b6583c2527bde6e1380adaed2aff5e84c2a216bff93d9e54478d1b3783fa02f77cc919796b88e64ba354b3439a07b7934eef88f27b7f9aff9dd8b549b153ceba393f1489ee70b1db3fd844aaa19d802847b7c0710882ee9b5790509b269e3152c556718bc15ac9f64845ad0ef15618dd1c49445051798ce585444fbcd9da9925085800b5b2404f91a15578f93c59868d5b1d6b12aa3cdba228f7c4bc73134db156408a5bf96c84729a0aa9e938e2e4f823a9cea07539d4459cde4c09d5af96edadcbcb65664bf8d7456c307359802e97c7096c9616440a59fdf2b157667ae7b614276ea2d5d09e1b8811ef51f9117279693a112e8443b1dfe8d66495486edc03212b81a184ab15fe94ec353b0ca2511173263eb294efa15504a8420a775edbf3b10652289486b6c421bbb607b822fd3524412b07a315addd101502bd7661fb125066e13942054dfc707d4a51014f816cb9c904e7bc3ca1f5269b57b16f07418d6c669943abc9a05a41594dd9e66f2c3a7dc45277cd2c5a8b6b79506c81e1a35fe1079433ad1eeae6ed1d8b2719e84c73601485c77e1285a5e5becba775cd95459e900ee1204bb02417c4d5ca7a2d0b7d3810a77d64793c74e25abdd6f12914e291d8b29376ac95064bcd97a8b2b37bc42453ca123501640bbf6ccba996816ef8edf4ba7770c1f14a55575888161b3a3a944ddde55a036b56190ee3cfde9ad6c6166905e100ba00b4cc047fcba653f03d9e75b787f577a499b98e09bfd7d9db41b3bb71c390a000da38ffe054e608c8be7485c8ccbeeb51134422a1796ede01f8ddbb9791345324d3433c4f815d88cfbfac1c771a75d1b166f1eb0cf2600c9267eeb7c86b095709b22536cd78991ff5fc5257af7770283be99f414c0616e946ef1a64e3ac89580228760e1000c73513018c8c05f146030bc5ec1760ed15fc6d81d9a9928cf28e5a4c1ab7e0c805618ca81641074b8d9dc5d5910753b43a1bea6cedf75109edfa2900dd133c2bed05e7aeac38921f9d899152a61be443c2f34810d0942f7ed873d89642d0e9407ab8ec54d41fbd47e69f2ea92e462b23132af2813309f739f36e77006b35b0604d63e0a9393df05f1a15dc0857eeb591c6b969fbc3f901b9558698110cfc92a3d877373045104d665a488c85943233b4209a4162cdbfcfa4c721c4a00166d8e9026e65f00d7f4796fd69f998dd232fd5ad1b1cd7b27c6806d164e065ae3d37898f07e25364d6043f21b5f0a6043b3886b2a1d8141e30254393dae94cd6299daf5c6c4ccb97f3d1e242649fbaba33827c226652704d6dbf6a105880966432c003844b3299c29b5afe401694759d1d838b9785b927bd1029005bea8bb22f646a2451666c060a7399cdac1f9620bb33d4089f2cc127e28c7654cc66e6b0b968071bd527d751c4f7a1f4c69098a48d44eb86c50a9cf1a2c6ae2e3fce1ab8b8c65c275d40c0ef41729eae4167c6a7465038d834c6051c0e24b4977a4081f9a1cf362e86ef91149510870799347e76d3ffd0d5674c2ae7b3480da255c70b33e78671dc80549ef478c399f4fb0a0618040351e0087c7639dcb6b9760023147f088e70fe27e86d7d9b403777143912659774303a96a4cd0671d87fda3a126321d6b0c73f16f24b3a978c5508d2d6b6ee7620ad693082c8340752f16df52ebedb37ac26cdfde4c5306fcc039f6cafd6bf066dfb61543f8b1504d92f2ba980a260385a4091aed1e753c1e9b905dde98c99855e69b6b06285a9ad3f90ba3f3cd289b2eafc68bd8164fb7a7f9a8b0c16f8567590362b69b62eb02d5f4b28aaff7598de06bf2ea97f1d103b62fae6cce800ea2fa5b793ced560655482c325c5bc3388b2ce029321c4260b7246ec4bb34b58a77ed15ea64974fbd25e877bd33fda223eb4f7c368beb4152e26cfc895bd37a72f58191a8fda21aba68388a6f88d7c1b4c29f8a285a47feb8a51c8b584c68d587cfa47dbcdc50071312413964d0872b28bbedda55d652d9bd1e88b0e63a217de65403dd20297a2bad5ed81fa1495c6d58477465655307279d169c2731e7ac6b610778d1bb96246347bb9b9791403c41758b145d84c7b352baea8df7d9e346013472662392820fc32051f69849f217ed021c016f23f41dde4894e5bbe92a2fd4232e5a356d21f77a1a8c087f54fd4dd50c631184b5e0a0649acc568379ed8a3496876c367ed73cfba5f5670d0a68bce2d10faaec1dc2248cf7219992dd1185ded886526fdd020edb41727e3ed986bd27b1a4d25da6e6b6c341e6e924731e1bbeb5893fa6c12e6ed385a5bef31990119e11ceb2fcbf0f033a43ad5ef4e55818909c82c106a64709e0d778c2335a9d0e7e6b4e30202ea25f18353c45d919ff78a5bebf3be6e162eb3ac30a9fbda84320dd8b4b3d86384ce5877028d4dc8a3bd8cc6865df69413fbbd532f4e915678fab18545a0fe9b3ed26194a4fff7b507d7e1789735a3ac318d78326bf567d74d2fcab0930186ff61f16a840dc6657f290f85dcbb58c62359c92210456c1bd0d556a24c7c0632301e638c938264f778c03803d3648ab4c0e30b4cb9d832631bf3b38402926d9468f20f0cbd5a1aa345bed8db6e8ad0ad53161df7011c8d054781816f915360a69e44437ef6bed573c2b6979d5de2fdd8c54765aa36b84e76d9895882a7cf5d0957f0bf646499a0aefb32fb2853676cf4a9e5b36b74b89c323122e410b5160a4c296a711b1f7a77d11e29433d5e32ebb106bc29d16421903a63fed159a288d6905ec0b1477d0a9bb008475da2291a11f96bbfa81a6a5d548d012729423160eb9283c8ea06061af04e999d78b1456200051ebea41c46009db795341a250b1f76144870f65f252ec662f768bc085a23eba6178a7dde2d66eb764648e6647e84897b39b132b9171b98789ce6dd5c9386ceb33dd1918774f9392cfeeaeb3947d3fd8a084a2817f67f4df65a981d3fce81d4d1d650332e4bf7530d842a8589d2236f824d4445cc9c35f0445314e29b95a3ae52666f54209e9151fcd100a606c0fa8b24230708e7a6c645ac77a9abfaf564d2334b4183308c884a5d87aec913acfbf84a213bd2f144f04aa3e2797b1a57c55c4df9722e2a1d69463d1333abee8008654b2881155aa0090d1a25a9a9359684010a31253b421d22d430055e3b6f5d414db28f06557eb9bc5b48d7a8c0adae2249dd48aa0da4c33c74c4ccf579d1793399ec9035ec800ebcb7e6e9a6509d539ef4a47a7d54a9d1d0f217ff39a0ddacd767e8377da2272caa779a9ec76cf3e50329d82753efd9f81c65e39304a322cd7b64743e93064a6db0089d1550244117a731de52b4b23512360d9d53b1ca9658039ae7b9fe067b9a1c4c573d4cdf526614c16d44f0360907bbf4db38b129601d66f4c30932b5752b1b0d6cb78f1258cdca1551d14855da84eb587c88447ef9b496f03026dc9e5adae49e22322a0d9425d60dd11cad1a34c9f9828dcd6459a23a2064b159fc221cab589683f98aaec20288e5c7dcf38c480238ea7d11480e8f62d39000c41d60749598e7b96a71d6216cbb7e009ba61c62672162b3f4fec17a88631af3c4a283d7a0a54b71ae353c46c0ccba22838c4028d54b9e434c00a6ea631a6a273d36389d9d3335bea9c139dc931ecabab373e8e8d845b2b2cd14caa4801c72ddfcb985c4d82273f3254c7930bb80e81d6885465a2c0cdba13e06f2e99f7957655ea681843eb13353d3d39abbe2867a844ca9a76e563d50593dfc80d81fcc844f46996168e4e4e02d159342d9db6018aa83e8d36a79630c7fa647db8b335ef544be6cedc17095eeb9fc9431273c1136698c4ed77e131ac564be15ced703dcdb9b258311cc093091154425f323c778609b7dae9b849b4ac911e225bf06002b2991be18b7404640c544bf7981f9641c67df839c6c720153b7ab528ce2fab911238d63397819aca31aeb9f5fb2fe9f0654d23e6c335d00b39e34a49dd87b8ece8a1d6003fb0f04455bd09fc80f98e97484a0440e9f12c968afa9ba7eec1c174e9b69d01d0e4e0962e75ec6f1ac53aad914fa7fd2a70de8b5989fb0c01e4fc3e813c1dbe8f6e052c652921f15f2dc20a5c7e4187e112fe2ac414aea3b4024091ff4a56bcbdec22afff3544aa3c0c52ccca22ea1c0d422786a1b5022c51377eaac9b4f9366a1d111c401b6e71e9d451b9531a599c89362ee48ccf29e6967148e641f39e1c04a061036d51f88e60bd410bf228ed58ba7c55ad63d7e8a282d04aaea60a503cfca2b18cf5c8b28284355c34a8d267f0f64a76811123de8882a87dffef07d301380a9a43836e2d335bc04a8feaaa41bf72ed75610223f46fe3be19894fc85167209d55d67534ecac0c205a67233b2d3665e684be0458dcbe9a6ff1a17089a405f47b5dc3990436e5c86ebd00f6820206482e7f9015bdda91002e825cd42e4e7183108ec0bc950028adcc8b2a8c9bd1cbbe1bd466129d59a3ac619e27347508c371479118a01be50cb0b9c4fcb077e5f94b7ddf95c4c13300a6fc68d1dbc06a6c29a4c808cc094c941680f42b08b5ac2f2f37252659b8cf76c39a561c5007fbf5f8ea6096135224dddacda08587ac73c7487a3cc1d90bf3168c9962003930ef7b4584296ed6774405c1100bf6d0a1480cc738ff7e967cd3934701f0f11c45f09d81892d71c82ea12875639d7ecbc02286328b4192ee1a6bd8913b1f0ef86aa17ce1968731f9bace248ba8ff81b6dbddbb8afd7f4a4d0f261fbc1c146683f64f0888d8fe0c0f124ff87aa2c9f63fe17f242d7f6b97084e99bef56c6a10fdca72bfec0e5851580b8e7a493107f4d0319ce468d9ae590d958eca211408498dcdb646c5bfa884e03cfc8476b229328aef6abce82a213ae5f421633106094a6fe7c246ce095c307eea0311f8792e0ebcf90590846844ba0dc9711c3d3810f4591a898b7e53d05a266fa3fefedafb24ceeb2cb1d27efa513104e877511b984260102375eb348f2ae399e266f6cf421f751e779e89fe761120c7c2dc226ee079fca01d93ad62782a4d1b898d8711acf6e6a0da7bb9144f7b731d8fcf0aa40ed7e55348962c6245dd51e85fb222a631c37355c0a14f7ee77a16dfdf9ead05c5a64b1e1515cf4a3ed465234b9d4d675eaae904001f4ed4c387ce88353d429cd7976a37ea8a6e3dc0907b5e3d1a1ba3336d13c30812cd41a5a677a66e1ad722f7585ce8434566bc9140a5e785b6d116e140b2e686c3c2d4ad028d817c1802e59659556370e01a6f62773dbac67dbbec24d46513b5b8e3020e94952d78b404b196277445ab367ed49305a2b837d3cc8c1b912c8a4651f8760cdd1fe00b7e4e418be8a4dd94d848bf58a1ab6080349001ab2fd2c023803a9083668eee92949ac7f770d5ab0a1c6522b3b38f3e8899fd31712bb0e90853496a584502c0465c0ddb898d1c5055cc95c2af76f52c7c974dc135749251420d89acd89df5a26d8a8a590951ffafab0a9e16147ee72e0cc20ed49b649be2248113cbe27616f914549c035d549f4b21f249f76223ee6c7402bccbe2922d9fd785c28c736906d62e50f1f4d499325ba3a82761422f7ab002f5519cd8d93dc184f959d9d49c3244a90709a31700e1e95c7a5ba99fd51c76e6138dc14b37d697243b2f02e758ae9f22a0bedea596d7850f6773cce34c7ba109863c4b1c873b20d40f7da64999b89dbdf73d29bafb11acb590c48cc573ad49db4480eac1202df2acc03f92e61cb8c3eeeed5371097b548f126f6b384cf6c277e9ba46138883d9b18741de5084b811e1b79248cf0ff73c30b12dc22cfac511cc3bab24434ad42a2aa30c408081a7204380a12d904b83caf2966482f12618a94dea3b847198acb9877429596bcb33a449066139cb5d1d5ba8761f859dc943d6a72907f4890501fcbb398d9c03208eef9d90c06a1f64a271feebd41177184de371c8b35a860f8d2d24a90a75d96a84a017ccad5a6a23649d9ab3c3a324371918e03b808eff47306ff5623554c86d5db1cfd22fb6d0d6bfe4334ac9c4018d9c1c09dc42f89493bb8eddac69f8101753f98240179155783a78f6c8c2171f07c634f2d576e89e3b46591cfb6303fd0d8fd4a5e13446fb806ce78d98a564c992cb07bd1bf7034dac2793a5aac67375d7f73aab53ffe45ec997c21eca60ebf1b2cba31950cc761e0f55153a4a8cf039adb956db303301647ed849921902a8cf1b58e25575bb01d28b13af462fccbcccee3b0e511173b44583c2771e8b4cf390732cea8895ba77ed671f2ce6a0c7e80ef9728de0440caf1cddd0e5b3f3b40f07081599143f1eb59ac063709db8243704477b0ce25086c4fc45d08d67ecc042fb697feae34614efc211ce4a69a4d1257a4ccda7255c186dea5126db517d6b43ca74c6b7e0021c408f984fa48c24ba6d0521249dcae7a354457a8625edf03328a85abf489a1748e8588e72e819a5f78051691caefe8723b9a0d068d1fe2c6b4346e471b530b3d02a3966df881d0ad6dba266dd7d069aa47e475d14a8054a9926b7cd1cb4082e9026c94a70466824473c260bae799832b9aa289609c50339859658f023899a806bafa9b51c8d0e29dfcb3f857e76ba1f578a4dd2c4f19e870c54ecbf8abb5e4469f8d09b2d09349ed3b68324d7c1bd97c8f227b7ab4516ec5be0ba4d07cc76cdae16ea74b63deb91f5ca6c857e9fe2dfe2f6010f1843055ad10610388e8b6e4334fcd1aa6942755094fe9fdcaec0c2bfa56c4516a054415095037ea0adc8802e85c493e33325382654fadd817bd1e7e814988c129729ff619e7bd8bf1f146941aa2216af7972b665408f6160957ea8adbfe439f5eff78566f7794e7fdfa5b45aae103ec447f6f1c1973f8abe1adf06b54f84e000aeb7f61f86afde584882ec5513e388c78897a63278b3a9003702126e5c560d15299d923534028da668b9331c190d7fd148b857901faba289af701ed7152f3b9a03961ef778b080a1502261495aca6121ce7a54c5c232166cb6bbceb448bdf4ccf66d0be4690a825b424a76638934153b8fe00fb3200a42d41fdac1100b4b469e3b663f9ba81380111963dc2a78b3c4301dd4bef4f7d3faf9d4e7e5fbbf4f552eea9f6fb042e7f085b34f29fe09bb7f4b6e7c57e64495381bd87968b71c34f219d4274ea9c621f2fcb31a3ffb23b86a7333551404080b8505e40327562959c56e366bbac2275139b32d34619b7456e009374841845299a6400ab222d8b79a0328d4e09e5f5be19088e4846b7d3473bbd4230ddb70055c46d3c13dbc9aecca9eb8da1140b34a2eeedeee86ef916551446063d49f243bc5cb3cc8a1118bee0d0c9ae11a2ec0eabe470ca6901ffddb8e6d2ecdb7234d86040bcdb0e3485e4036b574a164ce8d0194cae96e72ce5a982ee2b24a5d7866d3d876463759cad58125d2bdd40588a91cc0599d7df391c84617bc7897037902f49af1f2aa28c36a09adba57b40d8ab3eadfc4adbd4974a78af19f3f0cfc50cec978982b48fc90b4f06cfc8471ba6f5186c3461f2f73912d9f208324707dd26c3add3e0c5dba40f2033b082af8ccdf321f2ec8321b8935b5100c08607182cfd2c62c49e6bb004a66b930739ffdaf4a523a1f529537b1033df340d2c9589151bb04755fd06d48616336654bbf28c7264d391a41c20f01bb6fb59a3782cd0d7882e456570042d32fd0391b64e1a0b608cf22f409d03d9ffbbfb664f5a1628de684da0449c0fb2aecd4f3f1e01c9c5ea0cff00fe27dbb73ca46709b536803d51e18c4531ed878b1e0b3d36bc650620ce54d37375093145158a52cd797e6c991fcaf57c954ea4d830b21ff735fcf0cff1206a8bc23d22fb8069c06c172b3dc047c76a761ecfaa644f899de24760f227f37390c3742292b06109b12e35d6a6fb8beb5ef402929187617e8843f08cfc8bf02c1258d0cd3b59f8a4626ae699d01732b527bb1b1defdc52ee0b14bea08b099d34bfb0dfd4aecc086068ad638668143a19bd4b08e6317f0c09c638175cb6cd59911b4176c64494c20b57ac8585e74299c0d417f36dbaf0cfc5ca2e428f59a49b074933c6fc9d4e2aeac3865ea286221d954e996fa165bca0030f01d4ae9949232977bc7bb9295a145049402474bc6fd9c880cc661efe8b96db6e58e77b94b62f81f7a8fcd0031007264a63b930f8c08c4a51f221d4a195f59df36d0e2bb5bd70ea52485ca419790d6c028ad6671f24c18a52813e94d04a9422200e8275c0264960ebbc5f06e55facac12dcaeb34ee339a06fbfb9dc20c59f2a69261ddbe1052e8efaf11f762fcfd4292b67992adb1f164f4a50f1666288bbd1940e112dc88a83980cadf5d9a1358a0bc6503436f52ac2442d5ce85c54fb09eb349d1e96ce3494c4532e1bbb51b25d2fc7e95a23a09d0fca1321dca5ed1a4ac6dc769d2f18af40ac9cbaf375c166534db56b6e92b90d7c720d552f3656e705cb1956427aacac5a386d73f8a7334e32175eff4284c35915ac3117fcd82b47f70825d0c04ab44ea091499ec94efef532130b718264652baece4c45890b15aef8b971fdecdee38f18dd79637f4dd11f6b9269a2a61f83d03944a2e3aa152d8156d8b8f685776b6bdc60d18c0a91e204f80d4958ddfe389952062150bd8c662ff2d15a5820b5881806ee8cc7c5df5f0cfe8054c10f562f4d83f2c7092afc696d3241a9000657dd49b7bdab05035a8208cdf411360530990f21cf5f4b74808d5ab623848783f3c392768e2e0b18ba45b8c46c7ed424e52e51458f4c77ab1c64f647e3911906d5a285e75731818183ecd2c8c84de408ca656d1a3eadc2d568e0b443325b8e0ce53d3d8f1ffe701f1d836b15cc41d98088fb0141c103d2aed0289db6632e8848b9ce1371af96e06ccc242d35bc28018430aac6d9a95f61892f00d143fbb1606014762a5a521e11a40ba8a98784c42c55c4cc4b5c344b269198cf58539c24dfb75c13837697cab2070478c5474887001b5b996383d76170d2fb340774c3f803195508b7d2625212dff8f2d47c35c122919b51e778291a9dc1237ae7a39be185dda57fbcc7f10ae56db1786c063c023f9431f879e778f1a4a91875d73d1a462b3cf74ec44d3290e074d93a19a60c10410fa572b48830a9e0aeabc711fea19db8283628560944424414e5f7d354d3fb19213b350c3005e6f6cb835c9ac6c014d85b355580f252170c077994a22a15fb587fa75fd550bbf810b1ecb875b6d3dab6305b42ab95832e396ecdc5604db305a5603cdc978d19fbaac1a66ab1b9485fd1e5b38900b2d7986dd74024cf44c831739e3ad5f74b8ffa8d80695920487a3430cec2918f3778b05f07c876d8056133b3ee0dd70922435968353fb2bf74860416056c8d08c563c227b23119324b3efe732fbb61cc06ad99c5f6aa5ee4cb9e42a6d1994f12127701be244b7593112f15e84a2a74d2b61e362b87b920d3700f2806123d2f5dd4a2d6bb5d81375b8a19ff47b84297caf77218aaf6173cc75f808799b8f113106f5b94968f229fea74088d9138c8231466e7add9e94a2488d5f66ae7df18b79697af911090a263370288218cdfa00404bc17d498a1246b83172a161790075e9eb2f29a7e2592fca2cadef1157a909bd20cf53d85dce7714084f8b967317a8f60393205d9548c01dddbe93a0a67d8aa79f6af8dd265b86f19f97da1e51d36ac7638d27841200be394e91722ae411afc74302af774365dea321f7cc58c0aaf00b70fe7c66f6af3db2394c733501c7d949c9b07f6b8f4ccef6d1c46815c45ce394dc072ba881db880e898bb16415e89d14763c9090576458697c658ef809c690b333ed1ef9cfaa52460b7cfa02c9e7e4e6ac30f016f86a3bbc6d7d58637dda04a83c1c8e494be13d2c2d3d2be3dac563a621c1a504013ac2e02548486caa5cd9c9627d1f1a87e4a840744bed5e45a40d85c296e2a9de972e5e388a3156304ef8743204ccf1e038a5359bbe42e903ccffdc1e7adf5fa3c7c406b48a159e94018745279a2e38374eb990aa411baa17081888666c0d1317969791a307d088f83af3b3ee51cc0a4dd678cf5fdef27f2091eb8db84c3ec23356bfa0984f3a808133cc223f55fe83dcad6ab57c5b3327ae457e1e31eaf0c75be6d226fa84d3c98b8b16a5ab565d44953bc88c5629e01c5e2a3852928e198e2fa28553ec6cc8dcf7eb1e67984938fd0926d3a7fa0f34e23aa03c32b3c13b6d8acb1da65224c0849a844c04305c6265d0bfab7b06dcc594a2cacc4331d393314c61589d260375e9684d203b1b9e52d5223f2610f4501e8c231aebaccf01f957debcf2168f3b75461e4270b3208e948df3333020f970163836ed83110ed1ccc77ec168b914dcb7710bba2be00c3708f33423ea171a0025fb147684db4f6b9aac4d8cf9c9b37ff5e2b62e8672aee098c3b5684a4c205de0882fabf299665a17d306bd3c32e6e387d5e9651faacc671c60d161487d7f11a6c00e942d63c99b50f37de4a1f1e375cab42b92466b002e094ca12c12c29585cb8a18a656dc50d954c1d8c08007f92c0e40514df5175b155ec4dab33f365bd2bb51ef239f5bfbc0f52dd283959dfa82fa7e137f9288c112a08dd94e09493a58a071b742ba498579b2f7b9a85ce30887c66e68644e80633bea583ff7768a388bcca66bae59b2571f11f18188a408476ddb4a84f13c594e6f6394ace46caf3a593a81d946a5dd0b536fc7da6c34020bb664808830ff43d43c5aacd6a93aa3f505185e355270fb78642e2c4f086b18709bc9c38e39fec8934aa6d007f68c4c45492b8dc88c4edfaaa057d885fb9f37d217410cc2bd413f32828e5c65508cd9bea2d93d70d82cdb0b8a2f3152652dc368465dc8c6a18cef66ecfe06b53d483c908ba3ee0975eeda0a504755a5327bc0cddebd423cb8ae2a35f2897280d8a383231e1a4279f4bc8a884dbaa9230836f83e3fc74d0b76538490a241daa2583303f4766b40161add0d45405cdedd8d9bf0f035f16dd6dafb33f11c670df9779966cdc5b7b4119e15c5a3ef6d06fccae0153f3f8d60b4daf6b36bca6d22cc72dd24bf9862bc6c1d2289c5fecf1f3d33ea21a8fecca67400bf72865fdd18855158edae46044d12ced4a27f46fe80da805c18f060c7785d86c5dd9ceb76bc5ce9c74a0b5a40d72da3ae90fbd63b706e4eb729154000718b49d775df3f7b0118206d81061d56cb080e02a1316dc9368db909092b0f13dc59791781d9718192ec5e6166d0266eb31175133f9e49df046f829cd149944583295f4492455217bbde8422860b9fe3b373df97bf003bd931a32e786b0ff97e5e0096d1aa19b8268d4124245f50b1eb2d4a7cf8c571b66acf7dc571b0829f36a78b6da2e89cbe020227a3388071805b8b26340a26cb96b5c51120b54a5816dfa01a303f8eb1734573ae4e7525ec0a607ee674a5ab162668d8355ed6029c7d3830280fc3c988a11d13deb5eabd183164b2abf9cb1048f9e6e45bf823871e785e2d064ec9aca8a342f8b4c25f9d5da8a0902f65d557fd8d0c332160105301f894fd76721518c8819c7c5608291f9f53deab02e5f3ff8660549dbb9d982eaaf1e1ef8cf2ca37dcc3f35cb844ccebe37c637feaade36c45641a89533761bb2b4feb2eeb403dd84396ef190b30599550eef8c44336e8d67b7ec42a1dc4bfec1413c725a6f47933c23f4b99817a1f210fbe18b18ddb8997f90f173972b60bccb49a29bf9868bcab55e8cbb946a27cc7760c89b30a76086bb95eef3db50963cc16ce8ef4c21ec507decb3bbe1c6ec1d55561af8302383ad875cd79d469d7befbeda24c031712cc1f743165a5270abdfd1565357d2d5d02a9f8df96690173190fbef66db72e3bb22140255f557c9587e6d8b096b843e84c08a95edf13422ae0a6d8893b6cccbd352ddabbbe2084b2660f34921ae2cf0d8e5c5d1d334495afaae764c8370092ec233ea0d276a80bb1ceef915dc50d23f3aa2407c8d176ab5863b8a7cb7daf115e95196952d41a19c9013c972411f7cb6de5740d1705cd2fce82a74dd7792ac0d2b11645e67401fd99a39735a847012d04b1eefbe821e59c187cedaac56e0ed225eb2da559dd89519593ee763ca1e6e7457af54ed978e306fe35dc90127653ea921d7be86b31ce674f484d777f372e504692c916c32293fae7bc78abf1abd9b52841950a76eaec7f88618bb9487e848355298172babb8b2d46e4a7c49db7feffc0ffd44a29451e6db124432e2e315dfd051107a0710cded87cbe7df3bc1dd379bb6bdce30bc0a02548f95ca5a320a9e40a62168c1cac1bdfec337ed977a37bc00aa76609fd365de9e0915e19414c035e1e834036cfb75fd33d7a010f5a428af7453a0c48314710b1c1e862df8cdc4316989a25f8bcbba4434f8ab3822805fc720c01dd86257e676648476fa95c11c4bb008d25ecbcb5a4c39d145b05914f79473278d3330bb16e8a6ae76ee0556e29a265cd33fc1982f57c28a67ed45c099f0839c10e1ddabfd2b17db4f225ad62540d58acdcbd5adbf5583d629116d65c272ec14dfa26ad9579f752d96a91d503204290377513c4ee4d5649506d41328edfcdce7885a54f4736c459b9e11ccce7818c46eb3293c63ed57842b2c5b75b1b6b92adc65c166be558b72438539febc9dc7b8a1fed53b60c19928a5c08e0568f637630161afcc789a42266a4bfd328ca01734f29b5684925a0029c81a2be5f06b9889a64b4da2734ea47d4744097c39b8ad0e8fe25a51c10aac6d1e8968e07a8c3782cff64e852134924fff571ee2c34b81b08707ed43520199546866dcaebad30edd80b389d017dde306fe9263f4dbc432100b122165949438a3a43c9dda056cee124f616add386098c66e38f1d0611a1c958f898887f8177fa15db3fafd0ee987199b0867d1ccb2beb8b9a8aa44f72a4ede4bb2875e94786ab202cdc046305394fbfc1880ea539880bffac7654c6fdb4f5b46d1f5aac6bfb02d5fe0a90fe64f03faae03af8d3f7431a51cbc86a9188ddae6b5e626b21dc469c98064d286ba14c9dcd8cc62dbab9c21fadec41114a025eb7f8b77f6724e2a58b06b79fb960c7ddb4bf9bcef1e8843b8d69597564b98ae16ad5531810e4bd164c48b56d78ae4f6c8420676254148f7fe62bb96471cf1474891fcc70ae5f943d8a184da8f3df9a19db84e2745df12284e2090717fbcf85468ff250071cf2841e0cb5a8d281037a59c4ccda1d463a87d2c01301ac5fa0118fc348f2e64d0dc795177ca6cb0d23a7ca2037e36b7f0439df55fd08ea1ce65714a99f89b7e61a8e39692c360c2aae4e6433d8f543739bd4b7459b76e2552bc9773912e8b97da04e8a802d9179193944dbe109efd4d5851674553608c43f42c4c69ef46f04bb3620a34bf26ed988fe137abb6f74a547ca974e67c367f2b17a02629e1e836ba7e304023e37649e898c913071da975e3af0eaabb15a9f79a2848f20f1afead8391398cd0cbb8d447cb32079ba37c498f2478d9c1bc0dc5fb7868c0b0802967103da317354671c102348eb0614554d960acff501684945c9e41adf6d772748bb62c4d3e77e56b2a5ca69cc0078a3015887f7e73ebf05b3df2351a40dd90c1eb82eaf359460b0401c7755f87624de0ec316af001c892cd124df742e163090e7057f4fddde019fdfdc7c4518b49446d66e6e2458fa325a3f2896ff40fd844bed76f91d52798d4415d84f0cf3c030db2055f88711b27cbc3c08f7a4e59bf2697985f2b303610aaa1e185dcdd4bf3cdae78c872edc59cfd4f5f44530a2c9f5f228b151fc865717da45d8a0e55154bf0e3eb06d70a7fda6194c12a5d15bebda32293f5452ae9ccf2fb0f810535b1ea9d5e8f7b24c7cdd7ac994c95231f54fb23c50ed592388e58125a67654ceb71970bc26c468bf00a0cce4405d66c57af7b1e0a610ef560bbcfa7cf280769bd3a6a3f4c311b1938fd7e6ea0644af79aec46077231685ee032533dd7443137a47f301bcc3058e5585c61057205c914d3ac9b0611b5fb6f3e4694d4632024212c09152eec2cff10c394c5c8506e0d55475135a83070e2c06821514200065e46fc5d981f0ce10e6f551e0ac28a3929b81f6924165de8c5a3bd41a216a8d10b2f7967b0742112f1047102e4ff1ee623ac245bf5ede306f1393975c22ea4b0e5e22c0abbad225627eb91281caf5da2d482f1152ca8c24b9be9c62a024f328c930ef66c995aa3b613010cdcb056a9de676f84a975bb3cb9d42bc3b7db0a5a1b9318d01bc1ffb5ddfba5348bbfaf2c351b7a0616bc462a0d65bde3e4f00ef59bf57e6df96cb431665d6b7ef66055bd994c1fa008624f113da20e63ab15a295759235ff6455fcadab546ac11ebd65a0b84e230033f68a7ff416640032d38cc1ab6dd94558ad5725627735c174dac552c80ac8f53767542100c8901196418901073bd2e01b4b229a38fa3b12e3f0450d1d32f16ee634016e59d12162dce21b93fb9008e9dac93f58bc3534827a45fda9475db6b87b4383df1eaf4089080341413efba3a334fc45cef5628343fca3485c6fb1649077b0590e63437a47f5dc65a37a4384440eb3b3c089abff011344ef31df0114572689ce647e8f01608c0d777787d071bb9773897c41dc92f4c77b8d688ca2cb30e58521a5d447b48afc3776221c544e4d0b80e5887064e808cad7e19a3f938939b062e422f63ad8f606e1dae8cd1d8c9363d9b90311a34b02d02eba93532d2228ad10c4a40c76c3502dba88db458af00ebc76e9de6396ac518c81ecdadd255dff2720e541e75329d9debaa3c1113afce9c95810c3a887de0452c04b48b2e6245ea6d91ac2959316d5439c5b4512fbf1b33fba05d7421a50c7bf97118a7b386815aad2334ece088046088411384b045ac6fad7dc1b43ed3467dcfc01c4c1a5828dad5afe2bbd1b9de64c574f5ad60b9306f606dba52a1a85fb69fe81795c95cf7e4859878e519b25082a158cf286cca4ef2d8b3af8b1aa98b2a13c0d1dafcc005f062eb2eccf38179033400b55a6fe18a248774e47085100338ca222fd75fbee86b3d9ac2148e18d0ba78f921a07511b71175e9688d0cd5a66b4c6c4d591be9775318c9c28811a32a72df1ae9d7c422f70d1114986000c72e3a1aa7ac8df0e8cd93b9720949a8014f4c7e434c998cab4016626042055cd022074a30a0750d09bc4c8c2bb58ee3388ed3ba895bb34646306e45ba5d846493ec11cd47fbd1863422ad4833d28e34242d493b4261723bb269413c3e455a4fdb22a421a15950ee9665d4bf9f98504ade512bbcb8d7735ee972f7367053c1149080448675511be5fe16542aedb3b45a0b9e089b4d6ef39c53bb262a13d35752fa6ec0cb6f5e75b512c8402bc14fee3af9dd68f5a5db3bce362ab921bdbdcc35a4b88b8c64130daccf086affe91758a20df901ed71589f598247109c59bbcfb6a846bf6acd7648bfb87ecd9927c77dd6da227d45b9f4a58bf2da2f14140e757a1180a3edd74ad60860ca9ad4bb289b4c9369327349d36ad49899794b74c59ce251fbac3123fe31d775c5dbbaf6bba1bd7472ee9adc966ed3d67e39505d74d14536b9324f93eea745534ae9d5c4c4528d72745215056909a5263f7977723b2231004d3e763fb94d4a4071e2f11cd6f02d0e0ff6ac5379cf54de55af3f1c25957b7a0ac7e92aefcf3ee563769153ef1937bb864fb1d6c94af0b8b29c15e26e8fc25928ac1085591e219ecea713c223938158a6c223283319ad14b91fbebefbe988f4eba45f73668e03c113eec49ed09379223be9d96c36a3585683c3e35317c71de590ca45a95c5411085a9e16db161581452a5bd4451303b58458a11e0e5b2042b9673d9bcda8ea8e3d4433e1e852ae2dbad688354ab92537ddaea70b72a303d262bf039257b83f41a0d6e96dbdda5b174891da599f5c6fcdf3ab250e0fd1f00f9fa43632a45fb48828a8c0041da4c18a27ac701e30a1441349f880800420c775518bad696d64ade59a446b236dd4a0ac083c78e590769257ed2899bb8eeb39e7e4e991f9d42223d4eb25e12cc9fc4eb37514066aa1bcdbf4973a89c715c0c3dd3efdb2b854c1bfcee45a648f3abd3e2d240ca0fdd8325917d11a356c51178907f1b85261a969da3528fa557adf0ae5121e21704ea8c56e212deb97f746599496bd97be1c2854888375b4cc610b851156c80af58b04db21168a7ea53050eb7766f0cad39df9bb337bb29ffe0c9a3eddf57163661c3203c9c00a57bcc0067098819008b880075b1c99410a28a84167ca8dc5115558c1c80c892d6881060b04a1054832981da10d5cf438f5e4791857c662ae8c752622e0cc13b1c986341015c5ba09d69e59ebd35124b073cf8cb50e448901c45aef9931d747eb23d3d989e914c911ef7aeb32e774cc8575746a20440b9e9878f9853a5a38ac6f5dc6441c56acc375f132e6bab6e85a23eaeab7ee2401d862cf108045d6c767825c57ba921e810b481900591ea10b3c946a9a566b1db9b17467b63eab15087691cf94b5d842e657a4699a01340c048222f5ad5b6b42aab847eb3385f42bb4b9e219a4c5f619d2ba52baea0d99eb47efc823f310e2ba673e143665d4fa589fd56aca7c2c9122ca712048ed10eb43613deb99f5e959bf8bfab5e190ca34143983148c925a90c40522ac60820b1aa440073038c1b54536092422c5134150b8c1139870c54e1129465064094b241121c1490d808480032daee0794217b0d053062bb4e831d28521ae20528321505841c408028e202951451bce1044e68026dca008478650e1c80c35ea6d68d5ced04dafa8439281b146b93faed822e9ea97fc9047fb2bc0366ab18d98011cfbc5ad44b29ea1d84e109688c31b9c48ea811096380242145318431247104211fac5ad3e42530cc5baa8b1c83866965286be3565ad2993d9238b648bac5137cbd2e503243b2f3d7d205bc9a45c75d40b616db45b6baf0f2dd63aeee46edc551dda6f098f3c7249876aafe118b6da2ab65823051d051a369b7a43342edd182d42408c71d3d18e4749a6d7512fc4110474d40b41613b4d1cc10a21e0c00a25184921566f53ff43c7518ec3150452a464035aaddbbaaec3a309ffe0438b75c559ebc30f518040902e99f31b76205d325b1b5315607d67f952f10e99d465a1d16dabb7f5da476beb6dfd0edb739cb65f1ac7713d358fd29ab977e6f0b8d98de28437ec7128b55c31976dbac6e172455d92db4872a238d2a2cc3b79e6d35fdaf93ac190b969c4853648a1096c58c36c89981ca283236b0881133b08c21790051648e8d18112238062a69bb260d25c39dab3a9adb427adbdd9be53b3b4ca1a49393b658df6b6dfde6976da4b69ded1a62592c204435a68c145902c878820451ea711f27c8c2c8ff0862cb46bbe481d123653832b015959ce48980e1ac393b02a5df35287c41b96454690e41841884cead1a2113f5e008e0fe80204b9b9f72d75db0c8571dc2c81320329d2f54c8ffbc3648de857e3f11dfe6144bf62235a9cefa103b071d276ee86fba603907b8f1f466c5f01766af7bea3f7ee33a8c7fd614415c0399b4d6c1f67d066a2a50e9a4fe4b95d7e1ac6a9d9d6e9e34d9f2023d0740538d66c02f0086f9822a7005941891c3861055708926690a2062e00021741d2b082d87c0f237828ecc40a232924110532e8a00b151f00e1054418b2041154109bef61c49077c5145308cad547beca7eb27c1522eb902cb5a22c6faa015865143691384e5669f02387aa1086206fd8421256e8c102b4804513a690042de4a006f29a263f9328acbedbc9a9d7de38c77ec33aa08ea4ae9d9d1cee1ad629d2d7e972744c2f7d27ec6ff5da83b0dff011dbed357c44919c0d4bea92b73f425257bd8c4d2dc20e1b8708e0ae3d08ee1af7204ad7aee1234cf263c0c673431df5610264ac3e0cc28928f020092d82a616a0e9dd7f7e78a68c9399a800ca5878748526b658c3cf9cfdc81889125970a10c473cdd9d43d425b5004be75ec2617f2726a96b16c5a6d1bc82fb8c7197b1234cda7118c4f650c7f604c8d88683d08eb0600c308001110f4fecd425afd3d3b1ee1fd8aee1b05fbae9e3740128632916803246a120c10f9ef0842c78786232d6e1233eb0611e1c36c619b2a12ef930d49143b11883109e0cc4610850979cd13e639a8e21130e73340e571fea92f2291f50c6c220666cfb8cf5a02e294bb8caa84b7e9449dbc7f6910f1b374fffc8d32d8035a8ceaa5035aa4796c7f65899f5b13f36c8ceac90d6a3c93421cde7a7220d59a3a14a247df258651acf9124247bd4b59415ccb363f6cadc18696e71a7162d4a6da85df2520eb5285f65597ed486b27cfda1b00df4e0064e0cf9a9410d881093af41f255265f7de44d5300b5226ea7768a083f796ed71ee6681c75cded86391bf89d98fc48c21bf4f46110dc76ae7484f6d2b9f14777bfef0e8a431c55eee3dce2223aefa0974b5081d155657ae5125410a4872acb976e188456ba7619e36e18c411dcb7cba13047e71b16a254838639dbb9735847a42eea9ada3ced91f9080d1d21254d990f5292cf114a4461f247a87f286cce9e5c8f8c6679aeec8f6d0d06f2a62a40f9697790c8c11744d08524d880441162f22285a582ba18c111194c503484983cb8eabeca92526c9cda9a94334440199331f909018e358410c39ca9b3a22ef98939ea9245d4b50570ac91a594524a29a594524a29a594524a294f7d7ea494524a79d30c9c2600c71a59fef2353aa98ff451235959a50205844ea0040d78d006232b6881133438c113c2b04419a2a8020a82e429a7f7511a218216b9a5a813fb9879fa40519435b0ca3fbe332701bca1dc53da00793623e4de6614072d6404da4e089db2a90a705ee66dd6aff9392906f26ed3d30340ee6f4e4898ed91ae7e3d0f8c82a2b03121f711720b21f7654abefae824b2c69efede45b12490b0b62a7809e5eeba65ef86f3bba1f6ef3bb1ce5e1c32afa44c38768f2cd7eec9e9ee619deedacdf1fe619d1fa86bded3842efd16c0719b6ddb4cda8ef05098fc099a51580ff10871434414268b8a38a3a059c9a85f12ea97867a8eb8a30d89c264ac88e8c747d693bbc4933bc908d82098628fadc477a333eda1b0eefd964d1bd3543465f45b051246a5205dfd991918305c5cc23c52a2308fdda3ca63f7e49636e6c1f6ea77633b9202588fba15e0587f260e11d0b10e531a0538d69f2a45bf68307f66206bec5743007b90fb72e620f7a6699ba6514a5f850f0c044df60885a1febd4f7de84f2e62bedf5d90b05a5790fb601a78c660c223c7e1115ce171953deb1dc96dbd3cb19559564b279d74d2ea699ebdd70304045d695a6bc7ed666e332d486ead277797794c53f6898e45647b44ca74ac933740e90417609153b274420b92f277e316c1651f5b1771e09977de53aa03599190325ed515102177e5c9d407262119db89f5b7a18d682ba29b91ac99390752a63f2f0d9a32fa1deaced8140188246b6ce4a641421476fa3693359c51c69bd0362b01d85966f0a78f346876a3030888c3799d93609d0dfc4e0cbce13c8951384ff29dd85138718880d3c1e79c0e7e1ec43a4526ce014f02629dfe090fe5ee368f863932cd1df54e57c6c02b63fddde8d866d4153345016c1e2923d3f7fe7d1e493ac5a622b016b5d8591c7d9bcd5b8d54a31693fa35ce6d8bedc8c645bfecebcf764461f5679b6db36da69d087b5ab168b18fe48d8bdc3c990392fb28a4a4dcaf3f14b6813568057e75542557bbbb7b9b5157a3ae8c4d1280350b5c8f5aec0904ac582040c64e979f8eee36ad324d00769a37af29534a6f0bcb0a2b544959a19c9894a8522420eaf4799da9c46d9a94913254ca48192933815030143e75f5e9da1975f53714846910a5415d94c7cf6d1604dc84b6274c5000e93b296aefe636dba0c8dd6d4fb4d8b6c7ce68c90a5c657af93e9a3628b63db3b9206b26b64b1c019c9f69089c1f3d8d01d847d4d5ef3eaec43cda1eb18fb8b18f2c8d75d8065197e909e0b8cd7a72b7cf2c77d7d9576693134abc6da7d4304840ab949c04e54afef3630cfb69717b2f98e91674f265b4324bb74b29a594cbd5540528b3890828a96bec5998d3ddbb87754eef3a88bae68f8e501eda4383e88c1651237a4491681285d12818b1420a7a84c26451e5e931aa4714262b524da242b21a446192caa80f85c998e539524434447ff2fce97e573601a402ecd7d3772626400fefc45a8b3e6aa43c6f620238f691cb09212534d18534f0a08a1e3c3bc8810da4e88115d8705a343b15610811520cb048818f4b14c6008621f044e102354c898532d8400838e0020a90b680035218a688821186f004f9c10cd8881f5700516eaf84370465d62996f9069c8d32293f71729b4f4e5ec72ac4c78653c6c3c3938b9879fa00c1c2ab2e80dd44bf5cf038b3cb65b7aacb53ef5c6e37713ba84579ca03badce5e6051be0e4ded0fe5ee6d50d2dbed1e99079c5e766a5b2742b57e63a5bef2afdb1827b802f2dcabf45d93221d46bd55e39eb26ef566eeb3417489195d6bb940ac0d6655e396ddda4b5d26ae1182dcae7b4286fc41559c4c23446fcb032b05970ce17c0cf00f41dbefc76983c97fbbbbcb4de6ab5d8fa3873eb2bf62777476579bdc5618e3ecbca9d99a57582c7fa22663e39cbde93d7953bf30bebce9f137c3929d3f2d52d499996d35ba78c96dfab4d192db7a12ea30773471e5e0ff96e4cbf4c5dca2c535ccd3ff7e5f8bbe9a56e3a2d9dbe7413ee9f2cc0b17db27c577bfeb4b4f45b3ef6cc7bc98206594852a208464bccb0084a83142818720530c87e07863a68681e06719ad3b834f7c404161481831344787862344ed3519a4b83d2d0a81888264673e5670e9657af8e2be7e1e51396133cd67c7296d33bce8ce3581b90e91099de874e8cbcf26e56d44f7cd01b2b972c1db5b22ebf1b14eb2d2d1ced732db7667a79783fd98102a990c90a20eaac1797a2de75a85b4f6fb39ca55b70b8721b8afa917978f98485c2a1acb7ace010c7f4ce2f725ab4b5da6e91b9b6dc9c175766890690e784e29c16e5af0e8d0b70944542e8b87c7788d3afaf8d439cc63c78c8f53b99070fb9bf93eb9d3e93488bf22c770a6971c8c99d3f1690421b9c4086239820073129ee40082160a2044928c117a68cc8884924937868e1712753faee20b4565abb57ac2dd55aab6d7b57deea5a70fa54b6b83ac563496ea13b280e739cbc4f6f9ce091070f99f5febdbd7775c79ddc6261396179b73db9e307b25db932b3dc95b3dc95dafdda5808dbbd626df7ca6dba576ed395e576e5f2bb61398b6c11cb16652b2553269b128f2b348024ddbd44773a27273e683e19672eb9eade7ebfbed653563dad5dd7fd57bde9714705d37bef55aa7b55bff75ed5c989102740345f752c77a5e5c2bc6badb5d2d9336dc8d6c719248fbf7d7d79fdd80a7263e693cb86d1f8c8720af395fa5a5b8ecf3acd0f9a5b6ebfbab245d62ac504c8827fd0cc5ab929264018fca333ebe3311e2516fde1d8415f330bef606135cb0b5ef9d8c23f3ab37c058f128bb9c4b4d18277b09c75cb8277d015fed1f9b29c7e402c31653466a927aa77254f45c5e45d7734ee0eef58efcb8247afde1d2ecd695c9ab75cd60e3b9ce6d258edc07ac92cefa8b7c3a57196bbf296cb3acda5f1f9e15839cc6d39cdd5c1a37165bb4630af74e8b7eecc2cb76fb460d66d5a87eedaa3b934ce729b6e5d9959b7e51dcb1dbdcc721ab7b68be52d57b6c8f2f9dd58394b4bf5d6edcc724daeb272471355906cd31b13269714e5135c850fcfa27fdfbffcc5e518e6d8767dbb0fd342f2fd84942cb7efcbf2933bfec827f2cb514f70c8bac5630844cbdbb2304e0b962d36b6f4e3c953eaca29ebd4be597ef26e09591887054b7c7293d6595a6f966669dda659708a09d0e2a334ded13a0bde01f33e0f8b7978b9556172a47060706df1057ff5c6a30b962de2d7163155ddabae8aaa54f6f544f5beaaaf54ef133cae547d15fe57fdf77d5356b75abdf657af57e9da2aef6c4ff1ca270e7152a74f9de2d194593b580f6dc87a18de9175128667d1bc05b39ce6ca76b1e0b1b2b0dc95d3f0293fb136bcfc70ec433cb24ea44d5b8b4f2fcbeba51f0e3dcbdbae9cae9ce01daca76ed32b7764e1b06556b9602eb92926afdfcd0ac51b79c894e6b03ff909de4169a7ded744529955293c899080770e913fdd49d4a2fc7767152d4a359840ee43a610c90394535c21cb7228cb29a85094bb1e928b2c3f9b48966f22fd6afce31d24df83c274f4907c13c9f7ac85e869a69fb7672dca57286cd34b69a64034b9650fd9a345397fe46790fc94c94f9f2ea2e9a9831e74610ebd610ec5218711303f30776637a023a279b29de5facefe54fb4361ddeb6d1085950ee41dc874fb43611e36c942799394efb04e118973ba9bb08ef7ee3f4d5c29e5c4405f4c5e4b44c6ec4f7dfd64252b6bb19aee114572be9f7e8477925b59ae07eb57f5b29eabe7e1f5331466e2c055ae62677fc09d1c0f87f3a577ef9e537af71cd3b99bce9debb87bc4e9dfbb7b44919cd3bf1f4172effd8508f04e72067c3f79277912df4f31eeca5847324500d6af28ecd546aee7a49424b59ea412e0cce17c7719f3de3d08ef1d3ea2480eeadf8f00ef3d88ef08d4bf03499d3b10d5497e64ea929c6c10b6443e8180f2a3fdd1c1a1c05b7fea4eb7bb477d273613ced373ba9f3e7f3a1d8575243d618ebaea753a2ca98c9d2475d52880a09499c72133112b2933bb9fbeebddee74e50d73be63aebb87bb74c71a595ebb1c9ea12e693b3448b9aaf11a9c3d3dc547d05bcc05a11d61af49393f72f61ea11d518f802f01da6d9863af7134cc99b65e3a9b1e75f265dc8a6c67727991c2549fe14ede3fe1b81f2159cfc673e4075994b5e6c9dcbb1944e9c9e4ae6ec955f55337d4be13a31c883a79d7ae38435d54ccdc9c578032a61ac5cc1d69913bc9b58f1448e6ded1abd126c140b4b9e82314a6038f9c8e16b9c7e8e56e6742dc6d1077d5b5a12eeef60660987324cc49bd9fc256465ddc39fb9339ee56688888c26612924da23029f3f909d2664214268bb4218dc887c2b86f3f1b11f72d88fb36b347b4a29f19e36eb91f141e37a1cc5dd24d96b90f8f5b4fe63c3c6e3c99d38e64cec495f0688f32b799fa5ba223e2c7cb688bb49c6d15125b22fa63a5c854467ffa34a84f65d46762131840c91d9d3b0aa2861092ba40f905d16298535a618ebadaa683f882d061ba4dcb8d7b69be9b537b09eb94ce618ebab4f797c37dc33adb39acdd515c713a66a8cb9af08abaec915a487634d60873b8c9b3132bdd0dd3db3475511795d11915a246f4a8f2d49e2aab3ef5a7ceaa90edb1322b647dec0f1285c9213a44617396bb8f24e5a64554c8a528a02998325af60570de7617872224b8a2dc9f72da30d121508920b9933b4039940417a5201ad4ba0ca2ae964dd73cc781e09c41cdc9caf3f94cb500d29bbcf084e6c9146b432d6a4215077306e0cca3bd8150e5b999da1047575383a2456d4813b24fb94d4bc9d1151eb93ca9bddad0d584b4a17e1119d2a0d086b421a1fa9305386a429a9026a441d1af216e0b4bd995c7f991dee436fda2750ebc3849e5e1a997b9f5998275bcc032e7c8a1580b379135b9f4e541607d0bc91a2021703d045903d403c65aefd32359b30559837a9f7241d69c640dcafb15095973f27e0581ac297900983812ebd72059b37abfe2a0e640d684ef571dc81a95bf78bf0a51582daa43acd71048da05599be8d7ec897ed1a7aa0aeec74a82918e21b77ca469c8bf41c761dad0949832c820611afed86ac82e1f1b0eb9df58c81a0d0bb2260b6f2dc89a526f41bebc20c3d030f8058b4a4c1b5b0ca68c1e8d879ec75c1fdb08b9df44900270e19e55ac75173c82b8270b8d1aa809dd20f796445dbd151551182794c2a3c9c526c7a309d8824790058fdc0aae452cbcba33734214c6c93899102713cadf6d77956b7a784b4fb9dcc6ad5657b92d65569f974a71757b3729ae4e6f27c5d5391985a1a46bf55e7df591f3c97ddc44fca8b97ef58ea6bc9432eac8299888cf3cc71d1c1860b661dc7141a678d4912926e2594a994945088090ed77b3da56279d4b924cf2b8252591e024144c4e6872439883a2c58a81c21c54175d74d145ccf59113ca3aae1f0182d65d380cc2758478172e322f63e2fbd3d17a373d978e172600c1cba1e09e38156421b9391c7881c810285090fb5b0ca68d66c194318f8414271ca4d806398f4c36489979eece354c35cc2da4cc14af519edf7e28ac9390ae5902ae57f0b3094da43c474d28f79b0909e3215d330252c67c0be48bbe0d798b1ae4ee429e97d12190c6a444421e2790274040caccd74b81fe0bdcba1de38a5e9a7582d93e99d5af9e5fcd1d13b34a56ac1e279d74628f36019ab2d06ebf83ce24f2bc77849e7328731ec541814899f91938d67920f915733ef95d473f2a6fdfc91b437ce918b7e97ec5b0337753ca494fd266b0bfea4baedf50748f34122f621d71868ccbbc8561bc85752a088af86028a6f3e21f28b27d27b6e3ba0bebb8665a8f790cd6c9e12d1ce6c4c478ccbb4a048e764651842bcc605868011c5ba6c51c026a7308f84d277091c536aaa2034267b28695eb7c028aae059d4c5621571574411d13a03de570f79305f0f280a6244abba08ec81070ecdc455d50581ba1d8a445ad4466008ab9dd00766791fbb036d2330be0683a92fb62d67207a45b1a4f6e1e59238ff48b0bd905c97d5bd4e5fa6abb90b02993aee6447082dcfd516b41ee9bb6306941835b5826419641083e4f98c205454043d27c225701cad9dd148b3108e99979c8a22018a0d806e138cdd2bc0424ccb2ccd2e33e20567ef138f3bd78c31cb1675500398a89df89b96e98e37aeb2dac03d38398eb3bb14ea56be97ec051c200ac9f5580e3948dbd0670ec64ed236b66bf64679f7e75ee5c5feb859836349ed82891729d4d00ed77538ba60d7acf72c91df5abde9e36c21c14093e284502cff6dd543cda571caf74a33280f640588c012f737847e9f43b2aee39dad1b301e89989b5de2f7b580bd776d5b70df3bca30a97667e4d1b3257af8e9e1db9a959db0599dca6c157dc0549eef6a66943d505b54884526eda18b9735386bd7d373f1c56bb1d77327d697257754d2507ef585f82532ffda65453a9aee4763d3fdd05a53a1319c0393f03cc9c0251524f79ca55d454823a8ae4ddbd1df4da9fe396ceddf147e65eaadcbc46774c1c12015e3b8e9c32a60c8d7ba76938f3aa8317c7bc4a95b5ca7d95eab4ae9b1f8eea158ff57466eed40b391c12513a771c8e9ebb38f3dc4b17c7c415d71671b873dca457dd91fb37e9375bd25ec22926400dff44f10e934fbca33bf534ccc3cb26d8e2f14b75281412cf432929b94d97dc59a2dd6e252547f956a27178442949555309361dc5747a4d55858272fb303491413b063f7188937a7dea158fa6acdaa17a8a967a4aea2929d76e29f59494ab60ad975ab782af77e4e171b78889f3e5483de5297854953e1c33cb0f876aaf77864494bc1ec7e45cbd098ec9eb4b2e0e13fb8a2d4efd7cc9c5513f67677bc77995b71612a1dd1e879b32c05b1c0e07771cfb7aede298a06a9c988797553885ca1c5e30abdcf1f9fbea8e3f504e5e2af5eebbdd3873ea3d6da4febd4bdd2e48d733bd009a3e76409eda611fe3c9f2cb418fd0450ed2af14c6809777a00ea4ebe9d777d4bf37c9bf1c5d26319de4df45a12a0e7168173e68174959bb0fca4d1b1ae6618bf27801eee305e6c70b943e5e40f50b801f2f50f2f102261f2f70f2f102de2f80724a4faec9e47625d703efa7baa7d24571776aa8d38b7a3d0a756bba18f03209e6519253160aa02cca3047717171ca4ff0386b6adae8a229a36b505e5f32c99aae06f57a4fd6985effc99ad4eb4fb286e4f52859f3bd1e9435a7d793c81a95573cbebc7bb9262eb400613e9a8e988ec04c1ba929a3a35ebd275b14c91a1f264779df4641d6f828b9f7beb542d6f800dfbd6fa5206b7ca88cc81a1fa54f41d6f8984fbd6fa9206b7c70f60a59e343bb91acf1617f7adf5641d6f8a057c12452a6be7fbadf4549997a927b9232f5a9fb49997ad3f5a44c3dea7652a6bebba65b9232f528979332f5e1c9ca869edcf8de7ee5b242959b821b2480ed311599cc008e9608f5d21159437315929824299ee4ce38cd0fcd33d3e051029906889aed65fcc81a9acf93dcd3bf59a6c12d1d057086ac5f210e98693e827986907e853968c61e9906efa0f321cf696ebaead29c84e403baa18949baa9e0bb09d25c7e37244fd1a6f2c3a16f1cf6d41938eca149d1bc1ebc639d914472431e9a191f6b0d7bfa455373f21195ebd882f2f194ebc892ebc72f572fd72ed7988f61ae26181f55581f4b39c6c7945ccfc91a92df2dd71125d7f124579beb689253c71f4bfe91e63aaa727dcb1ad5eb4892eb98cab2a604e6158f34b7ef686ed8130269916709f059422037b53437b60b793aa438e6c88c1bb3c50878fa35c218f5f46b3e0412068131c2f9fc6733327290890123c625be68c1bcb8e09b0303691e2590610a6f615961852a292b94139312558a8400d886dab4cc3d5c19f3e1ca18ca12e5f953c8539dc8634c52cda670862c4f9c1fee8c2c378ecb638ac9f450d7fc8dcb638ad12d80a14ff813068544615198141e61f1b07a5832960feb8715c43ab2c2b312b4d2b3220b8d5833161185499e70160acd9067c52709e9c8885594c3a13cef03d0cdb59bcbef467d4c528b33868b3cdf75407212b9a3df8d8f19f987fdccf4cb61b3adbfb97fa25df3b316e7ac46490d59fb344b8e4ab2a09f5dd3296d3d492903e4c363b668713e2689c2608c7cc0303149d9872b633dc018853c797e8431caf3a12cf40979c29e972cd61a28d234b05a0e02ed9ab5014920d2103cacc522b84a92424784c793e96f18c48f998500a94be2e8bef2284f50cac839e7e4290a9a090d51983ca2477a36df45443f3eb29ea47a449bd0c9977a679ed286c793651639b4326fb7693b4361df7e907e452fb99ea1e4e58b7763e60efc614ea2493489ae7c0f892b08a4ca24118e8489d455e516729551d82eafcc2285fdd0b8d60f082af3bc31b3fc0f14d6d225af7294caf2a6228c83337da74dd927ed61878d7dcc2c4fc4ccf247bf2406e2e87b4827b4a027ff90466ed0938de8c953396c02472e574a29a53d654fd9264527650c29233f8300cee9026983be739f881f3503f18441cc58fd8c55dce3c5eac1fecc210e2af75119a8031573b3f6e55aaea0932f4d982ac7cd39e9e424858da07c7771fc5ce7140efbd4bbf6b4ff9aeec538a8bd2997c21c25dfbec96cbaf6dda0aaad003a5ad4fe838e0248a2ce6bb1ce0f0727041cb9cdc424e59bc9894fa882b2627def58ae7d78b99bdc3064dd0985d013b32984e61f6a47b3ca95f98730654bf966a2d2039fdce4f50838652caf9fb3f9c41482a25f21d1a953b9db596e4ab842b97346f30f5d46a1b96339c126dc6d37ed651567cd517ac96bf4ab8443fb9dfc19e08638aa73577d27975e2638cc61b2934d2e73ddea14c286ae2b1c66eea69713fa98954e7aa2793bcd1b04fa15e26c57b90a1e59df7088a3b29377727d03fad58016ab8ded2d4aa0228bdcf23077d5b342ec607dc3428439b6870f714eae2b0ff114f2ed4ed99763c359790b1ec3afcc198095759b576cbef1b1727bd6955264b9fd32806366d657f00d0b5729c382c7f00568b166160bef08718803011dbdbddbeecc33471461fde29930887b7c8c757662178bddf6ddf8983477dc94b3397bb22aa759058f1fc82bcf610d1e6c7ce897caeb0a0487a0ae7ad4bb09b2d4cfd97c620ab558af7227142dd60f513924994b3c6fc333089e3d6f0029eb256f408bf536ab9c75c392571e1e3025054f21df5e8f80e3aa478b2517a7f2f0645635c153c8b75b116c97c7edc106b074d3b5d103793b0b4b29aa5c7e3966ae5246058f2a0f71015afc70547088f3ed2a58470e0a6008b478835861cee2808b24846cca2dddde99b99c1f286c9ede492214bc5a0358fa945d8cafcb31d6a94c0c21e6827fb18e00c2304b8a6155a944e506d0f4438b95d657ee3bc96af5bf688cc23e1c354f998e025cfb28895c8270f99c5fcecb5db08ea4a20ab19739d790841804d1d4a59964454425a2e67125b7a8d63e5b1e707b69fecca0299b3ee311d9fe5e7c59e94e9f21c7ac16ebeb16a0493bd630885f3e2f5f2ebf2040ea9ac7975f8ecb5fb04e5884980be672ee31d6914da0e28a59ec62495d50582bc3e19d6ced73a2b05e4e1452e492cc528a9c28a098d6d6296b57b5962b699b3665510cd9a1957e5185204d64fa91ab583b58b79ab5ab7d395148118514511469395114d500dca2206a51e66d1c40a6af16ccdbb77501eccfd38fcf61bd8e07a610c4084f6c5ec6fa861513916391288af5736a0ed2d0044f6c5ea7037198c5fa3246b75aebdb45a3188a82a856abd9967500b266ced3be9645275fc69a942b48a9890b6098436fdb766bf63c36a6618b79ec2dee1975d56f011c7be8a891286c26f5c8284cce846a0ff11045c18815521851982ca2471449a8be0e2155a2fa5a447d68d25027d5f7915a6bad3eb9ca72e5c9f53589071dd854d612adc792ccbdeb343c7259eb39dbdab7384d9dbd4913afd65c95b266f6ccddb79732dcff6aac55af45cebefe459c5debfc6a5e72dd4dcd0c355e62d4a85ea62300e933fd3833d6984d4105146c6b9676c0a12ebdd3b71180df4fef9a7a37050370e491c792ecddbbbda75b4d5a004779b479dc4f06a05dbc217f78a62b044c550087a0408bf5a62c80a77b2d7a1fe5519532d6f33ca09e7b17883e468bddb661ed875195b793481b079832eab7cbba71f7a1c51f8c80dc71703e66ee7e8069030152468d42aef7e413727db7bd7e37362d568e03a5bddb0f3e4c508816735aac3f5c21da551bb0bdca1aee1b1ebf77f636e00a2185919ce3d0cc9de6e8cce12a65b69f3e79b037de735a887e7dddb6e1d297e3bb35c4f16eef61af08dad51afd1a2d0ff655dc708d162be67c68577d6d9795db5eb70d8738da3150927968116cb1be468dba5ad52142b1bef25bb558bf2d81b48223520861084f7c095a300ac110ac108445ac1e02435018275d36a70e118ad1f795b16e7acd800e831598488af5e4f9dea069da535ce9158f3f4aff2a0e21fa254b25cad2216f1f4be4952d7247ddd328b1d8ea0cc299b03079f65575edaaae6cd73cfd80182516128b799bae7db0ed1b40f955172495f23fec2bb598c3725e1e5e6e8ebeeb5a69bf60ab303abe64fa0e085724c504b85d6c917e9b38468bf422877b6609dd103c5a9c736e9eb59ce5386bade5b68f5c0feace9eaf00b89341262761f3a7f471cb76b4d246e9d64e20f3676a585aad5ffafbbbf3b77ddcf6e158cce259ada890860129a24dfbd309eb5813b7e1585cf2d50ff4ee9432f69d894a29a3633d90351c9bb7226476bed3e9d7c87dfb8e92b51f0e87e7ab467b6a9a264b272965ec75faa5d3e2d43eb1f6dd50694b320d1754f185182579f2882dce4b2ef2d469188f21a610f336f33fcccfd4987d2be8ee5ecd2f474ba618a8be3ed1c9178ec2bacb4b2790808b3c4e1ffbe9438221d97e0eb144ae4e919cd2e924e9e91acef96ec23adcf7d2bdee26edaad351f77bdf221aeeded8f4f66e69fef49cf953e9f31ace41fdb3475d759d22397d15d62992737ac9fe84af532447f5c63aaa7be02a25925cc6ac8ca9ae8ccd2b63a891cbdb1db9d4d529a28304ebccd8e9881f9e6201584f4f38e573844e9104c818c547d0cbd8288d80cc03a94f62c668919cf9c63a454204f4bbe7b4d6d337619d21a8cbbef1eca12efb898f20f5532cb07fb1dd4e6d3b85a22ca7b8c24896535c2145c6594e71c54f66c941460ef7f44aad6d5993d695b9f5ceceaf65adb5d59a98bcd5bafc6aebd277ad3bc1d66dabd5aaad9649ab656262d26add5e931ceee9f26b59af7eda4f6f5f21545e97d0454fd621cb25c0a1080a7b28ea8364e90255efba136f7e2578dc91b95b7d37aad7daddb665a62a575d7595dba954aa57954af5aa52a9a8542a2a55a57abd2aaaefa6e426df8deaa28b0b28bf9893937f46f10866fa1692f663fd09ae2df4aa1f4521d36da11663663d7a2694fa6edaf51f2e8aa416a50f85359176c94dd641249de727d343f3a75f32979f4a98534c1b2d9b32e4e5a70d24ac8774c9a35ec0b88c71f94944d6c82c8b00fe60161941b08df8717ba8011c6592113d7e80f446e7ae42cac8779e21108dcf6c3d209acf1f1a580775c9d9cc395cbee3216b482e2fe3f20de8d7cce587e817ca5ba75a1248314eadbc53f1fad6cb9830af87f94e9671eb591a8738f7e597e4bb393961b9e3cebb596b3d8e53bd2cba93e9fdd19975f23eb90d65b92af7be88293cd6bcf25f7cc79d8c4ff2dda8dea1dc8e7567bc6bddfe6d87a94ee912d587a3749356abf57e0a8acef6575656ea0baed9d6df8bbfd795172060deb72f9f415656f01d7fd0bab2e3e5f62befa6b772f919e0e5c33f3abffc621cc27ce52c03e0d4f7eb57baefedefac151cf68d77a4eccaca8757c2708564e5e00a0a8a1718982bf34bf76fd7be239869eb18612df6b9e3647de52bb8c795b3ecc53bba7e40c0e0b1f37dfd3fbedc5ad6570e735b61f08efebdfdf88261decddbcb0fe7aee0ae5f6ee73e0caeac83f79f3775ee86ef6eca6d68a924448b3366cc58f98c8a979836589d4a0a06a0c53b4e7edfc730fd3ae53879cf78bde38f19385c793deb2c7a983fe5ca16611ea658477d38c679fa80f072adfdffdf77f3d6f20a07faf4cb71027372faf5bd4d7b2bb7d57a03fa757aabf525a68d995b47b969b156eeb89357dedfcd8ac5618ed3ed719c6eed090ceb277727f79d4b4c19acdb50d66d7de59a9ce85b5a5a5a6caf8264ba626161d158eaaa572b9b666961b92b7c65beba314e2f8cb7c85c7e37302ee3cefc74bfb35c9ae56780ef33f8c5fb3b99e57419a8ae754d2eca9d3e58d4cb2c3f21244e0e9781735acce18638dce9533c966b33c9e577c3740550e616a045f918f78716e5615c1f48ee3c0f2923af83e38a563a78084161327694e56f670d0d3177661f0ae032dde5ca1c7365de868052a0dce5f2ddc604985d5d07826006efb8ca282fa1bc7e3738b4d9d545d9f58eb33ec0ecba4d775de7fa93afa60e8ff6215d58d984425d4ebf1c1d98420ea6f0935d2df7eee8a34776dd74975b44cd2ebcc3f4968bd78764bd6bb97482b2889ac71e79c7bd318236cd523dda2d7764c13f6a6ed635b5cf1a40fa16f9299be821fdd3a25cf9fc6e5a40acbcf57b477cfc150c4c0beb8585f17db9cc302f2b5fc13b5a5ff9ea8e2d5ffddeb1e5f7355b8adf77bc63af76d4b364bfbcec35cb592ab52da717c686aeeed88279fcc8ad1d3e7432cc65c6d707cd3038cc9129e6412d0c30be210e7e1fbff161600eb37a3759b2722db6dcda22beb7a1303030abdf154e677c7fcff5ebe4f7a4a5efd83aeb2b3f39c123cbb86a1ddfd64fae6cf1debbba238f1f79e5f7acdb554a5fefc7293340eb2ba72b97f9be3f20ba1c528cd3faca5b5fe571e5addfdbc23b564edf5fb17807bde98dbb09da2f8a77b0dc5ebef2f89159c69959aed5965bcb7244a61e20424a99b1945b64a6b87f5c0c30b34bf8096217165dbd65729897c3b8bcf84cd9143287f4e8d1a3478fd28f8a726f787fd4f0de971ff547cd7f7d398acbcb57c7263030b8b57a6bd56ab55aabd6710bb75c5a3875dc37d5618c5fef791a0b52527088e3727a971497a7b83c2525c5e52929600faed2d71f35a7bce4f29414b0033bd0e547cd2c9c7294db50ef82b93b949750cefa6e70e8e2729b4699e28a2cf2c9513860de82bbb1b6546aa790ef66e5606be5f47b69799d36566fb9366ddcb79c9335f82d2fc91a995b421d341e86415cd2c034a771f9eee4f626ef685c1a4b83e60403d17c0a6991a667351763eca1c0fa537f562e3f2e1ad8b978a87bfa015fc053104ced977c6981a720c89911b81ee4fe8b5977f1bbe9a130ba5a8120ed4131d2ae7e17f3dd4031aa263cfeb09d76518c508c681f518a7217b5d8f5e7c323e7618afbdca59519a508c5088a51bf64092ee9b4574c81226cdef1558b93f541c0d485abd4f202e38197e2a90637445dfda1d34d1169e04d1149ead23078ad64925ac904d3b27164f4794a9de4a32cfaa941256da2fef47cd1a016c7139db5b6b554d3eab6594e2b8141f39e7ea44c104e33c6931178904f33903690329ac87d13d77525cf337d5f773a953c14ea03c1d2898404bc40c02b01c68028f198c94b6850bf28518bfdd5eaab75a6485227229346bfe49e7e7a871b4cd7f81d8d27206c4f750908d3d53fddd30de6a461f1788a224fd786c7d30e729f12f5ebf4d32722b48adc4f059540958aa4a4a494323129a9330afb88a88bd293d43dcd48565fcb4a4df4a4642a414131f14eaafbd51ff0a87bfaa14414769aad4e3f292625aa939386614e3f3465ba292a66ea09334585e9ea5416a62bf5859f1491514ec1c8d9a712f6b8210a4b3537f4a1d50b674a116929a21f868478f974b4be1baf55fb457bbaa44b3b559c22724911bd103d6e08009926e5314544448f3add74204a70b1d2950063aacb8b2e59c35dd6c068b2a454c44c58259a321a48a5a2a5567597ee3d0149992029034489c74e369034faa719c81dfa239a3228ec84034a94fb24c483dc2727686f95e49e82205d5fd30d8fa7a08abb02a96eba272348191e4817907459950a9f82205d7d4a346d9c7e682065fa23a522f7ed294cc68266f92494eb0522b9e902a56e495e4afd7443a0c3c31323b9eaf680b1d4354ecbd179c3a79fd415814e726bd0999913ea9e88b4ab0fde53137dca9d12e0fc78fac96d7374aedbe907bc1c90a42ed4f68149d84a7bc3a7d8e9e7f4236ba490ac914b38a13eaf439950256e4369165529aa278d538a48ca8c29a21491155648ca28eef9fd1ab7d76faf78ec6fe73aa900505291c450e6be5191c42c6bdf4024c09679657a49bf647da9a45f1a1eb79b7088a3bde4b548719448728c10a445b963bb769c4da660006e7887f6fa1940c3f2f257560ad4a162f4cb8a2dcacf17d8a0dc592f060c259b27006d9025f2d91fcd6ed9e211472e1949998992c2b33f282294142d4e8be5872abaa82a50446400ed479414794a812aa230d30cf4509f369403577934cdb6a3a4e81711aa8a7e494db35f8e96edf6dbd476c81635bcb298d505283f72b21b7209f9840402507e34cd72e759a5b3253d516c9a4999d92131cbf3f2a6990d02426c51a2923620485c3d9cca43394ed6549ce6c9f4146b44cc3c6ab93f359c5b44cb7af24173c5b643c2ca1a79834ebe0041aeae5679c6e8975cb56b72d9d485fd4cbf28ac797e560f71409e4c0fc628e141d6507c63668bc116419e4c714a0520789e0ccafe4a4a498728e10d5918a184376091fb3f9a8bdc425dc8a32793bc4fb09b808459226d9b1843ee4bef9cacf1b277af5f40e0bdf777e3d99f267ae6fd7be75ddbc4776d90f7d11221729bb67d9a7bb4d85e6e1bfadd9a89d826bc56c21bb6f0b80fc7b3433a20d4e57783ba4ddb34787be8b4ab8d90493d7eb4d8ef41619e97bb3d237e7030e88e8301d1649d36a866676dad1bd716edb9ec4919fd8a479d5cebabbe4b3065cfcc9c69a80c539b91a1b6587ad799ba528993693748aeb5564cbb4963d0cd3929a53caccf54420fd2607d8c3af9022c39cad6274f61e463359e3cabe90de0cabe748d87f5c9f6dab52f7dacb684c2789833674ecd8779151b1e6ec0011c25914d5b9ffc0385b1da3559ac59b3fd1ce55692ed8dd1e27c8b33c90407706469afcf356e3409934b204212590c618311ba7041cc7e2561262b3082177a60840a245011b397d29e93d6be01f4660f1ed6276bd8db30e70670669987e05167b2e6d9bef6edadb53e99a5695ef5b21d82c23afc039d1608da0fb8218678021c250ff9877e8d609635e307b28907d379b0e95769bbf5f67299fea803788b130d60d5789ed0996cbadbe9fe52cfeb458afd7122916727c9983c79361093939457c95b3e56d60a487825b898cc593eaef264e570204554503ecee439d680f9c88394224f3cfea6de72e73bd6ede8eaa6de55afd3bc960b3e2575d645bddb3c94fb5de59efe72490e73e7f14de570c5779e3777543c4aa35cf2586eea2b24ef78006307d9f38ae4eef35aee4a133130c7c560c4c0208a6c913cc4bcbf49f92279f7a7b4d1bd4f254ac262f494711de46ecb0187835290c90302bd1b7425decc6d1de67ec7f7f41caeeb291775930bde062a1748fccb057a81e2f2c2c4eb3ab0069f478395a77249ce72c1af5cd467105ea09818ae9877285eb7ba40316e22832e25062a300887b03091bb162fbcf305304217e46ec5077bdded5c58c06a793902f6c797918734ca249f6f0ec61392bb963773c5c35cd4f105ff7b7acbfdae82fc72815cac0bf4e2e1056addc5b57a71bacced5e78e1059f82ac7281623c06c6b56131deab141144a5a688022c8bf158eefc8aea2780217381142982cb21068ab912ab5891d4bb17af0b2f90ebab981817aeed82f118efe77081142179087ae4f013b10499b5d202a3e42f82e4f002c138cb058a598981f1935b848c47029911cc78bfa9f7c810018d8732df72497eafea2932874030ceba40312ee3d676c1787f35cfb50062deef6478dd6db9403167b940317e182f8171d60a8c77335ec7ba4030de42720c14e31d8df70bbee5a2ce724f5fb9df73b830de5dd605723dbc40e2552e50cc53627c7581504e6e115c36c140310e5ee6f67031f19d0e20d8018997d7dd22b89c7a2701e07d89acbaf3925cefc2b831e01551ae2bde6bf3e2b6ae0eeaaa1eee3e7bb2c68226d88027978e0169942dde5171f55e9a2585198ee3914bf2d4e8bd169bb22806b714807f992dbecc378f926cede7d56a7784a4a38215f70ae43aecb5d85c6eb00637046e3374829d098fdde5aa9c9235be78d2351f234f2fb70c71283d8f926cf14eaeb57253d45c2f5f7d9774bdbc64f955b07bc2a0eb26d894528a80aeb55a6a2da5b47259b61343d98e33d9ced0d9436514c669efdb9f19a10b5217140dc451d7764f5df6f68d06501bf391ace9db2b66b21d67b26d5c429232f6363d5aa16c1fa35f618e7e8fdc629b6653246456d66383f46bbbc55ca6f29d0562b4b2920a87c70e24657bfbedda202d5a4d03477994e76165dbec16bc78f1e245abe572b55aa278d73bd1e572892f5a202b77cde22c29f17abd6bb7bb1dcafd05dd5c85a44c5f6e24db0eeef42377ef1ed7628a09f07bbdf67d7e1a96e0779bfe3010caa3303b4371f73e5ab044ebb87b2fdde35a1c3913ab7a95daa1afe918682f21d3b7ce923532c5040ccc847969dd550f91c187d382a9ce64cda5f6d62295ae662b963c71a98847cacc12b00a200f9593d6999431fd00818777c5d699bcdbef7b9fe34aa757eda6d75bfbeea78ba31b226b78e42ee777672e8d3fb2566f70df8ec30d91ebb71ef66adacc5b9ba60c5b39eee22869dfce5d1cda372d6f58563cf2e0e179b3bea0e44404f29393698327085fe8e44b96278efb70d9f2ae4b691880f529dd29971f8efc424679d735f5705cc98617788127e4310138c21801dd1cc688c26ef0ac321ca2433046fd82c1a25f249c1ce2ecd015f63549d6a08c7596fb152535a69c8795659477a6348033a33c05a527f8a1909c9898befaa9e4279492300cc3300cc330a4a13949f70d8198f1a6298c33a38de84052aeacd4eb0c20484ef30e67601c122c5ba4c1d5a6984e637a637b159aa631d1e01413a04ae31da6d3e01d25eff3b0b291621e5666728b4b9e8252826b8b261ffe2a088214fc280df8d5f143a92727ff48beeff52b5ddb940692874f91a09c1e8584a4a2a09090d0a090ec641a7a9b3abd230f2bcb611133d7d730c423c96966b0d0d09cde19b26c67d0bcf6e4d4a9c927a768f00e92a3dcca32090e9fc2724754504e39eb8e1d48ca299f0a96200f2bcb45cc8c8282638eb47019c7b5e1841ec8961744c0a706589802154e9860074c007bd3b440aa9fb9562fe469119235353efbece5a8b31f036c53210a40e0094312522637334295924b0a9b395d366c868cbae64bdea9dcd5c39bf2d495917be6a85df33366cc58dd98941bb3451f95bbe9cdb83347e19dc9e2ce20c9e49b5b63304627f33364283366f8ccc03384182151d80d0c4c52bf6a86216b312629260b982318a37ea53e0f8345bfba130a54b954c19dc233334877268b2c629062b4b879e3bc73bcfb6545d1504cd214ce1093446137dfa668c151ac0717c41cb15c80e3cc1110013e73349345bf90648dfd2ea58d99eff2bbc15aad62c2540acfb8fd8c4ff0a6c63b0c4999f91930463058c01c4999f919d9b421a50c92cfce30421486836f403007c711004b0a239455475a4cd9629c59f514a414a414a47088ac9979de9404aa5437f7e1f6f07a47db93c790a7e6112c814172c7123843569d2869efe1de685d7f37dbb6c5bdc1330e2c880152ec668a400612becb08923528414e87d143614042d0c33c0c190c21fd32f93c0c9f7e9534894a51ca69eeea2597e42697e627b71ee5cac8b487d5353915e2d3c3ad31942b93618ce6558aa64a519e33405a9c33411acff4a81ce579131ac031266906c88cfe9c213b31b9333e33438e282cc7e75590288c009f5739a29244613d7c5ececc66642d4e1529628ec81a92cf4a4329cd69ea55a45021ea571593d21e4e739c12e094d2192133ddc3f56126296b01ce64d1e29c39aaddfd4e96ffe12865209ccfc8280c07c31851d7bcb961ce8d155310014fece63bb11ede03d6915c5011f3e13bb11959bf4af9e5314932e66524a53ee3b397232973048e3046a291949991753342fa45f2f9199f182d4eb1c52a80238c9191949934a647cacc6301ece1de87bae673dc13e05e465d3f2dda8f29482d33297306709c391a6766333f315c8c31f21893240307793e75c7ea849d71c7ea842909ac24ef09ceb038c76101153c38c74981ea34389f2547293229333f9a5e72faccf4d3474a55a653d377d0abb0aae423a5947e9f2a31210c568c1519161e5a02700198c0dc01601b977c725a9229cdb18faa4cdfb206e59424d354a67475ea3ac5e377f0dd07821f6832e10ba9f00c9f19424ef74f7c66a3f439a3b16c330001048007991830625ce28b5b29be5b2e9958a14aca952b94935b29ca887b498faca12823138d94aefe6e54349724e409792897556f999d47d6e8f0799abbc3eb2c3c2b45fda221feab15081ec1668e60e0276b68fefc5d1d5a9c6600c71920343427007c9c91e5c9835cf371864c2649caccd3b832e20f829c8c1f2933df65e8f668dc92a3eee9f4b2e49923efd3f3bcf1fdf23e8a4bc8353e7e1693f41d59412ec132436400c719202440fa35a6720f781a50cad471278338cc414f731a3cf6c8f42499e21d5675fa999b2e3dcd25a832a96854180887e2dcdc1e9ea234332c897dfd0c4b42ed67dc1413a04da5ec662d090d2539b994bc98c09cb4505eacc414d70c16959810062b86aa6545c674ff81d872243272b849c9c48011e3125fb4605e5cf07d0b4b132bac5025658572625272fa40936a068732caf33577a44800a0c61d4b6d0476c7521b79dd9122b1838c1c666cdc912bdd6813dcde6d9aec131c72fd4d47bd99230a0bfb23b85a1de5f930c8c750481e43599e415a4c026b7f376eba2d8dd7dd04eb8c6e7e3937ee83fc1880e33de030a78769831400c9623d7c2766d33edc30c787dffc06ebc8309820e6c37762f6279fefa6ea2ba416278aacc5a93aca477b1a4c72639254aaefb027c124b727f9eaa3b524b740907c873dcd6d3ae6c864c9a145c6672e0dac83cb0e2faf14170cac55e305005462c49a30068c1596cc2d1e4d5ef2cee4c61cb9315bc424d1d0949ce6a389498a394283c7e751856790e094a4142dee0c215d004753519e1f67c8724c520a529e8f49ead738431673a45fe38c2c668b182efac57d860c5bf087cfcfc86a005003f6da41071a33327290890123c695437cd182797109ae2ca8348c53f6b920768aa1a111040000e313003030140e88c462d17844a42a22e47b14000d95ba506c5618c8410e53c618630c0100000000020000401b0002f3fa33c23d49de8902398b7862d01a817206309ff6344aa98c8b98342372dfdd6fac262749009cd397b460af7347f7cab8cf4eefe5a340af7a79e783db28c6682d71ca4985f462312efec52ceb214b6d29efb5fbea9f8acb311e0be88d68b7281b141d74bee3ad7f87af9d8e1f70d28b748e161c761427d13d2769c1cd8d26844dacced4ada68fe3232c07ff39fd99a78aa3d47ab878d7ae93831a2e7a3717f75ec6e85d9a5d83c72e14fd6d22a62031f879805c20b2a22cd832c8ea22969301a6d84536f6201abd5d641200ba32b28aec082ac4ae3c6cca8ed0f8a1cca95874a22d4d4066db1a4d09c1635f90ee2c0e2c8d4073b00b6547cd42ec653a447f84665e0a4c27cd85392b428b91dbf06ddaa3cf21c205981fc1c494e56cb4ec873abf029bc88311aed13461e0c1460cec57763a3631e921097175b0ede5954da1411ecdc800445efe8130a0ecbda8402eb67192acc8a9aabde7b2d0c31e85b8c046db5e2f4034d515fef2dd58fe40e4c73405c9c232b0927ced98b48d6b61804a42002d5fbc4d414574f4316cae9d317c66ec3bc33c9e4c8b03901507458043cb39ff650c5b1fb52c29da78d55db441f8c919234c25d726e5071806ddf03a7313096955340c34bb43b881f22f84b1ead139542474aedb66123e4bff6801731228542a712835357dd38f7b8544f77d288f33af6b46a3d75f3277e5cadff086a5b77323be61f7f26fc237d8bdff4df8069b5ebda0e08f4c8e0600627237faff285d5e453ed443c7af5062ad8614d56996c140a2eeac8dc21b5bce6fa3e8deb185a1ab79a2b6de8f83f4d8bdfe4df846bbd7bf01df64f7f26fe29b6cf662e5009f62c10da3f77323b869dc0bad08e22c0eb9d1a017831827afa2ab1c4d08853e89654a4e3635194afef4661004f701417d66de38e04bc3b52d5476dae2e2405f66d9192c77ca5d071e002dce3d6a0eea06c928931e5637b0136d9b807163adaed66bf1dedd02ac625a239a3af9d006073ed32c3ff464c0d7481f9a7c7636de27fc00df60f7fa6fe21b6c7ad5b4c0cec5e31b766f9f8b02b37b430a6a0fe50fb2345b99489312f05fcf799ba2dfa33ae21778e0d38deefab65d30c039a4bc5b690c65519f99f20246ce536230e330923ef08df67bf54cf8d81c84b9375313a254233c7216b79beda4069f8508dc33cf7c1eb2f6c51a13bd2205d532c3959f38ac4a50e10fb07d1d0bfb8cc59b63444feb491f6f6ee85423071c822795eca7d5996b6ba587557994a29fab6caaa678ae640afcf91d5749b3b68485f8578b12e0e48e1239c396eb20d569aab5b4d49c890ce7879ecfb39f67bffa7b5b67c2666dedb162884f3381ffa61bdebcac4bc46ce0495a982fe7c2d4d9f7682d48a222344d71c8af5b06f013a612cc1efea8e56539d5cf3b710ab393369d87a76b53dd4dbe425147c06225511d351db85b68dbfd3669d9f6504e9b8af3ac1f0bb0e539fbc41f04488aafe54ca757db122c2addab0b1bc848ebe3a23ab55abc66ce44b3e45cae5ee80d044f902d24ab7cae0148a58f769bcde9a1f10fcf2c09d33cec7f03b408bdb4ef17cc99c199987bba6ae6911f71b29715a3c8cd78ebed8f550f7f64d9520c7409a4596fa8eb2a8691fcaa6084c66883f693217205f49f84aa3cba65163b117a5270e89c68a272edf758e91405877f94fea783a8823f027688c42860bdb83190bce9b1231c064802f45954ae8c110b096ced822cd892c2c346c7eb1593f941d3315822c4c0190fc7d3d3c0c9c2a491cb02a31bb6668888970b9bfa9c8ae81724096625aa3117c3890bb27c8a521b02d81614f3fa650972331d8cb3d6c4f5d6a64c621ed923698658f625efcfb09ce3fa5f3b95a3ca73df66ad6aef2a93fbe35e1186b2834a00f3cffaf45b30a4aaec778b2185c49b57ac9bd51d3bf64f4afe2b82a8c480e357b1b2b5750987ee5e61850e42f01346a8a0493ee3a63952525bc578b13012f16bf645048acbe2a0853b68b4879b8dd064eef4962d573a9ffd15c8c2bcb96ef60ec6d3edb4cf3aa445f823bd14dee4d55d92be76f3e89a82062709dbd96ad23165dd91791bb9dd57aee3abc75f8b0b46212964215c7ce4a3ab49327eefda85fa6cfbced0fd7afb02978f7c447508cf8c66727f86a433032d9011a82ff77da0ea94f024fd6c23415b4449bb00b9770f8c3190b2642015731d62033dd9f6efa3e561b7b9d5b3bd01c4063c7a542296dd0607156d207543ac34b671f8086b7bc421069da12fcd30f11fa8dff7168859dd8864d314bf40ec1039ba65b5def18868608e2f9465b1809f7638a4fbb66d57ffdd4a1e21808eff2819aaa6f3cabecfdad715ed82ccdc214df43facba0bff11375b59e57479109abf175c652b08a124fbcf21d15a1a66a43e881deb5a440785aec78e3f04e1fcae30fe621ce634f3ad91d9857aa69dab8c171445e8d2a291ab0a3f648f1978e2dc3f97201d3e38e05a6d1b95a2c208c0ff1b9b759c03135d7ceb675047c8630bd0bdf4fb89b0ca2be7d897a05730cc3a57640280850dc9da65df38965e576a19ce193107d8e4b9e4c2db58d1b9b69fcb16af75bc861c6ba6d293689eded062d6d280ed59acb6384dd1091e28dbe57775ba2f5625f44e010a21589b4d80cfc8ee46324e689723af1189bb986c6a4d3ebd5224f647ed8b015f4fba731c3c72dc558465327a88cd20d2d97aab94a52e3ae0289be4d5cc84570a5668058b886da612665f56a2b72bc7e486257916f9ae84ead31c7a7a18e3bbb4c054227078b7f9b17090121f02f274cf88cfb5ce2ae6c1a337559779e8ad99849d0221beedb508fc7118d844d93d7ded6039af9c8b464813baf6234c3bac0417f3fb674107d14324417c81c88991eb4d954d2f9b57fd0c7a43da340e48b3f051e4d0ffc54180a21dcc6423398d2650590062b20d09ae640ee4de02ecd92ad269b64552ed2fe9747827be22134c1e0fc0668caf092e23ca15b71dd4bd8098777d0e7f1af67c98c861a30347b37287492ba0ea79acbe9021fbc5eb5c2dcea7949bf567eb0d90e2de8e79491e6f5411d36cc916bafb29883f0a113f48214ccc2e0290b8fcc32bec5cdba286fae20cdd5910ede05994dfec96d6d1b5a788903e940bbb223448adef7140675d732ed25f9345964956886371630b56b2495fafc39a63f53f196e3e8cde4e001a05dfdcfc1dbf65a3cd0e992ffc6e961dd433b37320b52d4771086afca0f1d839dc939dd35dcf34b55d6cb50bec704befc0dbec70b074c2941d2e48ffa6aee355bbf16aae6309c304ee681d7cb8f2530903b1b04c00274c41e5688185a27388e81eaecda9516f2e25a32ae5a9b0c8e62a720db3b56c4ebdd7b52fecb6ca57e2aadfa6d9920ed4f2bea4ae0aec82836b623feaf5e4426587b975abafd7e27440b0672f25f0d262a297fcb29cda6910bf004c22c93e6919b3f398147c184312b49a9c412271fe837a1fe1799ef2c53af9bc4456a606690908a5a44f48a83921b7884f642755c487a48ed241543e31614f5d137be8bf3e2a15fe92b252acff320d513877bacda2d2b7897a033a603af5136f053ad781ac588e7dc095662abd6de6157b7b66db3c4b61caa322b512eaf6cc3223050ca51529b8692895c7ebb294f09816879fb51ba4343bd6060b883a32ebdb8f46096cd3e908a8060984feb3efa9fdab52afc6109ec1a832f670172f1317192e337abe4039181782ca8b95971960d32fde41700115b2f345ac0fb2aa53ba04e649ed353590a2d8a44e164a19c9e4c5c69da351e215214ff46dcd0532c80d11dfb20144998af29c2e09a9b8cc448c9f44045b0c806685fb151ecaecf2280b9b04bb5ddcfec03d7fab32b53b0170183e6f48ed082bbd98352b74d608d9fe4a2a998df5ff73e318cb2d8dbb8032e864a8340403dce5f87297c22baed218aff581b5711559e2f351b88c866439038a9f0e1b422de24f67c5a635179b8ddcca59d004d3abe4db5dd04598fcc5973a637e8ef61059ff262b25978c4733373fe6261d5384afbf599a1d75fd866aac9339c22964ee3387c8c09130b69b9d36e3f31c76e92785ee1db286f509944db8b7e41ee83f7320193fefe73930ce74f26286d2176a48362b7ef52d3f1a1a807abba836ed4350547777f191af399d9e5de23306899e7f97c6c9c145ae38780f9f9c7f50b74c4c3f9f5a6b0c5c9d049d68214a85410f74a60e990a2680acb0f90f1fd33452b6393a2e941824a94b2d42df2ab67f77da0c852a87e2896956cda621930c4e3ff37962f9e857d48d70549e63981f7bd5f4846f54dfbf98a90662da6b36651fb556aa54a02ecc440e9486339ce66539e3601623ad09985033006189afff1552c2257307537d34870640e75c4f95546f01dbcdaf034e80e282e301b0b0e365913b171294804db7d06224459dd8df6a708bef23c29058075d0639375c828cd4c14a960493dfed9b4bd3df0347c88c54c6a8cb01ac423d082e57497da0d76c159d81babae755fb1a454bb25007c2aa19d92401696552f713dc7ac56e83347fd2c36092704e94071eeb69e3a6f1d6bf1e05ee976a5e5784b0e65a34e1705478fa643f9c5630fa911b1a9a487c876d23030f6ba48242fa05decf4356059e51f76069b0dbae520f7c2f2ba66aa3ed9543c8eb272f1fcd4f0b7d43c6e964732e9f01f496daff3055321f844ef4eecc69eccfc03aa0b3e4ff98ce361480439f6cdb57b4509d55835d1a9ecd4b878cab22899963520409ec81be9cb6120a92c4a30d5ca0eb575d737805a73e7774b689d42ea2b4363c7a06a5792461771a45323ac2fb15a25e40c92c259341341d56f94a686cfe48454b9fac1ae5319a804c7a146b52b5630e2874c3c610a29c0252663c0a862248e9266012f08f320a80a0688b094d896ceb8aa60338831a1060c853f2b7aa4dd307d52485fabef9333e72b659d51263e65f479b1c497dcc050f17e5916905f9e28011e6c6308ee6019c1eb0b469a53f4f395aab47ed0a3f52205f51616e0506273f4de888124f9c20d48e6b6e88276ada21807a062592bf0aa9d4b5663861c4c586069b8208dbc6f3346995ade1c812219e5aa8c684f23f2b2df93f8714a236726b4e5d2abd1e3984775ef80aa57b77a441d53bc30ef54d7caae31b1b525ac5111823dbb42a616db6f870aed9d56eaf5fcb9f61dfa5b1c4a8b29919f4b981d82838a854d2c501db035719ddbee9e76896ffda5e8fef170fbc90c4246a288a386392d07335b028cde57efdcf1d91537eb34617f457b2121bd99b068877cdc32ecb9c32421a5055e45290d0dfa7e3d922efa082014f56080ad728eb672a4661657efc570238a5bcdc451ac6a4970103e724109c15c07c63b58506f3c1975b43805fcd93305f7023fba5e2fce7a2e89f4d3be58b3d780b5488902c3cf4fdea059f4cd1deaf1e1bb27f95f7950b40b0c14d73687a9544794cce50b583455b94e29f760910a03aac62ed7e1e602794fb6d8380d4aaa7f17878bbb27765d820444f2666307a8d0d36a61e0346e36cb29275c786a898e372018d94997c71899d07edc34dcedf8249bad002b50d094165947d8d4a347966ae13073d141a61d7620c790b55a48dbc5cbc6446e9f06f12017608af4c70710d6460d5b5230614d77a1c4705a4dd6b32fb0cf51e22445012203c92ef8d8e01ae64bb8795031473574908b05b74c526b86f268a8d4b647b26531d248612d088906163c44a68e55387359cedb3a11b412ff2a859a5a2d4bece5d0b671f95d08a192040d46dcee3bd397f8cb56fa40f57ac4258380291aa5401deebcf7623e993835048003b00ea9182bf5a58bde046f4cd12689ab052f4258c0e14d46e742ace1dcd34ca6ecc8bab13a2c8423c11f104feb298428001bdc19aead174d64a04001930cabdf7b5d878e49c6b2088488fbc311fcca12b43a749c34900a8c4d4ab799bd3f322a823d84f47f8be0bc4f221047d2224635db5a36eb40e42f8fc41e8bc16c4d768cf743319b6728a645b564cbcaf5128cdb44dfdf9e5876879b95fd45554b7d815d9e6e8c151da9f005fd2c729830aa038623e879da3e97c7be79e8d70d814c53ab747a2d2f41665f0e61b100a8de368fa2c213b72783d41ec200e93f592dd83b5e98b4c05fc3dd417513c2075825cc855146306eef5ebd3250d7ae7f33ec0c1a99f61757624cbc97a767d1afbd80cc900037833622ed397ac389473de61c0d1b3c9c10849b78403444e4d0f620c9b4751260d81a22399efde20b77782c9756ea0fc32c50311607fb8cb8a5ac95523f03d7518591a70c7246d268550e7c54bf640bc2988cbffd88c202afb09b64c77b2bea3ae67bc6087fd332ebe1493eeff43efcf1d3c568e176857bdcece8be8bd58702d84befa7afeb9ea7ac1d24b257153512a72ab26055b782b2a46eb500e177be2965d5c8d3afbf3c01aae3cd99e15b65e40b180ef73c51a30bf5ce6422d78eb815b50cef99c97e3d0fc0b0b958389a8d1fd4f881938d46e6480a1981ee55a8f6f1cc35acf4f503674442d871cde0307c449e5da8240a4a70543924b26cd77946b0043dae112551eb6ed160aadc6a9ba5e8bc66fd100b6ae9b3c331e07c648ea5d0d44ce79624c7a2e4b983f5bff0c0287783b1439439843d47be4f041741dc40a70d351ab02c670451fe6cd0b29d2d78071c6360e65818899115fd0a1b5ffb11e4e27ff9ace9f564ba07e40a1c70fc62d30f5a107ecb6cbe417fb084c3eee97b12f2857a06dfab7d3eea0585055dcfdcca23120128b8954c1bc5ed5ae5e6e0d7c950091603892e4d8d9f7e3b6da4300537db6b9ac8fa07e6a43d4c92c9c451a1e0e9e8cb0b1f30d3d58e1306b666289bea1b79585d74f6cbfd0760f21f1194dbe9017d3b7dd7f682be41144a2811073d79d060ddc4ed8728eb643840f221832bfd49b3286d72e2491a77b3346b65d8e0e781a79ef97feef2ab4834e0ae1942ee0ee579aaf156cc326b0220f56a30212226aad856904a34136137e70c150dc5569c063da5f6e7e740b08a72f8fb43b2a89d04e866e58d4c4f82846683d5632929cacd8e78c4606da1fb262e3b14d8a070662d6fd32b0b3805ca6c812223d9c4beb1480d43d9cc03c0b227d11647f822127ce06c1c2c0b9c44a82117969091d4d618470e255e0ba09b49f012813732c8b7f847b5e9e240ab778afeffb2fecb99a7e9f985b3ba7cae6d60d9ff8fa44f0b42b05a5fd800adfb2a0b9f91f09b6bf5d568085d9ed5a1c0153412c72688d48178d34d23671eb6a593c30bc8c9fd92bffa0c7fa801525a7fc5122c17cf28bdf75740bd042aa02bd5f330bfa6337f945d1ef29ab535861370677e3c84da7bcfebb4d030c94a87f22246b74d31e4536ac11c61b6edab86d7b548718b51fb19a7569ee1f308adbeb40ae1b52d0514879c46609480318900002c4db8230dd370d26b779b72dc865bb2067d388de1f8f0635ddbd6e14d570d9666db6ecaddb512ec70235b3bbc7371ac025b7a614a0927306b2335aead7517513987e51b929a3e750dabb747c447378c8728a0c427e808942a30f532e8c74116e9ca21ca623a82c91391f214698b288fdf43c467c4017c65c58edec3f35c5e0eff0392f064f5904b4b42083f937ebc098fd137164d78bd2429061793c60cdecae64311f7a002584128b4f6d8c11964540a7a502accfc66e0960e4c9d37a38b6b0df1648d2c10ab51895107fb3b529831e07fbca22e03d8e2763f3795aea244257d0e203ea3221162c015d7315153a9a5a3e8957aaec3be139015c89095d05e8dc0e26eff577f999e585865e221c692eeb9e7665e1b8fc3a607866e1634cd11040524aa4181de30a3c1336b0693e38db14ec5e323678ff3cbc7dcf14639f9690ef5fa718430a60b99f1a5225b97b4a3386a0d4cae4c3be83feb8c3ecfc55a2802ad7e78b825f26e59966c412b38b8aa869dd0c6cdcfaf09d6dc6bdb3ad5f15c90f8569d98dd1893bdb72f54bafdd1895f872b63ddd0d31e010e91763d043e0c8e6c2492ec6d69c108db0761968619752a5eac7a215b9882bf118ec169773516614b32c36e735b42222c5837fb035e4b0a05c769c2e39f698af9581a47202d78aca31539327274fba0f1671acd8b334118d20b7d44da872727c0c92543cb9dc09e9da29f09a66fa0e906b3be71499d7c10a34c1ab1b899c37eec1cf9b48ec80d84dd33d17a3c6ebf4dbd0c58e7044c5077305f1584df8e84e665673130046fabd20836d0bcca6f60805103dae644fce41a1735081cea1c6adbc8d6d7a8433619aed4b690813fbf267c045bf1e31bb6b0723502e5c829c8ba5091934955305acf80dfb614d28132de8af4ac9ea4da526587c9d74fbc9c7016da72a9a7821a12c0b7f6a8a8f7a7849996113a971e7aa40448c91600c9a6a403d220627d79df1dc5b27c4db26cfa4b95e0ab36c3bb95980fcdc3a98f8365812fe8ac97fc63315f31346dce9b223e4ae05a77453d61a57913ec23ec6d373efc3046363da199b57240fd8a808e4a12fcd9a4d681ea2aed4d1cf02a5df2c4531b10756f220e035c8e493ddf3e9cbbbe427a85d72c232cb6f16e92cc1f689954bd578fadd595dedb89c09a224c7455bb9e1a0598fff098bd2de97e08f43542e3471dd18e9408041e62bd31ee8f82db17c2e74e7677a573f87e15380e50b8767287922928114cb1f48e801b4860c827f3fc4867f1e8512196c362bb0479e91995beb97cd2018b49e9111978174282e8bb75cc11b7ea3a30129be1afc4b3b87e5858072c8bde1edd348f3d709f77f92900b53c7b4a403e657f827ac3c38c4b49a5b800140fe9a77009594587d7226f1efc74b60b382bd7e1195cf38e98f05b10355b6dec1040519e9ea1bee60aae9ee9780f5a17ad5b0cd21bdb51f606012b40ea8e4e7f801b0698ac332c18536060ad6883e7b93f29b2853c45ac8edd36c0ebc788bf620514b3f23e0127fabd6c9cc612892a56793020ed27a199e7e9ecf460c8f653d7eb853ff1090cc421d06195d02003cd6a90eb3c012bb83b716daf0510a5f1c1488aed4f34717d36742503b4d43c73ccc1005ccb6465ca9bf92071c9e20703e3ca1c80ece26c61da9b39b34906c0399f8b224c1ca59cf45130b345ac372a9ae67c23a0b514bf76a8cb6836ad42d1ff023d2951c5e6d9887e813b40354c13e6e4fbf15a10cf7ef2dad3786155392dafcb62a37fc40784c0501f19f6be0ad0fe2755b285e59bcb278a47809f1908954d90da583d20a1d90fffeb5acbe02a2fe33964f4a58fccb07d2260438d107a478f3255b1f43417e52c4f6336110708a094902111b82c35b29d8f2f7857058063343faa85517d650cc02b03aafd52cf5c992484921cf6d99b0d555e84d00bd26d56cb1cd5c52f6a637a968994c1deabb502ab991609797d8cabc350269ee6d772eefb25f9ac864605e62a797d410dd2aea08ff4fc195a89dfcd08ace06b9b2c6a2c1ff358c6e0b252aef923e4ad5af7c367f186dd70b8ca46a0152bd684065b9b40b5b522a1298555cd670bd36ef2100068540e6d292c8e6a0612586dd6b133cc3319374e47179400a2a7077baacc17d92dda731a45845349ba41d1bec1cc82230e2b51e8c47a3f7310aaa39fed6d35b584059702c7854465f93009b1286afae6a9d72cf81e9d704e4f067e5c13512e0ab8c6d9cf5f57edc39804dbd771f1ec86fd19700f559fe1da6dfe5ef733d9c18adc791cd300892bfde68378f6d1aa171cc5e78cdcf8617ec6cd071357bc9f5d2975141d43b79c0558c719d1cb24ffe2a503d2fe691039afccb1936e400ebc4fe2fb8c3276f34edb818c6a940188db178349a81cf811e4f65cf1a5a4ea2b19cb83f382e8d66b4298721521e8c4379647078ec9b93e7a0fc258f32963c4e475e828a0661e24fb94450444fbcf21c50b700392c4f3f01b3e0a71c60bb70b985892bdf5a8cda6b7889d10527511848f91739604d1bf08a968d928bc5376020a81d905124249e57050f9e2e5076adb84ea9bea8313e2089120eddfcaffb5494309451b14a9ba66c58dca494c69ade387fc31fd9a2d12ff69f840b5d3aed439fe37b22f26ae4d8bca9e0c699d8195e2c814b75cd33cac8ace0ae504d951077297827c708935b20f7af595b04301d0635e9930f8c3240d7f0dcf9f9b8bd803e5612013feadd3f363679472e2bc2b9a538821275e522192e5a9c0ecaf6b15dfaf20b93ce8ea4e19678bfa448191aa6bb9cffdfe7582f9908305629a0cf9ee6c788186acaed31b164414ad957e7b8e733d6240d7c3df00fbd5f2b2467bf5997ad2281fb69af60057dfd22c8fdd6817a2d0d775d855da8ec374bfb947e97552c21aaea486e8192c7b5906587787b4acc856f618314504a95fd66699fd2df24f52b5455912ce96072d12bec45055fa3c4973aba2871abd39bbeaeba8ceb5f5e252f2b4dd36a64775054d0a9b468e7abc94459964c918fa20ccc9c07e7a3e6e81501f89ce2d9cc5fabeaafb95a94fdbf4d448f7c4713ddc4f14956d7708d6f96be994306ea66a233c5315d327ce7f746db159b800551e8ea147e0a7c18e495c7e153cd8051b89857f2c96094ea9162135c4b92b25fb89aaca8bf8ba5f8b026d65100f0dee2e86ec3644a0d222e945b8ae5e3ee537806eff8d91d84972abc307c372051cc82a830872b94683a34a75fa2040f3ce7f4bb26daaaec80094791887a4c9f5c770b44530793a3cbfef0e1bbbc9ff7c3abeb786abc6f1ca7cfd477995bde7b098daa9cc8d30cf9c96920125a7d191d9a0ca3b21d5b60141dfd2849811c2642c5c0a9de9b3b22f98f322975dd241170aa8ee4a044bab0f059f32cdaf0ce1bde2a44ba599388be8f97bb83c7e7fa365c6eeb3e490e26f416fe84e95f215d5c2fe9444c8d5f41e21478265446e2e74702fede041b313089f43b590b085f9e2bf45347a9ad29b124d9938689692bf50a61ec83b6665885e36e242b28eb423e1530bb3c624cae0c53733e8aae78c913214d8fe0071077eef2500b753b6052443fc7dde4f9b5e4f25f904f7addaa8f7b05de354eeae06733319b7945c07a2b62311dca78e21fa8fc0aae7bb9c9185f3c8e06fd0e95e114fe33b3b89f37261e911a05182460e0eaea0fdc32a0cc71e9f910c8055a135e370162a33335ac54a4fbea622c18bf56b7e544e5b897f6758f27783200027489f2086b7b3b10ef09007c19ba2307de6383b85cf2753e559350a433aeb10ad2042b315749ba6c1abbd9a2db768b64cfd8e63fea3a4fd622bf1f625f63c5c0a0892df8dbfcdeab96649bcdd89951b4b4b9e61be8d6ed244b0298cde3c09366df126bb18cf12ec4b2972d1aff0cedb863e7af84b253cd765c8c62ee580a306a6687f357c2544e33a895bdcde3b9abefda68b6ca96f9ee4b998f791aa2502cf090a184256dad9e133fc409c87563aae4e5b0beb742857d1b8ab5f5517012cbff3b05a7c0b096e2203d4d7822ae1c24b40a7ac2af1de722d2bfbef82160dcf2c0769607efb1862614e52cfd0f539fe5cf797ebee7b85435f42a4e9e8a8d48c16f92f095ba52295de663243cd16077e25b8466e6953402def181da5aa6e2f596466d614447e2d18e0dbc218bd11f2e52889a431963a776adac567b018ccf31ca5262e62641449a7c8dbe91582fa511478cb7c2fd7b1add41466c3ef9f3b18e2d04cd6cfd3f4d3bd4b4b33d4b415e20262461d0607d0bac5032350019c8386b315b2bce4dd6ccde7fd1afbeb0649551fab8260dce49d616088cac38f24ecc01f0068c93879000368e96e849439ed90b08b4db5816e4e11c910286eaaba7d8ec4cc7afabe1010e011112b5f72adb9647a77b973fb259383cbe0cdcf2e38fc993a5a9a9318efc9657d5cafcb6e2e86030e284cceed7f3a95d4c89d71c3056300edc031a8cc038fe5470dbac422e24ba2646dcc105dfdae4a5459bffe8662fc1e1dbcb2f4a8e67f91c66aad2bfc3f8ca7f8e862453370e3725405daaea8cc36d8c2368ec2241a38604526491f581527a0f4520db08552a737025b27e82773443e238d58ba4188e6cf331c4189df824b3ed0d0b56d13cd18ca56f978c339f8fee0ba94a7b19ba43b3eecb73c489bb5228941521c70e98a142211c49b4550ced710803445c99d064905c15f059a14c65c0cce4c8b65028b21e09ba216f5df81b6c4bc0eb8213f35e1c0c64bbfe4b72acced61d4776b8cd8ee6430eb1d93ccaa27c59bbe7f80db2dc7d33f12ba6dc0cff464be9a40f00f49f7378ad046de40de95522e08ed69fd073a01688d473856cd737029a64628b055aaf043c8568b6e2f9d18f1c35ec67624c8ef04658e1cbc933198cb3a59003609c35e9677fc6cfd9d08ee80f3236dd476845776c491fb6ff38181148baa5aa70158e8c9f4b8ece3873cb0727f923e9396072c7f6d58f0adab00c77265aaabb46a8b1071586716a5c0494c5e576ac3646f0f4eb2d22f98f4118aec41112dacf8348118348f604111ecd058804692ee8fcaeac7f9ca8fac8e4dea591558ecf415f22374a6230496e9d9a749e14412d603da5df88f74a68e120ea91517d807c79bc70193431b6ae9ea308aab01ce1901c38ab6e0797f157c541cbdb83090ae3b35eacae8ee8d0c059e2876b9815ff8211650696adbdb020036737175c64a4074ba019aa65e8eaae8df9db3214c0fe14547ea89e96dfd96ab0e008ee0a9d16b64265bbcd5cdd6b69f715d7a90844ebc1629bc5f8c3ae50c157a73e62f0f9a5b853efff02eaabc1dfc3c2bba07f4e5069b15cf9ec03c52d74f71bdf6e33e891d1ee316bb25e9b86f558ee02a2ef255e682d6882ea74ee2138a5e74216f38dcb084657f94ccdb3dafe32635bbe5566a2bf5cb0cca34f2bf988f3f44a4320435a3d53e2d37bf1ccac93a5f989e2db0df9b6be000cfee60f41c46d779993082ee69cd165e4de3018df75dd39c519f7b7c8de68e45ebc666da1c41471897a5313885896fc80319a28074c42b3c55c237302acf336f3f5f0888bb49fa4c8c7953b03e92991caf0264d58408230292f584504164e7dac9316eae0c699197489178e5a4f70bf2ffbb782decb7bb190daaf66387f0c09eac2f53b5c0f8a7061362b66c8dc96cc25ad782e6a66c8c3bb704d373431b3f23a6bb4cc8d68c9b431972eabc4bf38787755800f258b3157c93c61c065903e5405bb48bc6be79692d231f802bc13fbf5918d6e57343859066ee4df6603e46ff616c7ccc56e5808095980bcfcfbf0c8ef1ec1693801a1674b881efca32dd5b2e8e76eb05fd9dc3071775a1b04fae103ec092256fef39820c1fbe935e76e9c27be0e94c60a293e45dc1252665c47f7bf2cc3c357d4ba0096bbc4cca003d36fa3f60b9b9d389cf54647b8a065735785c071896e3ac87cae6ef5061daf753e595f7f1d1fa8e21b1993d640e2ed37c560e52f1fd29320ea17fbe4273cfd7e048e9ef9709ecc3a84892c743a5f33157dfe7e6742712e831e9ae8a988c13b91a5c6d234f7dc23a7820e4e18ae7f370c71f3737bd429eebdd0a3eafe8dc512602f7cd4e7cfefe610634b23c681153cc8bfec1661b7d98d69b871268d610aae993e18c8bd2ec1b6fbb049308c3c3fa9ff7a527164faf45b10d1aaf7a8c9b232ac3ce625ca36c609bcb0f6d2f7fa8577d9d878aea4cd7431a33cb2c905cd5bd697c5e613550c83c003950516679552c1f90ebe039d4d488d12c0fa142cf189456237e2d874cf16cc80143bbdf903b47e1609d2b53b97f51c52fd65a950f6b64485912a862f1fe787cb6d9675b85c080387109c89e0570e47b079ba52a260f802587d62df3c75bf4d41b25e643c3ac876f58431f7174803a9c6dd2610f48c2cb03f8112cf18a5d9d27cb4f0709fb14f0f449008015d25cef5d3c3718e691a5f3c3c63d3df1369416c3b90bf08f044760db188f45a70dc672cd6df399813e76a0215a6bbbc44899c513cb744b1e6719c4e61023af2e4b2a38b83e0a891b51b036e0a24b3fa28deaecfc4d286099c5073bfd92f504ea89bfdf36beb0e3ef7e5121de09e42a702f7a5a7df2a355cedf5e038492868a02bf0d9402db1a491d869296412e7147d2036250927e381d128f407ba134a25388c7f9ca1d9c81c65e13ef86a123634bc32ae3e8dc8919ea6ad30e58c7d2b6390256644a5ed9027b9c9b8847cb664390dbb3dee3350e57032a00ce1a8d6b19fe053877d800bcd6d77c01d8ae22ee6b11fcedc51a7083c50535a654e531a5983a6b4d5cf94b6f0d672529557c0c36da4b0587f8664da8698d206237c2e2d6be7696ec60cb4e2e0151ab379469a953b2fadccbe561a370dc8b178f03ee3d91ffbee75a6ab8d58852f25284468f1e6f0a23abeea0480469917b40751cf4ec04f5ae526de73d380a0033c592f545da7eca6c668a549f842a272f7634b97cfa7a8c8684a7c7ade9494eeefda0d82b518e8c5310ddeb9a494285a475750a90d3797057aa2d3ffa95d5543524e746958e4b097c4f5b1b9fc467e55b378e22e0d53b0f76722c5a473994fe03acdc984af68bb4e109a3b743267c27a492b48465ab345bf397bbfdf9ba6b60fe3fdb3005dcdfd04a5528721c750b5ae0cd8483f12218c5c142cd66987c352064815e8946e62dfe51314652e98bd4f319f3cfd144fed1770cd9596069d3f58a25bd95ebd64d792ce2e6c543012860132f02292d21e983e1715906dc8fe2001fe9d0a8640e14e489c1944bdde7e1796886916999c7ac765e06d3e8633d5a5da7202a280a15e08e91971280980afa58ae676eee2ad5db6043cd8a3bf775ce4c54dcb35045f775ce88ffe800ea647a9619f749219a99632f5160bc4c8a16145be542501000f4c60934aef4374c291fd659bd5b342b94a701f717ade05b38bcaffa8f10a95dc824f697113f88b14e08569be2778f21c41569e00c3025ee3f87c988360744501ecd7e878306d10b53ec36163b8504b5fdfd12b5e96f99dd209a8730458d51ba0e38047c2c80727f977b4a5f7c415eb0c0fef1cdebe7a51de35bea932441f4f8ab2c97c19980e51e3ff63566451ca1f23a0f4a3c07636896a94444528c1ace28418d8c6704ed112a3115cf69f00dd8f6f461ba47856fdb4b472d7d573bc3dfaffc92afdcdd17339d832ac73adb1b7ae8a6405fe3d15dec4d2fe19aabe699011d935bf6a05503211e651df27048cc6c30a2b7bb7198cb030514dca44a8568a0be2cf88996332960ba0d247b695ac41aeb8eab321f434b5b4118af132aa3dd341e34cd703270c6ec1bdc1df76291c89f343706e04542e258a0ee473406d532b6898c71a0ba9407c7d251cce3a4006c05bf97a7e7f4f55b4ebe1a24e701e7fa60b940567e769c15953f6a7c05758c42071bd33bad043a2681d3462ead6b68aed7a2297f8dddec05046e4d95518c55ccc6253cb3352a53dc5ae3ccc803101aa34fc87b1344d18c1a0a383f04ab7434a0755ef4cf541438b28aedc31d2b14f6364e5e9ecbf42ad712f686c359fb3427f01065acadbe1fd83a7b20c3e90e42906af0c53de5c1153f95036a6a857a1d97d2fab1fcc1d8f82d1acf2e705c4deac3a6146e3bcdfcd0b1ae2a4606a265b6ec9abc4ec648d996b022bde2721d4d98af4332321e6a2a3d86d0df3dfe64327842959f683af8375b6c263ba594daf26a345ae8e1cf81db081bbc8648f76ffdd096cf06425ceabc4f9e465cb785b6be326c24c633c75bc71da0a93e24c876b9753dff1c6f61d7a3b77aa7107ad90c10eb41375e6878439a3a9b568b028a92b120565fea7eb107f6dfeaa0f856673bf818c4d78b39c1acc05797e8c03c4548b59021d0092ac7af07122bba42f9533f7f9fcb952ed74040540b48499ae8350a53c34cda621100d6062966a44b99d86b76ac607459d99a668ab359f596cbe6570cc03376c9bb1d638d3be9a5f396c16cc1f516eacb60e3216d641b4c2aebcec446c329878f0257a48b791afa128b2bf0e54fcc1c1bfca209e620966ba03bed4cd259bc5e7563cbf252d1d9243869d46bdba9b7719ed1a0fb7e6c9017f56a6221871e69c9e393582a2d1b57518c6e3e3eab32f32a576ca18882e91880880a4402f48d7222e14fe5c1f3fb87fd48bf76cb860e195a1e53b1d594afc96343a4b895620829ffc0e58d6607af887c15cad1047f132de4097d93e8482f30d179533393c2e43fc8b40aaccc30e482c3deb971ebf7e2c0531c3f98c5cd3ced5940ab880b0278e143b97367e2be5b912c9601b155ed495eb4d34a0bfe858b99ff7064fde287ca65f24a1eb4124dc15fce08038c569c167cea2335bece0d08b06fc539acd7004cfa06d7a90c697ebdc98e2d1ad04139a1f18f484274ab41f9caa06741af6d8e2358bd406df87858148c31dd3cbae2183aeabeb34749a7671efc2bc72c0230de70060da1cdbd31e28029c5075ccb58a531720a6820777681858d39cf9221928467cef12d9e708d1bee108d103a6aa98c9892e222f1bdf25cd75eff22098a87e0264b01ac1439c9726eb6b0d140bc315008d7c4350caed1f65210149061d5bfdc70dfd1fff00ea576c1da34a80b2bec26106852ff41e64e328d76abb3ce7ad6dd30b6aaca0caa8e0fc9971239a0daba5d9042effc05f1c05c4f343a95f9d282a9bf4ce2067ccc9ce33f863b29b44b67c0b63b3064400a7f12b23107c9cfb3b4c87c44c6152c659cb3351799dbf9ad95520f0c8f02f952d52fb0a1680763022b6801a31124d553a989e62056174a8abb433d8c46410c069b051cf3ae48528458f041418b01ce7f073e134fe1c8180f93934a688ed56988d8d6502e2e9a72ce4ac5c963195e3ca72f86b408a6fa7bdc3a2409e59fad5f011af4ec358d2151168619609736941072275ad3a82f95c9efff100fda748af7fde2366e8cbbba521cb6af4b72f81136af2f1475e741e1829face541be33dd8855822c9ddcc33291321957577d7077fe94394f7a3a82fc1e6d6b58ade04b00099517c4a1490e9416d60222d605b7e269199b8d6c1a0d602916324b9ac359ba973f4e2500b3075ec90a10666eb38a03638d15e798e27a52c62c7e6a9d807661137796b1e10276bcb4a45f8a8791282e5b35315cb67b5900fcf865f4852ade382a13c3b284daa321794936f63ca2f9e3c0a8812052b2148f0de6cae44f787efaec9424561414d4ee0fb0afa27eedcb24035c75ee78acc5dc809e321d89a6cd56603e83b5e86ba26ca38666a1b4ac22a88224480f4f6c306fd68678cfe4751a10e31e3e0b34b631a0496c64f4111c7e8136709590f65f6716ac9d541f47fc4e9b6bd7158f256756d875002b20ec31bccc2d1784218c21d6fd9e8b365655f0fe08f6193883c8b2bb4a9e2dd18c807e2231896078612dc8d35b543dc42865ab688ebc5577c20c8d044d5747d72d2e6e003a98a20fdc90394fdc9711eec050ee9c63f08be1e65c55f69f7fcd4fcf564d1eb67b402f4709f1882bfa3233c8ae6fc363e5013e7619da72cd5c00d325f302c063730aa13d0ed3456489997464ef33b95aeff54f6317b5fd7d370308100234ccf10c7d189decc3766d8e1293d242600d1059638ca05daa5dd37ac8d4c5c701795673cdc140845ba581e15d6ce1c51e897ce179dcf18d1a7fc7ba7377de1505b43cca9c4eee2aa97ce47a4fdcab09934efdca3ca8ffd1b9d99e247e55b879d527d5a85526bb12b6a80e7da5bf27a3b5789dd7e0ae0e388139e3bbd8d1cc8764a36f9cff07235804fccdbd1e6062ee618c4ea3e64e9659917cdfa778a2a251e09a68766560732167ad01338d539b80339afa40e2518f29e4ea1fea45eb5331c0ab8a2aba7810566818839757d749cd40e232ed6f3a12ae6f5591dd8d0ce15e3478e3acfaffc04e27eacbb36d2986ec8ab7a14d0331a847192e17b68bee463d6057c5a3fc4cb2a5e0046121ca782fb5c95ea7844c342052e72385f976b3ac331b3284bab1ab745abf61683d253c8dc71bbe0f208f04e20ec1cbf7eaffac5693861dec3f1b21699e7ae28d027388a009db81e6683df161e73f183b26d494200314e18837349d102f494604a564f4adade0809e0f01f11810e036f3f7652e17f3e1fe9794a6ea0cfa05bebf73f6f1f94fe4559295ca8b2454eeac1f201ae01d7ad263151c00a6be6b28b741459ff5144f7af09e9621fd375c277496842b1c5295d13fe61f0145bbef8623a6b5f6d67b38aace0d22c27679698d1614785beb3029cce84e82161fb3b4d5ce50aef6fe60773e0a9c80f53e704047e336181c74a5095150999953fff50db0ab4d929234eb559df585650721b7bbae4e74fb9ddd933dbb88b215ba67749d3ac5af127c7a2ef3a7fc35ffc437bb99941860cf5abe02f987098102f2b0e6efe4b7ebd2ece123663f01190785ec3857494ec1a02b1f835603282c2782818f627100f5fa7d07d6146bfae4f3bc7bb626aebbc9cc5e514e96715520ea051fa0c79f4fe2e647f6e33e33d003ec39db9772d011f46634d3b677165a2d93465b0737a97ae4a6cc40cf8d4aab6def2994c7558fb9d21e3b8358ff54c89f520bbfa68b9d2f9f6f544d15baab5ec747079f4262c7a0bc205b5aebbd84acd7dadd0bbb99e16a53a0bfe51064bf368d13be6944225fdaf83c2203ab2614b89ca92ab530db6122e840db436aebdd34ec9d29bd90b3e6f668892c5163266172ee544790a283144c0ab1936f4342a6bc6da3e48ebd4694da39b28d26748d1a51d93a2dfb17eaa24e0a6aa23682d3db7c5f339592e9ac16e3a9011a5f10987dc4b96cb40ab32f0de58c9479e150fcb8e7f966777d6bec09ab79b228cdacaaf55aec363d84e11545a9749aa96028f7fb904bd11c9c74683a7921173d788309cd3baa00cf22f2108ea35fe3f0a50a6895ca93b019d9ea73cd568f1d428cc77e2101ce0aacf515e79e691aa1f1d4f712eabbeb09a009622d5394b0d0847e0b95c87b8d479ba246cf9fb8757d6a5a3882ecf31759aff777480f8ec531923af2a3bff18f8cc3cb2ebbd29f352b54a64d2e1b6381881a877c1a37db0388baf122cb35c3551b809749e08587c3ccdb10311f6015b4439e24ae56a08e8c71d222daaeadfdcc6cae79efa01e86ee8588b3d7878a61fd52f22d082a62667baecc4057abd9ecaf0e76e974370684d598bd2b97b6e68e26844baf220aabfba67569c46fdce78456cf0d8900a82f2f9238f6dc38afdf1cb667c80be9a33aec385098b636e861faba027f18ca535e175c50b7e5a68462a0b9570efa6dae9f06575fdfbc7fbfbd28d2c5f31d7503d631261270daf52a33cad9011959a0e0cd56e16d69ae88195aa4a64e9c0b79dff20130fc08bef36aef49a6628748580c773540d454b862b0d7d45fd485b83abb224e4735ed409a8d5f5b3aa0bb847dde052a5f9d57432b3db0bec64912f30e899ba60000d08067e76e10b028e13cf7c5c1bd9537148c63b6e1678778cbeadac3c57881f2fccc1a6ee7428328bf0fd37eeb6e63516c08ec7685cf4a2156eabc778658df578b67d96bf9ee202630904243aaf259f49c8fcb1ab79ecd8c67db72603bd23c7cce292c532cfdf3f1d9ae3a3ee0f6218bbaeff56f6a74a6a568f93b4439d92284e109d51d22067e3f0294542f415bcd54a1401114c6f78440b646a4d0b7d1c9e0f0937ce7d12acddd1b085810e4b1a7d11f956e7e74722dcda6f22b3abc72426e43194f811373df21001ae929c15aa322404cb703354589888249358bdca6a59ccc602fb389949121722d194b523f0f1c285b4f78b17c2805e07e6e879d4e0d62bc8126ce5399e61de4dd76bbdeac93bc3e0111885cf42bc602be30bfe679103e5382878e0d6992f1bfb7d3db580f224a95569487690f82cdc026ee3901a527c067242e3891f08f5af8b3445e9ab8acbac4798d001ce2d6b10f6550f7c61e56b045602b6ba74889190a2c5e2059561db2f6cbff5c9a0a0fa156ff6a5cbbb7264784f2ae0bcd215e512983cd95a8899adf96ac99afc47f890b2f1bc752c91ea1b9e48498bef6c99fdf7d047925127b32389e20c9b0803c6a5a09dc93ba452129689a08b5e77180e1b1d79faee2dad8ff5bc1ead63cf7f06ba2579c977d9798af8d001d2bd6d9cb8e1c0641b07a8bc2855f41adf03017653379f53f925c086e50469bb46e269ffe5b6837aec0ff757b258a5187a136472d8a7562a11da1b72ff7b367de68657bcd25009dde4d24954ee7c6ce7be482963c7f20ed3b31df0dcb65c77e8646c4cccc494620d44472408570f8c8a890036651265800f4cdf6d2ba215392fe7731a92165a429611875201d3aaaf2b66eb84d4d9c3c4a3d3d055180a936bb4b5a4638739030cdba6788f9d17462b31e574ae43089ba3360986cd2c56ad28df63d6df372059dd7b8f2ef574fef6171f4a5ec3eee65e0050c90faa10e7743a96728d862e7e914402d7c9978a909d96280469d237905faee6f9913b5420ac70d6238c7234e1f9962adb18dfcc4a09a6ebf59ce25961f67a6e76d4ddf201229fdcc4f8006034c5ec06c70f82b0448bef3ee26cdca7be6260f119fb56883e82ed2a085e8e0b35bb385a66c9e8953053d8f5715573da2a397ae900f451cd86e96912bcf49e857a83725b24ade751c86883fbaff40b840575407cd661d0398088dc37153681f46e8f480206269fd43b9cda09fc37213c6feb56fcf792852bac721a1220a45102a7965abf7670482d7dc18fd60380a84ba5f0320b7f0f90cb942e057d037594ccbc09cc227d6afdaa541a9a762170cc3208c109a62c60f467cd071c2aad73e4c0fae91ed74adff11d3582ce896c21fe31e555551554ac061b53a30c2dd29f1ea9e2140da45bf231994a5e5417c60e6a4dc972ff0034707f31723817f24af9646579ccd01e7db945d6e2be3beca78d5fccb15e2cca9c3c7201c43992262c32c2a2d8de16441de23cd14816842af30e253ee969f6b168afc0502870567c3d854f97f1fe051b2ac53c47d01ce37083e12a22b4baaf710c9a2b69bc96c011061fd982abcd062c93ef8febdf001f68c361783dd6b9df7b81766f1f1e1ade053353c6725c8d77ff9eb03b2799c58b94683ebc3c2ee831d61df6066ef661e2b5aa7e340228bbb26f36f7a1b6fb116a448fbf716c84e2a9bc974a46cb9976165cde47f2546376f776220cde4903187717a3cac8f2d91965384737a70212bb57302fa1b61f0a82fddd6f50dc94721cfcc322baf8b88ef9bf669e0f2f98a0dfe8422515c83d6243b1da3576bf5e8e3784e60bee79d52637c03e3e412754ff33110dae56a214cf443600e5915f5887f184abc8550cdbd4a487bbe3b4ad9c5d8eb9fa33fb8d0a56c18d78670d0a8d840dc48126aef9e2faeedfe97fbfa34d7099df28b670fa87d33334e5539e12f175c0b643fae864465f5677520dd14193522e526892af81b9ae87f309ef531f46db9ee201d9e1d25f884df4c42ca4c5c19e2fc64f1d159c3fbe7c628fac0de56a361cd2d91fbec5a54cab4f6097648eb4489f7ee559d268130867ae28f74f746d23afd2cc5a84217bedf5c46514e70949270c94718b0439a5b60052ec0a4c3bb9fd10fa1e98a22bc41ad51d4e8a940bcd875df5914fb61b01a66f010a2929c7744b47cb37f3e59202bda8c49f4bdc3ecb131e9ab6d4b0278698d0db9e502128e6aa36e5ead587e6cf179c07044dfea80cdb1fe71acefa477d30208edf3dbbc8d8402a39d11c4d5aaebccd661a3e32789fa13f253b77b8579f1312ef675178ea950012e5c07de27401c87c7a9438dac7d02592c3d04a14b13cf0626dbca7bbf62a8ff7705c3861459978381b3db4a0bf8e92bb60022eeeadf8233673123db73b7a571d452b856f7455edace5afb5ee50e0b2fa8893e13a1e5b8e9cbc3e270d82ebb0f4bb79efe3fc6010190abb6b248ad9157fa90bcf3758f901ae6e169107904abf82ee0cd800d8166eb6e1037ab8fc9b4a4b697e33b158ca2f28ba6a1d0c7160d860feb34c04e07b9122f51ff13c183a32ef8d02c1a31562f163254911f96c465e925a280001d7c1e3b0156555754d3d4202ee3cb656a4c97c7e72918d72014aa428c6963edb28bc98305f7fffe9894c616c4c00d3627644d71e1af493eb8b3d910640c7bf7005bb7d5650742c76675ea3791865b4027b79dcf57ab87bd6252770e90dc7446f8b85b7612a359689c690ffabd1684aa27126d27efb158e8c4bdeafd216cce008974d9bd2609e7b364900636987aa3f1a3670853f32b3fb3de9f8a19d40efae4cacc439207771118e267ed1be1b2f329bf3f8b4a901638a7f57267475a0392c5ea6a3aadbcc697446d956eb15eb1da62c63d3c6260cac3faae972de21e50d6344138328908a803122fc8a110f9cd832ec061a8c0e7189ab34503762506121b65ca43763d96a56c472277cdd969018ffaf0931f502836bb770f74f61e692a2adcb3acfa7dbec2f01ab453d2d2f8030c930ee25200437d40a0d9c3a0216aba876750af0103d1d0ca5ea1f73925de858e45e7cf458ce7178d49b5f3ae71b0592d94242534d502edd0c8e38eb702a9330dd2f57acad81d37798776e3cbab33e8a67d75c7e868615d40b1680a80d14329e721c3859d6e28c91221349fb04a05bbbe6177ef5163357df23d3c59cd38980f1df76dc1bbaa1889cbfeca4659e8902ce9efa703106d878d756e9f7f94c96a1da7ab1fcacb51492b4d9936a3457a19bdd8f7a3dd1ef689a71d102c6beef36120585b0421f0db6e594394e13d3701515d481f0bfad89e0f521e735306c43ae35d8fb526ceaa2b3aeacd7ce7426e8c29e5e91765442329bf062d5422cef1f5d623fa84ae28e1b3a3de24e93b28b2760558ca2af7c35237570dd51386a53340543016cb42716cf9c5c293cfe9cc973df520314bb6877fb2ca3a9edf5a209fe299f0daefc1425b179d2ae8c3fd42754a6b8fa346cab8be1de7fe11286becd74fdce8d0050b07e067d517d2497b87fd9766e8d7f02045315a79de134638c84899e13b8ba06059f8d42fc0f2c73d27fda092838d1bad5857a2b801f8efdc2b56da7f978bf84e19fea5ad54646098224ece994651dc3d2472e9edf0d46db04fb6029ca536374f8ec6b5589913a71d91c704ef8cfe707f2a29a3f8eebc1a14ab83a52da51812d85b0167a12273b4ee964360029f66a6f840e8649ae042cb5f42f332ffb06f3ed64870a4329c50902356830c2acea0795ed0e5eb367d9b18c7154135ae42cd912672be65aa43162c133826c9ffe15f0a0e693014e6755a8ef26c0b996e6a40bb917958d16f618af96335906435e292ae5c6dca5b32a32f5a7b4786c952e14f5579b782c5b44cd2a67a8ebcb308ac7b414d446b25838ddb0ed27f9a2eba1fb4472d23e5c211a357b8fc439c052d90f12302e0830d808736eb437519897b51fc6cbabfa347227271c31876d456863936f8d2c63af902f57a7bfd28765eca65f2da9bcf57fdcc2198de97af8480c2ed977234798f9b800bfa8705643a843515210f437208a92a49c54753a867f3587d5f92b29a2318f1c0649fdd520f01fe92f72b91981360a04df6431356ea4130e938db96d634efeed064a914173b796e7e1a76a33d13b20c3d5869e9b104d7e8d2c79fd201585b6035494140a817add3e8d734651520ce1678b82023ad6e5afd4175cc68c2e0acc3e3029add4c82662b5c19e460fa51d6f29b5b914864fb4a019b2e9c0275b0ed6753b57c823d2142515840ff74eedf95529670c17d676eb1728ced59ed7cadfe7a69b4431c618a609446d433fcbe06b8be1fab14225e53ace89b10b1542b3d4166dad6ca3cd639f2726b91355b7b632eacdb505983b189419cc16e663eeae95c7a14655b15776063e8275b23fbd459e5beea9b30c9046af96a4d04050eb860173fd7afa8e789b119f8277c7255fc26bdea77ac74f2a4cfbf1b0ea749122bbb54ed4024487bf42c6da9f8b3c3cbef057f29372bc093d15f997d3eb0b248abd40d7bc78daa840be882ed0c539c7b696295208237293e82087bfea4d29268c93736d932e142844b7b75166cb4b5219530dc3ae91813e984c5c8445d013091625d1a2085eb88bd7d17816363edba8b57d2ec2fc0790f03545912a7450ebebb2a2c496ebd2508483fd43b4bdb9d972b1c5c44873c1e14d85d48aa48bd887c1c8672496279c9651b58b154c505baed3fa9e74a55076d95edf8de963de961041fda92297be8febd3e03cd354dbab5ffc11df7625939a46744d15f32d1cdf36798437d6b429f488f2acae236c41efc41f32ebaa06ca956acc325d0a46b32aa05c71c113ecde9b2a2d1ecc1d28b42c0555a929b9cf4e4cd5856720025a18caa38503d7cc841b548553259fd068b6559306ca4571f022611b0e78c257b896666ad03203704832d186b05a6298b776eb78c2a3088e70ef5ab6abfff7f5c166610ea05c3113ed9b4ef26dbaa6322f50728a5239a5d69f57be9864124f485060365963bc07a8dd073c8125a0563d92d0b76cfc1c85814e4317281346dd15d3268f430c992dc8daac3b716588e39e014fb8aefa69ed218a27b47ad7248b19acac708f6de131c9e3efafda6549cd39d27d559cedceac4079dfe97a4cd7543593824adceb828d168439f070fec92ccef2812987d9a7467e9abff47da820f94ef615c2895150ea47a6c0a8305261718444e6ce0a7362ab7aca4cf044b22255ae173c0c971e90ec0d59d8bdc87b54811421c49f2f38857ece1a2932b88b944088e7dbd3ba35b78885cfd73b6d8da70c0fb306d9e43912a65cea8a055d180bebd27c75d446ed516205ef0db7c27d2ba70651a823220f8f7344c4c96cd35f08615cfd293776325d9aa2cfb682a5bef172aa68a070d2b74ad98b0b55e02238237d6c4e1cb14b642bcae76286b7b12b981e2ea1f61441f42b1f9469fc462e5356c07b0bb799aef8063dca85417614427dc9e3d824a08a70082003389367772d1260d5a8100041aa054902e303c7cd3781af4a5a368bcfd663defbfae86fca64e8db1703a83cdf5b52d76df998be1dce8d307e99284f58c2cf337b4c1f105f71d0c8409c3e2e6ad6d6b11c46af369f80f60b115256eaf440e7420e537d037e29fd1e2f2b4776cd108564836841967efacccac3be57e59709d2886ff4e17d1b65ba3507786e98701ef5baf9d2b8be5e54eb6e99995407adc84f0d63d043d548e807fa3a273147feb7b9973119e84027f4ed1ee871298ecc2b4f3a7b473da594e5c4acb504eb0a1f039979f76cc38af74dbcfbe13dfb343c2d39b2ac6a22134c398a4d802150d0a2a814b4a8c2c54c5488f2765dc89c13f24a5826d5f435d98ab5f229fa2d575af18ba1e7f19bd711cae5aae670d1abe673eda11bf8778d5f00eeeb1bd6cc8cb8fbdd449f438b2e4653c3b00eb943dcb4371623fce20f8921bf2ffb7daeb128770fbb4f0b805d6e041ae8b734efd3243c8be6c7daad0eae365ae5eee749c5bf26c89891c5f43b2f8042c578e88bc58debd3207011283b3d2c684e5f31a796638140a7b85bc08eb697e28afcebb9b58b4459e3bd2bd8b61a64621a81e702a3311cbd1824d6fb32c7ea9b7e0f9c218794dc424b128467b36fde0e775de47f2b9825c4073aa4c198260645234d09c4e5a4abe097c1e07029f07a7cfa9a4e14ce93f71d002de732c9b52be709976a0a5d221c46d812e55a369cd903fbb46d039c38affe2936622f59ff5b9f44049b891289effc604f8aea6015f0105f43cd8ea01a66e8cd31fc38f661773483be7f1970d1a8c54e4e9c3722581327d9874d850aea44d20e5ae14efb38c0c44300db83445f68e428907cbc3a3ac97e1eea91cb4a635e5229f53b1c55d70289f79f9aeef48eb76bf9a9a9ce261aa4f07eee39146dd02b3cc2c5b153cb710e5836aead9f7744e0390056e4f6253d9c0012256bb07c0a6951fb32f66a092cd5600f6f80f9c5366b1faa360a37ff46416d8ecaef92de9ee66003dbc27c10f523cbaf681eff381a906f48102dd73556d6edc1cd4b92a3f3abe3eab0111cc9b9352316016490201b2e941705f2a224d2103242984ebd6c8ed2257aaa38625fac7a563f3ffeffe9c9f5af4406d122fd3582dd383c5ccd116b5fe6d182087c1f3457994c6533bd2e81a7af65d63f0b83458016186eab1156f7c7b09d3c524062332db0b7a627cff82246253d50ea8a7ba34f536ba12a2808b00da39a9ebcedb3f10ea88f94b5d4d5fb72e38ad32f4856f66f96e8609872c742bea16bb93a9c6f70b635f1b4d113602ab283e24655e49b587ddf1b821236d57d470e31a995c4fdb7a37ef5ef35914a8103244873e5f9c67d05b0757fb1287d8e4f5db41dcae6a2ad18841e581d3afeaae34d315fc789f4c47b0da54b2a2d44fef3d0c676875be2ad77bd5b5bb6e54472b37c1cc491fac8a8f4c7ec11c9160dc4ecad28848eb897ec09642557ec579a5f5881ebd9277144547572ac28fff42dce6a4731d3090b775852c20952b5e6ae1fff9c2a0d44e3e39e1e3418e7bb72d21965449696e0a17458409cbd1e319bca5cf04de2058947dc31e501124f0ca2938c21417395782d6ecaf21a2b08e6e1ffede8310d349c0215d505057ca8141fcf1901c228b81fe888603bed76425dfca60c70b1f76375666d051c246a0eca8af268aa127c7e71c597a354cf7cfd999e2723dbc2e84050244117c9da8e5951887438dfaec7112910d61af4eea760d3433ab093e05ba6e96b0faf4e7a7fbb4eb46517b9ef2cd4afbc81c7e3a6d911012301e892e9eb8f8a73fa039e55b531ce02302d9d0cb55adc55ac33a0e414e931ce6340e49dbc75b2265c82621604add9212fd22b2733b2dff65aabc12d6a9101f3c09ca29fec0effc20c07a4b102144b11b9deececa18f3b456741d48a515ea114e622d91a4f9eaa66468b99d8123658ce43686d7d46e4b284fb5cf694139c541f697603f46c5133bf094032e38248e2d373ca65131c3def099af6f6ebac40daaccba6faa2b5dfef2fbd364d8775907e67c770d032556ca5dccbe150684c32d85daca8ee3637106bd6696e5a2d51384f882aef5ed1ce05bde0fc7683fe82f5960727f5beb6258c63c6e0d121a9fb81166262db51adcb2ce74171d7ca31d3310d329359d6507b6c7619d523741aa43656b3af8d7d9008347a4a3f84096678900c69da7ccd448b5bc2a43b4c731a3c2e5813f07547dd82eb2b8b9fdcb8dada7139f2118100a5c83af913a4d8061217759b7032424c5927ad1bb8d3147a70df53715cf1bce2c45879ab23d7dd46c5b74689382ae8c0dedfaa6e78ef6efcf2fb525d74db68b27e80b4c2e525abdd20bb114d6685698f8132a39c88383f50f8f63400aa628fc4eb772f9a12f8d8f62b797f82104aaf3ab72ceb2137c6db4f246ecb3ca708d658eb2924c94e0f21c5199b96055726490a154d5327cf41e68bf93fe323f1aefabd10b2da65b151c47291e7d90567e98ae56a8167a3da1c324d88804b371e69476d10e12a2200acc466136f50dc5571d9494620ccb97bec825019c7d3986ffeb9d2b09aac71b25fa62ec488f1cd38af3895a1d920773e15cd4e068f537829f5a67d8e18500c0b30a38bb288101080a395ab8393c94abd0786431da424b48c499a38194e7e8a595984d40b85e872a9e3df3d42ab698db1d86995efbb1a017ba7bd517d27d8d8b36723d840553d09100259ed057655bc95d39684b98117fc2242b9a8b6b4632f6cb1f6a24eb1a8309fe6b4ec206ac69ad176d868ad994bb90ce59d43098ea2e9ac4f806a2105ae1573e67f76ea38f1b384c75fa5a62d94ec3044facc7bb0282669a2db23bf4aa672880c5ddd41b561d146ac4006990e4fe3e2ca38112a011d7178ed8102d08574196c61b3b4c2f61890f59b4e1f40046b93c022e5b4ea4003687f179b087a40795e96a242aeb55976b633d65aa16e9a7c750e46f1787bdf046629a060fbf3b9ffbcae82c916bea37ba8597f2fe12f11ab88b876c2353ff612321710589171c1546bf818cc80f4f548f27453249b66a162882f0624bb9a3cf7f4f5c8d6d0ab5202fee31c3442b5063c4da6398b2636f0c4a9cb3252af985982be0827fdc3572de9b34f571f70f903b52193f8156c7e949ae05e0018ef5493b6f3548d1695bdd794b73e18c1f3ab8f883a1ed9fdbd513e19ad22b01e09554b3e7ec2df0422b0e936af83f8b0e19708ab97bdfcb8f5d9ac74b60b2a40bd12fc08b46d94d2d42d2f2b9e3fe12c4e8717aafcda376e02b373415d9e5369dab2c90e15e0891fcd0f9c8787f2769f177fcdba16c3ac944276e87a3322b208dac73eddacd681d20b2d8134961fc4474403064eabb7c3f43323439053cf974905570ddb0ea170fc5f332096049c6f76db4cb336e1c0cb569d5b1d14101f6b89d66b28a23cd3048341bc0e6bc997fc4ca25d424ceb6211533b27ce4f6caa0c21d9e4c2df91056711f0e06bedac37d46add1f301c7e21a0363c2cf3fdb82454ec8b7924be17ef6dbdcd0dd5e4cf4befc44ee610c9c2cca5f954c63bb6de14b19241c742d5df6717949b678e27c9c8daa0b34702996ef5320849651e2ba5388ca9c1021f718d19fa90e6b4893496056fb30b8e0bea7672747775865bde6c7b454587d6988b664fd6cee6b9eeac9029b7ade54dab1f40e5c81944ba3b1df3345df0cee261cc943660264ad7b4ac62d05517ad7e0158d4d8a13a6df26b11bba0a1ccb68aed0d64ceb532f18be1759544f524de1df653071ce195d465225c5b434a03374af52e6b1c9412c7f28045fc3877be1d4e3c4f81f0b942df3592e27c54a918f14b4ba68eb55f77609a7a649e65fd10c9a2a0b396504cd45483e47edb44c4910b1e140234d640a84e42e9b9002a3685828b716911e31f06f22a9d5ddc98802cf1ce52ced05fa7317e17966b0f3e0fffee5ba76926451f929ad4f218f5f35a36880c5d055f8463804366d6e73f996ba1ea96e2632ae5d7769d9364145f9b58e3ace4d8cde42d63afc2d19bfa65e64950c8ba4b0cced51d371f794f48c79b04b5d1a292e34252dda0577e816d20c67d480b7c891781d0325dd9a0085ec5b1f80d748c3bc76905f5fb1b7b735a6b5181a174c92c56d095324e5888a028b914e03db6248ca7b127d2a1a98ba3f160bd91512568267d59ad42028c7995ab35dc76839ab8a8ca79ded5719d5c567db608450e340bae0a8643dec48c0e401eac9fc83d0889b70afcd0925c56bd41cfd07b19a3420799ed8947d5084431d8d80bee89e8d230d54decfa467bb35a0154f18ec8703fab9324598230745fa6e0eebbfeec6064f9ac17a97de2a376e4242b66d2fdc1171e2970ff470c0e74cd4dd8b0ebbaa351fc929f8dc8d253985ab60ae5a5d5babcdee9cd936798f0501a60a60bf8f4ddaf87694a2682df796b1b5c6d476173942212df66e4a427e4564d997dc723588c151c03783fa74c7b2e6ebfca43fadf1a882bab3d8aa56048d96e79747478ba976ea5f7408402024916a837094c2757b072529f655c584a8833cd27521851056484a24f02b60a8d88c0f9e626628b47d9c5331834d266b534a34b7b69421748c4188bfb3e101354714d9135e5f1f6a35cfa422330fc2494d646be1a36dfff46ad04da678473f1ed7a8973c438128e5bb980b4f446ea75869a2d059f75d2c42126b52f18589880b3a03986b2346db3a8c724dd31843efe9b4d46fc8f2c25c9261be09aaf070f02edc602c4190c4e6bfa9be41f2395fd8dff79950b5b6a6df04b2a9f836c28790485355d5cb2909d3590184d8ae14635530106ff675538a908d1efb6a202f9acf561b8135da817c4ef12b7ac11fc43237b435541c121b565592872330b8f1c52e17c7271194e45cd1e3583227145f13c0f3c7d1054b39624fabcd7ca34b6c48a8e6c00e708188aa998d593f9b2155683ca15e09e4cd95d011cae9b8695aea18b9c31ecae3bce9c822185167a1fcb365e61580b99f2e763e5eedc95c961c1bfa7522571a66d6c4235bb5e47dd96bca7e70c855273475f8b50ed3ad3734087d864ed0c65fa3ded23ba239a8090ec000c5298d0803b09195ca705365e91cd31dc3a642a24f02675596d4ca8792210a3f54228b34f35d34222d2a43ee424094c8d371c12011c80f99e0092ff50806c841f655b1e281828a37a2b1a6d2cb18a629cc030be884c062b1a895d1530dd5010ad68a068d07fb9d5d9ed340756db002c873efb79ffde552283408b862166ad68a46c9fead36b9c012e3738de4f817781be6459cd21f29b748c2b1aaf7c0dd3547fd298899d8fcaa735bb42310d576d2b229365fc60f8288cce54e8e7eb2e948ba5884c44ba774cc42ba6099c107f069a4469bcb8f128d0a2a6a449385c234bb146e59243847c5bb3eeef61e3388d4e1f52e2ed8bc844fbf74b83f50fc01120e44ee08c35cb705aa7f58e4a38d931f9688a96cc6225a0f18c1f03e6d7e263ead801f915da8d74f8bb439a96611a78e4bce76934f83e80fe6da351e2443991447c873a01be8d8b8933f9590722e19ba789ed58a8323e1648723473d78840620f9b714853b226c9aeba4e8e3cdee3119345e184ee74438314cff42a3fce8b203a5acd51557df78952541e52147004b65a8f919e1d234370512fbd79704f389cec83da67ad3d75f3e010b559486e274beb45e6f9aa94c2b205d456580dcc5d34967adde6aa96a8e5e04f7deff4f75230d89624ae69a212c19781a352fa029e8d32601ef71f13ea239e1a57a0146d364fa9d2749c02840d5c00258a44b1af720fed71adf8881647524824c383e327516349a6e55f5be817ebef200492c68578bbb746a9612e5e668ea98ef3850bf56e1489c04fc5fe328df4d55d49e9152811b26df676d0db57e8aed17711533f916b91837077551f2d56a840f2c01c669d721ad06d0607b0f2dcaa6a580958b34f4328146ef4b5b5d7ef055740f3878a9359a530e6d6eeb34c1ccce4a5250b825eee4bbf8c9487fa17cb2ccf5e8aea749f93b870a1859ec12479db13d2291d85b6c1272d757f5ff1a42f4b89415a874e6a3719c99fa7804615b8b4155d2045e0afacad2921f8df7ea670c6ee0eea9874b6a9a59bd4612448486a0a82d33f92cfc9e9d0053b841460201d803e62e93d5b6c5fda4f1e62a1eca226bc5f890082fb1110d42a87b60b1a63a13fc6727414a2054594ba992106d543c510fbf14053850b4a0bbcae16319eb2a74c0ca2da4271ef31fc65ff30205882848d32918ce2cc6005e34aff451964374a10f538d973b8c72d3e5d8f033f86e5c2537e094b2d0c23ed7832628b60e0ecbade701933096656384274a39b4a73506826c4610a1343963aa3d3c2f61a949a3058b8cce1a6cc726b6ff13a549a08115fbe0d5907d31ad4919083d62bd5f89ddf739b4a9081e42a21f69f22cb76ff429f33860c79fd8da25729d50627cdda082c4804855ed65a9d4370d1c7b16aed251024959b99c5d2fe89639f91be0f757f11ec6abcf160814e4b879d82236ce8eff8e9ab956c8322885db90d2e7052fb829236e224238f209c38f7c6f68a81392ca02f0f3bb701a8ce33287ba746c53bba894754c4bfcb3915f5d0e7b0c69f2413dc7a10928c6569d8ad8455cd0c4505facf563e012e43bd136d02174c8d76cf0fa18dbe80ce9b6f166119034fd56759c6913dcf651b08fc9cca48b7cd07220458158cffaf7da14e0d11439f950d0018edcd11e935aebbdf4370db514a00b67bff35a2dc58b790019df582a7a023a8a9477d5c8fae9feee89ed107ad58c5fbf4b5ae262b21ea83c31279cf30739f3e56e065380d741ad924937dfaad481e2a5d8d4835bcafe4d6da752ae34a947b66e0cce5474a9ba03ef828f57455d1ffaacfb212943431d4ee5aaf6725ab3cc3f4a79f04e84ce1a2c232d7c6cd8a6856b4151cc214a2bf543b6bfa2a4d23e0e5055eb1d062eb2a13423f907a7df31d7b293bd0d0d50a1352259c0742508e684a620b9b4d8cb17f95d992a04b7d7871351dc1e721a87cb0a1ccd9baf3b77af0ca214011b494624d8706c3686035d2492bf984a91b4c66d211d38ffe9a0f076cb77866941c3525986c98b6ba1442da66a9029a16989988100c69af8c80c5cca570952f24c7f871d57b93ad79c4891b075661b893e640659d1789657dbe19c6144ae00053788e50f00796063dcd9163bea555a68a8072a12031a3d315a9756d3c53b460d7387386afbee72ae91e4fa338a6a01f4af0b7d0ee070ee29f5891f97086fec1507b628a3194fd2aa4e7a4b7ada2e6c4087033a2bd0ba0676b3c38de973e7549fca0dc590081cc0cab24c5edc7470ea7b5807f780a0da4e3483137e03a8b4fcc23a59c9485c23e165857a971d9008c71050fadf1a492baecc38eff85ba7122243962917679ea80242c1d06bc788afc2e4c04bcf0e6bd8b9095efb26a7546d3ad9a59c89cb9e905a185d579d0b50a2c9d42b722cc748e00312bd64947562917a16aea5984aa3109fc766bbe8e1bfd1f3af43f36f40c0d6340590b501bfd235d25aab11dea681f39147eb7ce11260254672a2b50b631a4b1cfb1906d14bb6e5eb586cb05c26240c787984a8505f85565ec1ab243319f24f40292f5ef4192d91486317aec1d9a983becea53824c29c95fba286c292628871d77ad6709a6717f09ed5b8c12e626e346179c9e32ca87001b84f40d20e1f3798a6878392d481ddea9607293c8019c79b0fffd1af388e008b1a2d80f811c6ab590f865f7cf52245fb6c9d8ef8bc0b8ac8e127feea7edaf5e6acaea375c1580dafbe6fd9ca38f0e2f39d2c9c7e053ae3247848c626731ef6740f49201fc0a7ef028035bc9c732609cd83b1e3cfcb857695d6b56af5578bb42f7a745c95c3f7e3fd5d3fb7247e312757d83bacd54b3140ada22d084927089cccde4f1899a2bff291977a410c49f9b17189b1a7c8a872d90df3f400116b6f8016572d15952fdd1d3712965e6310f839b0b7939845b41fe00d95c010896b980083cd347efa94558531c44814f2e27381b8219222c79ef4f9b7cbf007886fdbd64cc11a5951cebbe17bed8dda8a5dd6f19d2c52c7411c584bd2f1804a7c115acebbd3d5709ebafca0582da784d280ce9b10908ce7c72adef1bd241d158dc6cc203207bef4d8c58b6cb9b890bd144abc2d554afc72d290a64ef2cb97c9ff8de3c661e3afa4a5585c8a3415e34edc32672ac011b1606d6d14dd60f4cd9cc6fa9923669d155e603f18dcb4d05325f503658291bc4359beb4a1aa4c9c1ff9ef58176ab79648e541cf5c292d418a88b4b358782b876881117117180242fc7183e49511f634922e2e6e49f838000b93e9e8f4b28cd468fe54985ea790535d512095cc33c543e60c186d486dd5059a04ded6ee1fa9b289e343d38eb8d6e2ceb1c34dc5a8a8aba83f9d03bf8ad827b400a988640d783df9751e7756fa4c0a8e17286d1f154bcd9cc46e88a51f76800e101cbb99e0e03cd4c77cbc7a243410c96bef125ae6485447eb74a6aeba89ef0ce8efa5e1567de448b6aefa2e108135dc222433ae01c7e6fb2ab86bc06891ca15f9b9fee8f8499099b44659e092f3138300501440f2a0d77f0fdd9b3ee7eba2ac8d3c84cd9451b260b740bac5b4eae7ec4cd1c2b7fadb3f4c15cd5a6d3e893c9290fd87a755e052068ff3d415875095eb12b77054db0ddc40ed58c397ac1a4f0cb820c404682c53f384b38b8a336614dd496b4cbd704adabc7f099865d3c5109001d54a013426b1673e964558cdd7fd04558321805381e0909f2fcfffc0d97f2a226f1c6f0df2946b3b7ad4ba206895da5468f7ff435ca3b697505bdab7ae06fbbb5c017e298c27ef4db93e54c00bc2b08562ab83b037b6b50e1c194f6f73488a0326275fb15d11cbf1a110a7100bb452d855cc4fe67a8694b84be0c9062ac135951ff8ef58255a44adc3d653afcd54bff7ccf3dcab64c3e1fe3c6dbac9be1c840952a8fe20ff37ce610efcb1bbac2c7cb7e5e12a022db313f77694d04a05aaa1a61e4a25cc2c665ccd25ea6d58bbd9429d5b7ee59f5481860433f919903db8868a845c36d86d09d9dcab381bdb4313cb113b3c7f7d7e3e32cb2da9b1f2cf5c7c86a8fe366d236584061965d10019d1b399fe34f87442e6565a074de4b568b548380f0a2b60a742f575fa2e531c2b50fa016963721bcd94ab78461cf82ccac9458c8192ebd5c099c3e0b8c3458c2bd2697d1c36ab4007f72e41348cf28c37ef75beb6b8cec458576cfa97659cd70fa5e9622abcede94f2ef43a8e7764875bc96229a65012321da13eb697a0903cfc24855861b08813e9f584cd8eead421c6c8b02fb5c977af42c0fe2b670dd337b6a4d419ab4938e8991f8c3224d0bc5002569a21bd91e4847376a485598a1c3619892aebb2a72d2c1ad3424a1f8054a47363f875f91f54c560ffa590d53a724fbb2eefa6bd5aaa5ffae88be22e41206f7657a2d9734b2321eb460a254af2767b0f00ce12c230a9735b95ea59d1f979ddb42977dcb3651335618176ac05627438d95c592eb645a0c82d1e92add1b5bdbfa987a0b09990ca7445019e62489f512b81750f22c9a38b98802752c9b1d880b7f708d47f6ec56eac0ba28991cbc572d2229570734cfa22a22332622712711ea1bef08366d1758f6c37b137688b4d341c520723612e2a5b5b02e8b98aa9d5517d0f8ce4452926c27a150e3c668a778c4a2b1de44252fcf21101e5200d0529283a8ff90170f7252327713b54a435565d989130d97c5f54e1a4a4a6fadda169378222d1b47f3f264a2955c2995045a9458c9549fb4645252cf288443e548fb463c07f5aefde48b597e82769e1115f1791c420628fd64b42cb46d9b79d868a91b3308aba92d24d557d528297445a2945896bcbce460bca4292c84d61d40fd90ebb53e8a5e1998e3998795a13435ee62e7303c6662bcfc7fc2892ba75cf639dfaeca32e37b9c0b25891f1476245bf6a031fee5d0011f54879f0176d6909de53b427cd9f87d1eff716289582a331f676221378b3fa4144939d12bc9ae900ec5ef5bd26cbdb02c1b0eeadddc937a85a33eb8f30001eb17494311eeadb25ec4d9c5c379c431171afca31a599ed4d11dd5406307502e797f4a5a341a3676544e19bff431d934b2166cc66ac159cb89597bbf2d56b869ddce6b319e1070295f337dbf67ba3aeff32caa838257a8e57c2fe402a194cc79a550c519a84179a052a0620cf9783be6187525264b78f60cc0e49b09493f6983dfff389ef5a247b3e47a66e181f50468bdceb2ae4f4087cb69f9a373656ee2a693bb23225ef5081e76e6155898a7e730c01ccc0598ebc33fab7d177326597533b9d8129861e70d374bd3b154f3c5e24403c120584365fcdda78cface4e48da878cade966c2a0c04b029b17061eee6cbda58004ea4ebca11bca1627dddd365a637842a1a07d61d9de3aa58c3f532e5afb795c310a334effaa928a5464e9055efaace9a9da6a565b9e005476fc98088fe35cc483b98ec1e18bbaa869332b69024c7254b8432b9cce855177600d505657403993774481a76b4abd11370157b5a2e6ea0db21bd1ae224d48e6cb8720f0c87748504e453ad8c7aa32cb5b8ec909f79d92a649e9d96dd0b8ec87575a99595d48cf295b1a9fb5068e783a92a79edc65de04a2c5290a861f4a297462ddd2da00a1ce39193117ae944e58ae3f9393a3f5d86e0c87afae5d8229a8f64cf8d4cc08c9334864e1838ed8f9e8c4b51032d2d3f5898ef7abf14324d7bab0e6dbe7dbd58ad4496680fd84735e1312ac26d3c457db5a34e6ec8aef2e6ee00576b2be9cdea834701fa9710de9dfda010d8dd48a8466063a1e507e329bfc957dca87023a63510628e30807dd885dff51a8329555d5dfd9d4ea5e032fa438658f09b3e29d3e18317ed7b390e98549d46873710856136248cc9a98ac93e107f20e6f08e41c0fc9953411b0ee8e3e852c4a1259e0c2d3db759eacac5113895249013b76bcf7e63e420e8905fc723eb56eff80ed4351498ed3c69844e2497561c6d594aae27af2df3e82523accbb0bacfe752fd1bed4459554a005218059d4a42e0015482c63e18fcd47c354f3149aec79663c8418b0f5ebfef816da0a387dd65b649809c7c4456385787c4f6d613114e8ac70ecbe34d83c4484acea8fc03c81737200cf7cc6f57e10f6d59396d21b2d4ab76cd3c1e6d1a99bd32b14253ebdfc9e99229b0bd333134ab89e46ae782f3059c1ce477f9d3a7ad6f3bb6ab982fd4cda8794050b28e3a816de953bd8c7764d5b5585e1a651e183026704418e92c2dcb5655ef77ec32b2efc7157bc16ff8c9b3981e6737f191b5870285cee7964d08ff574e6cea583558670cabbc823242b32f6019307532e391399c88ab71c611ade729a5e5732cd04ad954c63b7b5a57c4ac35157cd5b7fa2de27446bb080d9176bee26ed3bc93219f1eb90e8893934d64abef03941432b7893dab141808d1640dc422a72a6a908073f4cc7b76e746c295d4b40c7221a131114389364077e609ae5a0e64af6cb7d274dddabe80f950fd049e457a0cd9d60812e15a6d3bd54cdb83bcf4a2349ed5fef79c39e518a89f4950f98f93311963a2b31818d0a0b72d4f5eabbfff1aa90de600b45ba10a802a52b42fd1c4bdeb9e3b1b5fa8ad8f0c42b8814254b87c401fea9e32fb4fc3bf5d1411f8d28b7c2dd7c1b9282f342c89e628e89a75ad891b5849421fd3fd6bca3b257587211084c01696b01bbc5f4c9075001f6015115de3335a1735cc2e276032f13962748ef7893ae2ab354477dcae372aff732eab780971dd393aa7e401279d88e9d64ad83293f88d5418577a9a557d3171b9764af7187bf866e793400100a11a264a4680ad729858c2371192ca024847e163188b6f8ca45025c98a0a91c29980043090272a802d4052d39e55302662d9139d0026a8ce82acedcc18b2c15749fccd7f873e807f493a2cdd5eb34f00f75058f5e471ead0f049aa19923c298f3a5977fe8c64f70d7e712e79385c5b218da40a444c135383910a45637f224bb5321c23bea37a8ad6dc1232a45ba5c7605dc27e77b8a0cf4d11d31441f82e91713032805e20705e0b83d73da4d6c76a94d81adc2f604c50f2d0643e0fa8d83607b11b1a2c560e9aa744390b2f9bb8d3496f36b6a552715e837acc1532a8b33f1c7d3da371a9619f52202b1fa712c253b44d58970e21ee9afc12a7a94a1058cd53188f572a41699b1a53fa71006f8fd8708c25b2a9cc0828e4da42e90a3184170979ef256185d33fbdb97b3b8f45887b7c742d45bdc628f74e6b6c8119c0b8eed214b21a8c8fe269215beaf38de2bcea231bbc023ad27e2cd7d402379349833ba5c8726ce0c2a8ace7899d91a08236a8ea1152359bfc7bb7e58eecb75d0dad3b99986ca65285667e64f71f096d1a7a13d6bd928e5bc91b41a22ec84b5808099937d5e6cf27e19310ca1000509e284fd3001cb3a7d2c92bd49552f484b43af924a5f641182834c4e1447630b578765e4bad39bd56e58052864691bde73f629678fd1108887793109a9322e5288cea8cd4f36fc5185012c99602f3f091b84e9080cff936870bbf1b108edc45e002a86940a45f0286ded7c21db5fd16f4d2b515bc1024223d4255c7e8ea1e73da54adafe50bbcb422f9dbc4acf8c029741bde9f23b99451197835067c350401897704b503f19a5644a6ab021292280b67374244812c2acacaf10e06cfb9dbfff1ead237f82bcee22601ddc423f25e98e3d4fbcec207188970d55a50bcd4b5e9d6b165ac8e4827a41cf7d7d0f358bfefc49d3891470e28929f8c544b7c10a50b94a7875ae184d13299b55c1a3693197ec6c29028935896df61aeeea3d8a72c7ba63dc12b8d8cb0b6e0c7de116a94e9000d4daf92fd3b6e66c61490c77962203184b682d4494ff6e01db0e93a35837f974eccce89370fb4d11fd4dabc2ba477b14fba55db29bde1908e294b16ffd2b1e6156efe17288129c39258ad9f6172f820f0da87b494f68a091ce9f5cd4f61c1fd62883ff8129dc71b2463d0663773e6c6da3256503d664c9ee8749784c9a603c0add8d42370aee25a4b13b9b9c2611a51fc0a8423fb598cddce8a78959a87cb02947e6c53eeac6713bd685dcd0306ce5dd3c6951a7eb17a72bafc339ebaf211f4beb1e7d5dd1d05290c4b4dbd335c1c686925c1f94f8a64045ec31aada368f4eb208ebc9f8a169c7a4c02c4141d4c9ec27791a86facd4417925f3ac1d2268d3b7a76d9af5ed51ecf2bc70bde83dafbd22f447ee81116bbb4283a527922c6e8823213a06b5c1ebde53cb9a3b513b8c186da276892e3933433dab14e48fd3f1699c06d250d18822f3842c73307d555700ec6d8402ebc353f76c2316b54d52f06e4740344a5992460431f1dab0009b0aa0a01def8bae274df68199ec5a816f89a6cb1fbf3cb12aeda6b0e8e39d5a6a02fba5e22477e0b274d2364522619df09fc692eb89b1d0f11fe8105909280b264de7f195ffdcdd50ee1fe97d04b40f1e5bd5981819708760cdc392bc13c20d0996e783fe8c5cafaa6fe2d9339715bbfcd81c7707c0b26fa58b4c506a3d3cad753721f4cb4a47279599c1494aab4b7bd4cf3060bb2201c83b19b01b3ef237a83fac0a2ef1fe1ea84d324d160112b7125186fa224cf0c2ca245c1c2d571aa0059f303690aaf9a93f619e2ca3dc72f3362f90f0cfddbd5ada271cd3e044f8400f2aae3deb549067a4a85d37574a809b4bce4ded001072ccda44c9fa3efa7af0bacc09f99fabeb34f1a5602ea300ef38d5eceeb297abfb400e2378130c95eb5c611d031621776758d10eae66b0ecde05e71b20f5cd352bb5d1405c5f745b23099cbc70f273f795ff69ae5cb0fdb33c8fd5be93925f5320341301df37e3e561adf2f4c8f77ab125a4ea6578359dbcad822efac696495300bc7e19ee588f4e2a90dc44e791e568f4cf22576f9e4f518f2e1f3318dd0fe3edda7391db4ba41d5ca71cdead4bac2613b445d19c580631472327de3c0b659cc85108af3af480dbc9eb4a6c5341b6ace91a8ab42289616c713095d156e3c9878f089a343567799572db219b3a8ccb8ee0cb81aae1a19dc50ef772c084e43f9e4e7eb2a4e0fac8da0f2288a55357b654c6c9456e95a458bb5c46686678263f64cbd3788282cb3dc74d579352a9fdf237efce447444f3b9603d67e355841f23defe0c73221a76a4998ecd751ef5538d81b729331d7111e9688f51c9145fb3a6ec93e3f50ce8283fd5ac4d4c02e0cc1420ea2cf673accf7760e3c2150a241976b23ea60b2008b5be36abc99ccc0cb7cb2b7a229de02dc8fc06128017f101b8976ea05f606da9429328d9cc5d8a59dee9a5de6564eba827d834889cdb2763739c69647ba2f676e30770d96651f98a63dbbd6793b88468f24e0822b76184b957cb23366edb051b94590ef0b003b6606865b0e6f8597050fc78ea3a3264a1a8dadb0fde65e639318dd469b48dd2444f38d8d963f8bd7439353a880c20dd92a4117e5e29f82cedaeb7629061fa68180fcc3354e3de45e7b290fd90c594008c42fcc3909ad7ec9fbdd8a778bcb7b496bdd491756b0ea77ba82210b239e85c1ed7a4d56f9bc1579bf29d7faea714e1e28c3185b8ade721298dcb01a2b18aef3b55a2fc7da5de5e093cef20ff09d14fd954f0230586d6820dfdd3b2fddb54beea29d513b2046417f2b76039d0f8482179167581ae20864a81cd131c23a58867c847570c78990557160bfe68fd98ded8affad200a32b6f398a36dfa9f8d8609c81da9c6404a089aa8a8acdd67a6619d3071aa563f6247ad90018639eafed37818622004a94887d95b823054c2e894100c0fd5fe6eb3633b158792c7bc6462fd0461f2ea6e3cf69888d912770c084fbc8d3f2ea3424d26e3683111bf087871b561412398f265534192a1365d9da12b4b4d1a92e851dbe0e9a8d01c73723bd6f821c105e7c5e6e9a36f971bc25415a4e16a0b34eac7000e046d833591cd91c449a1843ab15f18ecf67a10d17749cfc08ed34629a907910c2b9f04688b242a7a1c96ad371817608567725aefec3ecd2490781ddaf7ad79351f39383030e27d7edb1dd0e65dc17d3cb2a1f5ca144374e11adc97afe2e01bda44c9abf1c52be66d08d8aae19f3ce698005132b2855d6a42729817f4030482d02e53a8bf8d4fcf026d1f66756b6e705fb9a7a38400a546cc1dc319072e318aab648359ffeefd33e0682d6da4cad4aba00c538a0f4b80bc59fe9cdcafc339ce8871303cde4c8ce82f274254e1ad9f65a729c5097261e46ca9ac36e7a8fe63db72b00ec71bbe953bffb07fa2c1742e29a069301194ea136e0bc071543cf79b388aca41197d48213bb65c66cdbf0997320533b18e871b419d0223cf43d9742263abc3e6b3e288bf80252740ea36ce2b5212402f85375ab14ec0bf23948194a0c64a34a4a23d7919cb36f06df0bef0d99d7f8cc53677518beb356c5d5b12080982b8ec0e2e20bc8369a2ad4ba5e80e03c64db1fb1fb6bc39a686988b6e3e3fd5510f49f803d0dfa81759e1d2e1dac6599168b1db7655fcaac7d17de8da95e0a7aa177f4989909db99280eafae448eae1c794a4cf9fa9844bdf6805ab53b550424c213db2f9c95e8d941eec28c16af0381f1d7ac251e95d9fb034de3676446db424069358010c82a52915a2d45d9a1b32ea24fe2432eb731f36d73ff3c3399a6962e30d88effc3649dcbb13c37a7514ad8d50bb0c753c344d7e0181810366c66979629a1cd1e0120b9bc84f43adf8f3dbc22ba7ad67ff3463111e1998d85effbeeb2008f1530a56839150b6b50d3caa6da8ce31d4327b9febfba590b913362137b54a99169f045a62a05b0512809c7a2a977dec07fb44e90a66db5ab5fc7d675152fae613d1ea10bee4907408dbe51a12354d70b823c0513d227c23b303a275b0beaa98606f244a6fd49484d630ab827c720e63478887ea4f0cdf5e02ef458de19af860b9ac7db54867c74ba2682003d230e0423d39803205162cbada603b81e6f17f0044e2740009813f46bfe709ba58a84a2eb5ebd12950d0428c771f6d5ef90a6440c11895187d831579f37c16406ce6ca025d159055ca5e7a478b405e4c1c2bbd6f3c30c229a2dd47553e7d6d330083479b0aae1821f3fd2d4700e881d090c4a2b9854e3160c08d374778490548c40f09d67fd29c5908f634748a84292b17924b37b00d1843ef6a402c428e22019b4c9cc143db73057efcb95bce9c330ad7024d10c66289614021ee50d20828fe6fa6f32c6c067b0d07a334ba035e621ecdab552a886135e79f292db77f58418b6b8b7a61826cc7eb3bf491fbe82e228ef05b9ae9ac5a8360f70a6447b21d630345bb46b3222f0247da41f5975f02e67ae620ddedef524cf684fa8f8c6b8ce8f8c66f807106c144c1123bd85802ace33f3ab53d3fdb3898b49844b46432586af8bdfe086b2c68388000e1f6fcc77f1fd677f9a7cb3eeeeff8bede7b3731417fbf1189ca61d7360ed043571b8b20aa18c822c3f8093d5d011bf8585926468477213cb30f6bb5bff32a00f3c9d251e546c8fbb0c2921b993cf1560409b33ecc5467cc5aad9d73890cd92e614c048a12d6f303b9b326c730da7d69071b6b2f2b11350f6980d470b6c29e6846d1a4c922dd6cc989117c3de8c8a8ccc99aff9003566dce8f331060b3636263eee5de233971e23e085ce794a5855a7d66702b1f3aeb400043bce439613bb5a84c820db3db9a0695a0019a5a0ef1a5a1c377dd288a4dbbccc47597ee22e124b977c7942561fec08012306549702cfe01efead7c19159370603793c5705fd88f893b2976537fd5ab3a743852f8b6e2441127ca86c747f30373a8d3cc07e849a1d802269257a65c052e3bb49546aa2cc760d4da38b84d34516714d73ff733c254c54239b9301770fb0b568289d8253b91fdba38e697b63056a49a5a58846dd5b35ffe8e5991688f78153a743433c352ffc3f69b83dbc67ddb686dc895725868e06b19b92c3b2ae25b4a740e038abd4f14f766a0b92a011725a602a9d0a883b9380e141a142691717c49f88d347544d22f2ad27827889ea56ea9e02da3e1bdcc6588244241811ea06d0aba452613426a1be8d5aba7dbc4dcd3a574d61c3a5e9691fb39eda8e9719b64f9a83549299d6f8297822911917f75d2a46407899c95bcfeed34929156330dda68201cac647e97200860c96fc0d43af11fcc56e0e6727e3e47a320ec4239b83b391225103a4eb96678cf7d2d47061e9495a3530be2264182d8b8ad1926015ed0e9be75e0d34d2c8135b9337a8ad02e725ebdc275b17a385150ca1fe50a8d0853effa4985ab4812b7823cb779c8ec04c98f60729c3005be7df8df6a83003c4c7037ac1028a5a409a15a703ca890ae10fe171e06269dbd14cb3ace3c3dfcc121963f4b9ee1cf16e9c231c2e0d70208e0ac5956b2837401de56bfa8484d02e6141637ce4b7247f1b108ecc2aa4b712c6abdfa0a72624c9de5bee2da59432252903ce088a089d08f6835f4c863d4f5ce307bf181c1d06bdc42edd410dc47535a08fa401320afd75b1ca0d337309712d5eba405e4cddb726b85e9ad12846896d64d07c101e79a9cb386e5450ef407d4b0e85803c4d324fc762b8067bfa7c671e083f1082200c416892458d0048640863402803429a3d845886e152bed87ef00c11fa4a7ccce7ed0c085520a401610d086580b083b004a10d086d641efcb275b134320c9ff2ca0c2c2d34d49043c70dd30c2c2d34d49043c78e956c074b0b0d35e4d0b1c3b4e1a723b7d050430e1d3b4c27961c2999861a72e8d8613ab98c46d7663394b735d870e928878e1da6938b0d336418a621878e1da6938b0d2fd9b583dc5fe4ad348e6c02cb54773452e53da1705115c32cd124bfe734cd799ad3654e1be67c9993c79c37ccf9ae5243a6cd901d67a6938b0d2f3c6e00c08e95ec0f043eb727171b5e78dc008000680568c91e485ce2a57e9761b82e36bc78c6236637c80c00330b000e5a0190c8b3d287611c392ef1d3eb1cd90d1bf985c70d0008000e30f9e4dae67f52341f570c8f1b0010001c6072cc8e0cc39d29c3b00c35f2859f8429493ac8021fc3343af80a40866195ec5832d1dd12974c75d7524877ad8222492fb97f4386e11939a63301e410b31e3ee40fa9837316267b19c35c9634ddb55ca2bbfe98a44d3205242a691d231a9a0d80732e94630620eb5b8139c330c6de733fdfc145d4204754ad727752aee6f33189b517b6913fa34f639834000792af37ea64c955c7c192b2882f207e72b919b17287698e78cac822962f85e4a7b85e9c45ec197259b63332487136828feadc2a0f848ad6c37f82fce31f2c1b8deeadf55bdcc67847fe250d90eefae549e84d243631c1a350067d3ca19b3b79cb9f21ec24d624ce24b61263125fa0b75b2631853a7809c20efe5b6528695ce69199edcd2496124789bd4ff08d8b4b0c650884534fd52244a6240d8fb804b2b4833ef2258d9f802c6987403a4710feb4a83e95bde76712062f354049636384723d9206bf1709644a1ace860c1f255b50438692c646868f83133d03f1137ca58782df0a7c0902d8651a3f717002ef345ac03bc5f7859c512177e80a0d78cf0a2f0b5946ef091fdd3a472e16792bf0b77c1ba660887d2aa893568ed7c4ee0beb716c7b04c01d75fef5062e43aa0d64c93affbefacf2871211da5b449fef5ef9d1555cf6d7c08b5215f6f2f6c43b69d2a725892c61922fe3344eaf6ef5d3ad909f4a398cdb79d5dfb623a2643d80249c7f782fdfb1c0379c07f9f830e7df27ad365d4b68d5abaf7fc0423a7811f8b40ee515e44ed47ca87656370e1db21b60606177e665f60a1a02929d9d6ecc37a2f2626f973c53b45d5eb7c0d70ea478bdce136287e8b62533de144cdac0bacaa0596052b68c2aaa093d914581458211fd6a7085a8f6db327b02628c128b34c5812bcc8ec08ac086c4a46cab691e67bef3d49258eaaee606c1384f00650d218bccd06b9b71de43eb971d55b91383ef1ba297277322aaa34967397eb2a2976c17b628a074503418a184557b9c9006a520802c4e101ac37c83ec4fbf14081f056fa35563dd1585205599c782b3a803fc8fd3798c2a34d741a4d63398dc6d26e3426e47e89cd2dc76d7229309e112a9e0e971bfad471401042f4a9da74e16e511f16ef7a9e2228abad94df8684930fa1f893218410daf8e9e4e6e4c626bb14efa4b1dcb59611ae105edd7a903b3148459b13e47ea75c5445d583bde58e714814ddf5471bef1869ece0a8f2d2741a2f55b7ead5a7922123a3b64d08efe434f189777216cd6684510fae14b96790ab6b1aed60ade525ed486b384ec3a1349cac69aceee2d358eeee1a4b635dd06fdfc13349c8e862e6fe8c125dcce7957089f1fcb6f2936fac8cda9010553087138f86fe0df45b7ce295faa59841ecc1db11a778518ad7350d95434520e48e5cc7599d1c63bca22a46558c30decb156a13f6a213f29db6a8822cfd9fbc552b5ea95a5157fe616d197a32863f38dddd5cf14afd5b9edc87a5c4cd9d6c6336f1078b9a777a2f8b07797cae782cfd1f2344277c6cf9e23e2c4c7a43f9e676e12d7faee7e8b970670c6faa0bbef68b3b3f31cde2fb156faa983b31dbece21703b1bfb683418e6b3c5f71cde7eff6b957763aa7a6932471998ed64b2c08c3988ec9401eb37554d009080328034803b8fd20aaeeb515cb1cb3f5ac83ef57ec1984fba6a6595e6275cb446b9cc6e96ca3459973ab9d1d6f2a138d8bf9934d1cb3ede01703b51ed2058fa53f5e78ab79dd0c5e5783579a22f76f2a934de591f12a41b82eb1a56629a74da5a4bbd1286e518fe4fe0f12cdc78d0c391c0df18dbca9bc835fcc855d723612a164173eb51151af56a29317173655161545e60bd28577e0806463434a91915d58471429437a480a8ccf2e9c038bcf9146921b6789860286aa553062d4009298ede01922bd04cda67a2b57aecdc4eb54a75e133184088921e3c234f48c8c96e99f32beae1ec765f42b12d516f5a63b2c2a0bb7b21c8a3556a6852b72e788a118962c812230e4fe58c38996842046ee22a33e5ae466990801814a4395c71766a9ab2ef5af3e80752167c2193e49f868f15656dddda9844bd28f7174ceb802712d0264b9e2add4d55f91fbb096a8b14dd7c490b45a33f08571d4950766c41e62884383833f5874a9ffb345eee7668decc23768d4b8b08d2d341f17bca951ca18aed93eabdbf694fbb02c08cf108146baeb3c9c1c5c7568e887fd606f7585affcfa6560c970e1aec3badbd9c82e2c43092bddd28decc23572c5359ecfb09aeda507047a0afab9b98ddbe0bd60df321b864252a01aa8f2920a168136ddc169a363034776611a8eddb8372eacd2f97e1c9a0f96fcb9e9c0c5513fac7ce11939e2cf0dabaebc54571f2c3eaccfcd112b46e0ba7a2bfd2b755557ef65864d95ebcf15d4905d98cb19aeb93f7165757739c745d48c3dc445dc8c71db90eee67f6ea2e8d3e7faebd3ca587477f36175c781db2694d3649a6aa44d91be9729c40b22c906c5b60db966d07c5c19e32840736768c92e8c7356733fe2cfe7c8a7d51f24f721aea1b931f7e9e53e3749504046e6c3f2d227098be6e346feb03670e1a33e2d27a2d186391c306e4616800f0b860c1951e5348f718f69ac8fd89481738dc45e2c8b31c69a6d0f267991dd0f94f0de7befbdf7dedba627e42a9e2a8e538a1477341a8d46a3d1e8351429292929cee5a003e411ff3dea457e1801e0e925e1464b0e148ffc7ec63bb5605785d8559bc21e6e8b9f7af09691ffcde47a68f85e24eca107afefa607c9ddf4f06e7ae8e1c6888d72d296f1dfa31eeba4f274e4f7b74fff537e747fc8d2df0d79d0fbffffffabf8444a4a4a4a6cc863caf76e70e1bfe7a558a1186c4a7e0f210ff9a216970d9e5ecbc4b73f3ed60259ba05f26869c9dd5eeaafa39617d3d272e35cc4c8ef6d6b5262e099b0e8d17cc4c8b9565a4ebff7de7b178c148410c2941b19ac55bbbbb156eeef884932d6c6dddddae47e7fa192a131a2a8b29b6993fba5cc6e72bf1c65154aa6b41c2d47cbc98d4f724b94654a5aecdaa4284b9992725b8154f6a9a02aace25a2c7bc7c0335d2f8e8e3c9256d25dc8d0c5a6885cb1e40dce0d4e2b494e2a954aa552b2e5a5ae3868d96ab55a545423371f81919391a93636ed312727272747ce542a95a2b2255bb2255b18ace232216568644a0a912a3904a7952467c95ca5a44d2a954aa56411b992ac1b89b35aad56ab552a954aa53212c49b88d34a92b344a552a954aa99b3f2d26ab55aad248d4c4921522587a8542a956ab55aad56da1c816a884d2cd245560e55cd9c88d34a92b3e4e6e6e6e6e646a552a954aa99337366cecc99393327f76b9b0872224d14426f6e542a954a1587449b5824ae228bdea8542a958ac22a2e8ade5471515586a65695756f4655c693d09b9caefb1f8d5237a9ea4baecc88e6535148a553be6a436fe8a43792dec44eac32abf79bec389e24f7773854bdf7e250a311e5503fab4c9751d52677d5a6da44183f79ca4ba897b2c1858ff2547bca539eaa191c8007ba1a34f2fbee7aa96dea0c2e3ffcef63b409467e6d4a6913a94d2f3af666af4da3fcde7ab2d7a693fcde823293fc5ecc6b53e8b50924fa9c784617a5f36223a564af4dd96b936d1326237b6dba2a7e6d9a2ad96b53acd132bcacd4755b7d2bf0e51129a5fcff22ee52c618638cf1f929082f09e1922595724e5b03897a58e44ea194b443949109f6a9a0445192fe65425a72c28b44de1694b92d99993ec1d4844b3021c4e7fba3ef3455538a89f2a87b22bd7b8789862921c025e0127605855cd694e299ee13deb469d3a52c3f99e47809a74d2299be4fe8540f33284f7aa8fd98197b1414110d69161199140185408faa5dbda34b942080a01807674621e11e74782c7fd3ae268e91690a7e750e7567919e9c49ca9f89288451370b2622c8fda0a7f5e878c75f4aecb11d425972472ca131babb3b144d7427a4bbceb0ecb5e90467bab3b828b884ff6942d13ee79036a13c8a91bb3391fd5ffb12d9412d863ecd229d7754975d244ae2752299ee9af4ee3639e239644281c58d8f9aaaac7af14ad418d2cf221eba1fcf5f0ef5371322a2f1131422d3269190249e49a40213110da62778b405425c489b68aa3b153c1325c1ebfa9b9e60fbfe57ea27fdc99be4f889c45dd63561e960b5b49032eda9aa77f4532176e5fd2db0dfb9bec9cfef7a5b3709bdd74bb5d2549b6eeb8a24d2d316bc13e9fbe90948a39a91a57413714e3309d3d4981728910617769236757ee55debbda38f5d7d969743752d59d2949056b752ad1bbabbab5aef1d8dfebb0eb3abdcb7453b1cc8e6b6de4adf56445d2b5018502850370bb91fbb9990dc54b85fc8ad9a50cc215146497320ede024191da6bb2eaa3b6cdd564ba43a691960b04fa56b9b6600d24ee47a9b56f26d181da4fc1b6a9394524a1d7258fd80c9ba833d6020cea1bbfe5a43f7a68c6aeec554fec2359dc7baf8a14b40a2b8f2650e9c105df6774e025d26ede02f5fca9f3fa5bbb6c30d2e2e12477e38ee3730307efaa14bfd489c03b2a31f46206d02f2561a88892574ead8387538204070ea0504c87b79391807ec0cec0c5634cfe3c36aa6bbb75a6245d3dd73598547f718dd638c1e3d7af4e8d13dc6e8d11d93e25fb966191a9b16179e7211f4aa839003b50055d0053d3cea6921b9676570e1b7cbd407dba39cf4ba36e71ec80a086d3c9c941b8269ff7e23fce41c10a147654b7203d130301f88eda2b5c41afd466ce81bcc5bbd97976a62cbde5b392fd8e97426addcb66db466f0ca90d334edbd3c7bbd9798bdf7b2e5ec65efbd74f6b6ecc5ceb6514a630988b8f521f68efe177d8093ab15080d91202e39025d4622663e9e99af0491202e9908a7a1c6488c8a1994fdadf40c3ee4aa7cffe7dd1de516b9223a3b91223abb73b5560f42ca0a2bcca4463797e575e5ee703ef60fa70eb13d11b533c46b43643fc47dcfb32e338b5fd62888e66392c0a18b9c19254631a057e2c62401519f1ae2ce10892dda67ef726db613eaec853fa2930fe8e1853d4b26aea901af6bd9840fdd41eeadc0c79e924999bec85946c4e7210bf45c2affceecc22821139a5d7874e1930b8b72e7c226d966a150d640275ab685e824b35a74b15b27ebc89b3f9dfcc92ebc6926d9b52656860c119663fcfb55f385690ecd1ced8becc2925471a83bd1475c73f29dde21129d7016656465dcce94cb5d4aef803332047daeeb5e49efc3f885238dcb75b1c611fc0e08c3b8fdac83e55f3526a1d07f424ff331266f2d4a884cff4108bdcea7414ba52903d788be836be72578634c422d34a31893bf38c81191a3f918113722bd401985b2960e3de3f7b7746b92e649a731668f3887ee7015197e34c2b2c17bc96fe5b570706ee05bc95204ef845f09be0d31a0c1050f053906b2c411c013074bb0210bfc171940e9446e1b29c8a1fabc76683479822cdfca12211a08169382a66891362539c4e347d278e993e17b4a32fc2e2cf9a16fe812fc891f12a982579f787206d9b18eeea07da84e0e02bb98f87529872e513983775279250fbc2740efd1807aad1d40b0785b94a28e0c71cb14251b55cc9d9d4305c932931d6880949187a4812c9f0cc41bf04e3204af244310b9e8c911c70cdf7d057e03dee93d20b2038ff4320ae63944a69c66fff3afd2a65b2f10c8d27466011ead07cc8c5cd453829381846002d22c4954c5453d25d006e1a29e122d08cca1b729360a97e6a3f3f6231b059b2d8ccea378f2b3a28a2c3cd9c610fd80b966c6bfd03eaafbc9cf0a2ea8c8d9ca90da0f98454fd23eaaabe56745152b2d5b1952ca39a79cf1d169323bf959c10591ec166719f6b733a6123ca397a159c18512b2d3995f11b0e083ec6f55b20cdbd53df9d7ace0e208346f34bf22546106d9dfe2eb6fb94ce29a35cd007646bd66c81974063663c66b9f013b1bc4e90e1e01c2453d25288843448cd86399bd628c32464fee6fcdc71b0397e7dbeb27fd796112bd50a49c988465965e9b0c59369f523aa7dc222a92327bc57944c6b00fe219cbb4e62b8867fa92c69fd7c33c2f4a9f4e1a0466fa42f3f1c690e75b1b99c432fa737f4f65eb4ef38c3a9fccc02fe68351d5d3b9580666fa12459a413ccf29e9c4418ec82898493b681fe3c3ce8c0f33c4695377b613bb4f4dd334ee7591c4953f91883f73847114f6a8ec51f651d7cf979884713eb04cca381f58f62759ce07964917e70343c53c831c91e3db2bc3b0fdc17d56fce026a3c290af77cdc77b429ba4e42106f99851325146860499086d87934c399405f2c5bdbf25300b418411322d831b3ea104e95b4c3c63272602c4a5941527fe305d9ee602bf837f4749dec54f9dec2efc11fc575b4f38e62d53dd6fad38080c07e142c06aeb67ed32d7bd3e8689c8b73fc007e2e27ea84fb51e75d60804d4de910c7f904040f78eda0e3874d1451745c40c44e74c25a24b3a2595d2e5fb4bb7d97148eac032c53774d7b37b1e65edbaae75bc95761da7fa59eefc54391dae438737ec4d870e1d1958b160ba1cdaa4838c2e3c208187f7f27aa6238eaed3d1d2d2ee9fa384f24db9e1808393a0654eab813cfaf7d74b155afcb28c7e63469fb3cc6297ddee5b812c80b01f3582953e8acb8f4ed95cc8d0fb4b88508e316e71630b765d302b988b8369f1138ce41a83f1e8a11892f1265a01617b9452ce4adf561c3385137aa6b8673b008896104124814070e456b9195b33a96dd76621131b4c552c7aa07620bf875040ad879b0b7b8f6198cbc5c5e5caa0eaad400cc35c86a08d8e96166bad12d9e5857f34c2d17559ccb41efe3194bd135441151c026d322ca2eaeefa88211451a8a0ea0333db06552ad58af6ed40cc8580cf5a7ffa575a27ad4398f06a0091901b4ae157371007b2fc42962a56a86a135c7517dbf472d7fab006ef546baa7370f55edecc95ce862bb86a81abd52a360542cbcd34e6ff6bcc631f2077bbdc3d4806b8be7e022e2e01ef5382081091243fc533f4af97fac3c40df296642262fed1005f8c8f62f6b7d3dbf4f3ff3b76b7d462e8ffbf97273b5ec1c2ee86a317f2177d0bc1fe6ec2c66e7f39f647ee4382dbff1c891c1d421cc8af81ebd3c0f7299ed493a2f201c9401acd38a941e3868dd20b94951928a54e393f22839bd4af11ba4fa3c6437ae7bca8682359f288e59da5c381a3c3f19605cf10712858de5dc5e2b14918661cd86d6a7c880ff195e645baf32224aeddabf5e019f2f426a21021f1621a3f71e4142e1e4bff073b159e155e161e175e185e176fe50aaffbc2ebfae52fb1f746a6ee14d6cd0613a954bd3aa52568682eec763ee332989df11e2c03eb06a785a4c8ca488b61492fb1353e28df1ffb517077217bc1750e4949087b8ec897c8d0683e2c0e3c98860c4d4a481338aae2909c259ab73c78c6d6e9683d48275c96225048743232e99090b45a4aba4b72afd6e3f356947930cea4d1c90d1bd5e486a648de4f1be995e8b30f2702c2307e84b937c1f84ff07f08cb78118ef11e2c233b7e19ff8df196c38e047b1257823d077b177c7499f481560c9170973f1e1c23cf96f796b72d7886485361e3431f1b2113100a8cd08b14130f7ec1c2c2758a86a6554270885232940cc23536dee5c992d1c883478f34f260118a079b641076242f6cde0b29834ddceb2dce3c1804228d4e6ed828c96a724353f402c33cc34aeb05d7b5d034713b6fb9cc833fb984e30dcb4b2bc781921c9b1ba2620a6fa5683eaebcc588e1c11e2c583062686fe1bf9721e31a59ad62c8c8f0ffabd1c9241c6d5238d29c9cb7643cf7388c956cf9068e393852317224dd258945baebb8fa4c1abbf05efca91071b0c8e3196f06c779b0f6019146b108c481094713f256db78cbc68796bd35850c9907671984bd853d49774a4eb07721ca78c9c510e27c890eaec42fdb0cc2300a38c483af2c534d081102858a0655aa9a3f182e016968e688a0404e0d38d39dcc9c5974d2554f5280ee74e864c08183f052bf0c1a10ddb576f09cc279abeb6c641e79925984c4792b2ac9fda82886dc62782f2f87700e271c0ced968017f425adc7c71d3443e6c1314b5c44bdc10554f260cfb206c70d1c6d702ce271e59115b13811cd1ae26a6cbcc5816788441b9a8f2b7fb84773037bebd1d06fc3fdc683bd5db9f46e4d2de11fd9f024de6ae9baeb5afa060d10074b3641d25b33dc28e5e872e4381199e4088172e4f070de6ad5ec0f873ea5d46b340f5f802d91482412bd1793fc502d5de9f108e5fae1d9436eeb3cea3121df473d24dc8d1b695c67b3116f050772d2ec7a9e29feb3e209c0a4889bfd5127f9659c115dea3742ce46749703ff0b3c93733774078f440e1e792b7d045445177e2f3e51f27b08f37060de4a5f9946034c642fcc5b8156945d1e145c6a81f1929f5e4cbd38d4cdae0c8f6aae97b42d3bc1211e1497b77aacb72323bcd45fef68f46f890c19058df0537ea5fe2b64b4a0690217b005363b0783030b63f395556c83792bfd0f86a040dab60df2a0c06c5d1fba6b2319959d7ce801c829e36666ec2701231393bd121f83fde450d75b8981f8d00391f918fe01e6e8ef863c1e863f4449b91f8efcf05e9e0f6d9ad3afca45096453be07591af278510b9429a9bf197d353af118020b33e4c1e34196f9a8cf9b1caae60861106a81f2fbfade8d9c2e0f863accfb897b397ae9306fd582c28d7fa274197dd792a1430923d7d2d2325d5a5aa2063c2f6ae0de8e8fba5ef2db0d39b0a564afd62a4496a1c7eb1f9df279a9df7b6f76d893b8d06293d67a796eac9452babd56b1a6512d34fcf23643247edee3a93f5f4c05816aee7bf0c7f3dc93fd5dab7ffebe0f9e0779be723e7cea7fb8770f77c49523afa07690c4a88e52b7b123f40d85003803a9c32c6bd308d35e7741e8ff7879082dcaacbe536cb3d0d1d252e14ca537b8b8c05a9520c92c56001d58263f2cb62ba9f9a82f2bf6246e0e3333da0f3ab307af0bc25ab1b4360af7467e5978008a4c3df808223e78fe7a38cf5fac7b901400fafb3e7cdef379c9f900facb1d41443e88a38097fa636b43f870ffc31d010477801713c40e57961caa87fba89783c5bc18cf6bd1432baa5108c8efad2cd19c85c2ad9c0dc2cd1a1c82129ca7f9c0accda0f6a3bae603c332d468e0127006ce6c19c5e84cad31766aad1d2dfb2cfb9096dd4caa320acec019382363066a5eb179d15a61adb5d6ae8e3b1c5d773f546284504208e5d78bca182116310dc362adb5d6fa53eb51ef3d62dfb15dc521a24fb7469c4092b3959a01a076801f35571c2a9d4bf92895fab25e1d595ffe85491a36eb7545fadbd3a757adbf552a257dfbe875c94abffecc1c5e99a45fa9a4549398a45d52522a29a5d4297d2969ac0fe3b94db45e99f61a362ffbf26df62169dd072dc717362fd881a310d8245cf8f15f955fe555abc568ad579b62f5f93ffe27a61896fe98fcf1f294500e410a9afc32a490625285d01d3e8c5a8c2fabb46994b5204364af695f31ccf46b6c48b1cd22be7e9537064a1fa552ab9455cacb625356595dda235c98df10ae6022fbc799327a63c8f49f5d5d9906e5f411bada8f78693f502a8d326e998661a616e75608dd553694057284691e2477439a4af7840b1fa542391b840b5feb31842b68de8ffaf243b0be6b3e6caeefd846dce1e8aeb3732af067a862da0f94f8306b3ba0e4e85f3f26716b8cb56ed75f169bb5cad75ebeacf4c9d8d45dd3b40f615a0f0d57ca8d5699d24fe2d6874f3e2449af1162947d144649169bb456fc3294557e108f52457ea8561adf3578c285d53b757fd97b5492aa9cd13d734adf73af723ef7cc7d9421a594f6a0f4dd22e1a2a0e35c4f86ff46d0fdbbdb669f5d55b6631f1f73eb3eed11463e3acb4741c13ed426ac0736391412196ace592d6e7e2fb54710d8858fde4be5466fe5fdf3814d4685e07c4288d2f7e8938fbe509b64f76763b0fd4fa5633be68e8a95cee8d7855dd70cbd47c50c51f11f29b6f4231e465d8f42cd7f1789ce7e59468f31b6c9717cebf1c5f85e0d5b47cf240c2cfca7826ab14eb8a8ab0224b973670fa10366b0b093d2ce360fe0bd17ee48b3788d99c4a6466b9b5ac7c5beb3d84014d9daac6258cca19c1c84e6223e26f4fd4fecfbdb0b7388bec5309c99ec80041521e822a390a042462657c06d647234828a43b064b27d22b01b5f2293516f25932d27822c90c5881732a5d8e4ad8cbaf3b7e20fd37708173e4a9461f812e19e64ff67692b1791896c0dbd9721e02a1731737c7f93f7320494c94170c83c5c196d005026c7aa0d00092a5232b9024800c146c666f6adb56f33fb94abf6a3c52cc53ec39e24b1275dd8932066edb4187e51db016b53287b11996c6d64b2e582f812991c390cb2f857c8e26fe4c223efcefa80ebf365a74c89e651939aa64d4da39aa6554cbb344dd3ac0f1b8a01358b192ab2cc62460cd97e7c1b32bdce3ad4a64e8f07a06a5f72ce19dfd6ee40190a667bb5c6837cfba1eb6b9b300e75a1b0cfb8886b04e2db8f0016639b300cc328fad8d74aabb55f312acaf998ac4fbaea93607dd2ac36732e674772f6b1c2875a161a882267dc5f6f310a7b0c15bf720fc6613ad61851d8f84fc5c60743f7aa7c0ef1c1fcbeceaf34d22350587d8a49587cd245821de99c53ce4961866913565daa7d9975740784fb9e3e4a4765c2edf71133cc12eecbef7a18e4d132966636629b84fbde2ae1f64398f782657fbbe1e8ba2a5c32bbb8707199928c0bdb49198daab07961db8216b1277133bb85d47c4c54fca924bfb7e257f35139d20e3ffff2420e7596ffbea3f130bf7e08662c62107b58638e3714122fc8f561671b8a01b972efad643c4cf96eec13de32b927bcb1072cd21d258202dd5d14c481ac36b5da8482ac8c459bfc8adc0f71daf4aa98020e4492dbdfdd29a52e33fa244bbae89322dd5c9404ec6dcc5c7bdd410c05b7370ce5499d94ef5769d38b1bda1443c6c5df5f8036fdf7f7d0260dc68cefeffc440308574972292004107e82343e4384b37e922587b2407e6f05c62936c24f504877f79021fe5a589861288f829d66708857e88f65eb9be8e78be6765d5524125d46b8b28d2874db50b66dc35a43d93694387fa3db86b26d1bc6c1d0aed836ba6d9063f9bd3efdfaf519353360124903cfc056efa0f12a7886e66368701049975eef404125b907115050624641c1301419c3478c9e37dcb26298352284cb610482f87569af6199bde2755dd73f28648b617b73df10a448e567c514c49061c6e2c719c433a531d2395ffba8cdcc5e536298866134f747fad6b3d89da8f5a0f3a164b54844603eaab3d4b488c5f93462d28c947e8c33caf954e239a3cb27259dd883429e19bc5e1fa6c1b4cc84200105339c792b3786a873900ef571ffc2d0e45b8a356b2da531daf9f6e935298539da2863949e8d38021747b25c8956220c351e227784bd4456eb119fbeec49e9c08bff693d60fcd37ad82cacccb8f931b0bf0c0c5bddc1227e822c088b4c25b89d59d899c5098d3f409905c11be36d28cbb0cd02d6cc7898f6d82c9ef6a366f1b42030c3368be7c1b0187db20c6398f6a3f386190104ca8a7022c230530ce3a9c6c1b8c224e6170428a8e0cecbbaa28bd2e96429b8a6c693303c52838339acfe705009c4b102259b81610c0d0c5bf86910a73b229c6427d5c3419c218c326c516a7e469c68c2f37ca55da8242df864450a158900000010007315400020100c0706e3f1681ce879deec0314801175984c6a501dcbd324895114c49042ce104200400006044064a64d00ea2bc09a2724a73057b2ea2914d79a31dd40a7d5ace371bf5cc0ec7e143766d196a571e9a6f9fa120431361026765f0f5feb6d95d49b0fce626d2520fccce42126174a7c165f3d34167a32b3e6ef9447cf161a708fd8c2e84b411232f43f7ed9399ea94c4750de7d68baa0f55411f6a26a6f895e7d75eec9ff56fbeedcd0d6d086ce1f1c90f0fb264a129e59b717a09b919700ce2f411b6c309464116230af4f1b0b693b3077d307949c015373a7287e7be08642a716e44f7720b153ff0b0070318a8ea71071b9120784033ddeb00785ae65801ed27a374e76ee29b2fd0d626ca8d76e8b983667c810cf509d480c8e4104866bd2cb9b9ca0440a79062d80a5ad9b27c826a57213b889f24b4e837362527b489f5c9e05717262d1e144017c7e3e6542b512e8104eaf74620e6338067a29a1ec5acdf8e91cc0a444020df92f473c42127e15b04cb3f20bb731823a1f4d5f393f5475d53f352f9f0513ce28f2351593bad2a12b14d93cc5acc5906119f7f393b08d7a09b734865cd45ddcf09400fde55c173ba6a757a0a213555f82450c7e68e43c86f0245801a9c464937153cbb64928ba99a61272343d64337ec093d2641fa96b763d87af706036d2a84df26752fc2fec50da043569683a2dabaeb1d2f365cef8836ecbd3ff00c774cb14a8fc0966dd733cca3f832894a188534023a18f35bd739506213af8cbaacca0af9de9a691f5caa70b67baa7b84c14bc62a9fc375a54075c51cdb46538ed3cb503b0522054893d08aaa5945dcdad4021e024dd092338d7eda8a48554b44da76890bcb0d16b51cf5b113f6037e746104e6e1fb4049d3d2780e4405f817c637855b1f0a97082bcc4049181484c9620554c58580722e54047a88c19d62a1174e9a92c319f1131670c0e0956e1b0422159587c7d5b401a071ce8e94915313f28630d2d9940ba30a566a2fa0a9124d72c84ed68f06f6df51b2b1092d276c0916fe906743dea82153f62e532e8bf96893d02ba2c6a1ffb84e1bf4424310221451e15e967df3a01e4866e677ababbaa00d0535be33e4500b3d540a8097e1bd8c12895abffe9f44fa37ffb0c6fbdbede5daa8e67ce908e21ce515a6a18ca9f5aad020e3b4504cc6d42f4cc3d48b2359c578e38b7f050979431b29f1df293f0f5ab1a88e621931e2b76e902c744fdfb1c8147ff151921638b982db3ac9857194dcdafba5e40191383462cf7465d97eae26483aac2f34476de17a5d8d6848c104941be9ad0d822c1992a07d452b348044a9e0e225de2f1fb249161c114b13bd20dd86b0d8431ffd5a258a8d724402a49d502d8b0bba725181060c2263cbc8b1c672d957e7d1810806e5e902b190464429c0aba642517e45f21b28eb46e4daeffecc7563159600f908a948af5bef66b4545df539c2da998fd802a5683017dfec0effa19efd64829b1900520397596dd40048d14a7c8bdeded8368e6953cbac6f05d4080bf69f23761b4a8eb1b2c5ca08a3c5bbc02e92a9074f2407d4605c34ddd4d0f1eb2b9e90176e1dac66760423435192e7236aa3e7217a8c4582600ac2a317ed69a5d3d3c75ef984404594d03baf4acc1815ff951a7bd3ff5f6d922ed16041042411f969805e8173fcf43f758a671a238c7d0de23f216b25e9ffc1605bc9bfc058c72ff608c08cc1c8b37cb42230ffbab7ea49d3b6d884225cde5569436f52b172a263f6c307d5ca990ac1900e3318250fd1e7ae86cd186f17e252fc1b9d322e1abfc759998e5272f535456bbc842a60afa49c83987bfa283804767777856609b88cfea3d5a0fd2e9584b827123fcbf06db004a7834475991b46571fe3edd621f3644e0e4cc1aa1b8b321a0f35db2a80bb962233a653e55e7210b61403911401e968b55c67cfdb8443e0c5de655b6388ee57e0a332ea3f798017663ec1a0a95be3ac83e7255de14de951eb9983762656bcaa0c2c609e51cc99805caf00fea0d41d98b59253bcb372009ad43a00ad252c08e7d3aaf27c0ccbee1421cb004776b1da766153e728447be7bf40fe2ccd10f22ed900c9400595405edd027167c368edd8ff59bb41249da82c12aabb75424f470ad57a5d6a230e3a708fb66c4559b3d2837e4fe06103d5d416f7a50397cb8cd3050bc310ccd1e5685caa77748a832f61435c96f5b32f19312e9ff3373cc9438f69c338babb2b0af74064afde96a844e59cfd17c2ce70765153e7a430e845179cd3d0bdedf6c5931dcca33165a725063efea276b7c835507c8837bcf5253a1b3a15fe51a3923f28764c5ce369e046d4b0f0b252c30c545884691c03e6b128915b279129884290fe188d7a883339633c9b552e9f35cbae467bbe555c2dde11821effe2d8ce07c897a94d8e34308ab65d031eb1b007d027b00a20c0124c8bafd9d67cfd01b7a0783bcd131764e71f6ad142e2a3b883f2d2034f92487a4e559bccab584700a93482b0e7dc1532c42dc1d05f8b31946a0d5ce26a9529e793b85bda9f32ee1094b4998da848b8ecb1b2495a0c2de4c07800196df7fce5199ae8b1d97aae2e377c4be27ee6244ef2fa70699824f8062754234b6a0799fd74f97006deab6bf909c7033077c84da13827b72af8e01e6373263d59e8731369bee19a9776005722f7a466f0a9d876b52559c6095e8ad01bef83a7fee1ead8ba77de7afd556d1d4c6418180c5c8c3d9a2349f23d7506336bf6990aa7cb2bfcccd03a436f7f754d75e0b318d960c13288d6d2661aa65b1614d445482d1b169d6a0dcfccf235f7f4fc218b8d04bbe241498305c546307c2b8aee1eed7363fe3bd9596b5c962c13ce143e61bfa78b96366d310c8b23b10428ca00e04c7be4ba2f45fe10434a574c35034177ad2b3a1a13ea7ffb62c2bfbf800ee3720749eb9d061bf659d4ee327dfcfeb99562e620cf593d513d4d3d68729073b805c81d2cc060442e43f054a4711413726689246ec030444921f4a4989258b205dcaa175ce7000db0fec815e2243a8d4257043a05e411953fe4d2da6ef58c61a410740d122ef0a692e60351efb22c29d14743a59419dfa5abeb3b1c53db3c2a7c0becbe87446207fc84a85b120cc408bae3f3f2719eb6f425b426dcd831ac678353f10494ca0e269e3c1a1a206e691fcd5348089106be0d164470e3af79366d802812a5e75618dd5b0e18a0a373167f6ffb4944a0a0164bb5a7b6c3585ee2ea85195025fdaa020cda535c7a484f2da6d2e6e99928672779967bf8aa4bf10b903db478066e8a5565f2bbb1cf3723b519e168fbee39caa2fa279e45f0288b49eea433defcf2390a8c4a2ea3ac2349eb0d416bade85651a7a1e5d67a5b004ad32acfe35a877f6edf0f4a92fe0e351e2198cceed4734ebc03e7bba12897a48615f529f2a4caea2e14614883ee384238b19d515d160d4741c3a0c9d2892d079e7d805305c32570a58780ffa09631e22136fb49b6433bb6a5294808f71dd0c142ba4ab43a4d0ce7909053e033e937ba7216873d9a406ffebc8d10b56df6e58cd6bf5af88d0f1f6c7ac010a21f0b657057500d4d7a2ea1f302303ba6f4e697e736e4676a16fab7c100497a4258d09b740a66c3812b2a75fb6a20c591ed07910f9143256fd0ca6d9c40dceb069816384fb3a8d0e453c0f92be1a1f8839c4e35f49b6001675143a6eae3ed73a042994b3d0cf838a563fadf7c854123e034bc413fa62afbf887f2717e88068755ab420993772286eee0a4b0c81661f43cf37e08c10038959382ce789e33cf5aca78fe9c959dc9377a48916da3e19645f908e8910208f690c0295bd1d72f98c5d398d6b5d5862286b2aa916d063a5ba2c71a24443933532393f3b612970b874f7a6d0c15036b847326a39b22ab7a656fd65dccc815bb31e5880dc7801559569235a2e5565ea38e5145da210c5f2f910a415e4a55811281724b1c0e750d34ac84cf735964409e63ed9763a4c006bf0ba64c68ebf457080417ce2947e0cbde87b0e23c8164b9194d8e6a0a22fda0f37a4195a731670436f2b04fa0bb81058c83c4e0ca9b9a286e51a330773d88d5549c1431c4928619a22b31c62159ba5db37381704be38c4212a0f89076355bb13581015485b87bd2133ade04eff1ed088e9384726d605590e37600fa0a1496994a0a7a4c1baa48a5a02e63f8e0c5707b69c634bfb9682d8478a337da41a6575d04651341edee26bd1c8d1fb5736fd1d13a31297923072adc068d481728d1c9c899bba6dc84d0d3f86f67caff338ed1f5ee0986ef97e370a21f2336f34d977af59a95102e53a8c974f1f4931e9f3a8ccdc5df783f0e153ffd57145d992fd9e27ca5d21103e94f881792d5382efa51989c987f33d9822cde619ab4eecdc7ce9683997e50d8c4a8505883a72ac049d7b3b986671abca977a0c54f8a4e5be0166c6f8650bfe00c1e89d50012c9bf80e8b5c7954f678b16b93fb0a8dbf37b870023f04386f4d9f223219075c17cac7780a2f71394941d1b71d30ef960a4fd3d02ffb9ae4f734867687a33218d3d55eae2eca41a16e7511cbfe4eb8e778d941dccf733b9fbd004b5f7b5afa17dba00ef239cf96141ca4185f8fe78be766b8c4d76c5e88c65fd2052e70a96cefb0de6fe8b4a0bf221500cd671132e11a29839c9bc31646937ae4a4249480d67801246fbbd1b8157f09171915a0f79164830125c441c7fc105064e1b38a6330a7231b5750d0088ef28e34faf45028c4c8a202a1370b64da72d50efa3251769a770a4722a36a4a82450e46e8e0c74cdd6740c882585b72307f060d98839884f66d8041d4935feeb21469ce08edc6715b4ae80b64f7d25d21d02a70a8dadcc3e58ca00e858e95ff4f8800f9f140f94f0bdc82b744c7103fc717c26afa27865e8f3f3f4e77f0d5b4c22577e94c5115c7f26d6bb32a0d89c9537058b29a327d8dae9b909d2bcd59437de9140dd068845ba88669242e9c8cc4b59cbbc75ed1c47652866eb73533eb940e03fc4691d3872c77d5e372f714944bb55cb40ec7f05184bbffbcc0452923af796fe121501f734cb7af799ac2a6db852d141392dcf2bcb0491902a04ddf975e6341d64f12623d2bc9c8918988795a68a5820885b543949a03807b8833231f6957b1db71b3b4293f573905309ca1a21976a433751467149641b543b2781f590aff0b680044c1dbe5e859e9bda52cc0e8cfe44de44da3976693c188c0500e10426baa73b8152b9ac6e162d3332fc3f424d2d98b783b115a557a52773a89946729a85f13d1798b764f27b6f11621815765c26175f47af0161e36b47bc6d0618910683bd13b393d49bef6df1d9aadb1dee8413b7fde8d9a4967f10ebff9f7f21969d49e97a0d516575c0fe4974c26d0c1cd31d09cfbf83bd25507f9032d10b838dad8b20e20d5151fce190677577a165b3c87a72c76a2ed83e1c49f87856c1c1e6e3609d9a3d270b4ee5c306fd7ec19c6355b6c9733768794136978a4445ef49ac01cc74d0ec753b3e5fccfecfe40c37fad0a9ca0c75bd003dd26ec63e8908c8a84dd44455d7326c6d223d0e5ded6f0ec7bc8b7e55534781e04b68c9768e76a234479edfe2420268143737b11ac1123ebb827f929a2974d44a24d36d743138bc7e62d55b6b58729bb31d5f29306868ebd6570e5d090304abe5bd1662ebdd15d4406ebee50cbc1535be820eae5d0e3c649058f5a7219f7d683ce0ace10f5cb700ab2ef36feab6a4f20616c031c330d9c7d3b7224e2edf4af48178a11ae1b2b22eae1d071f1a1d2514bd2be6594bec54aa5d2ae46c50a514f4767889d2bf6a2aedb76e0425b2e9ce5099adb4746b66ea897879956df5e2ae3a307418f89ffa72e735be18d207b77f04649debd1eda00d1cdf1b5f425eeddc7fdd88c9608cc4901f0ccd8c4d6917d33f8a709f62a1880dc683756f0a73a5603664322f4761a951ab4c6d978e696db47b457c4461b9b6c112429e052e0280024a6d7cf4f044ec75db9fb800296a339850c0c0b0437864743de3db8837a20c258b0afd0ff0b85dc8594e939b0511eeaba68730d318e31bd787e06b74f41d3962720eb39a33c990c1d1d383e449ce974084801f1216ccc708ac774828fef0655a44f6c907fa8aac247deea933f0f739cf2c9ab6311c06ae66109c60557be0cd7a6533c4100671016485d56f142173d12ac4905757e5cf62b3cd5e5121bcc11a121ce9db19380e58f112ac040b70366a49244bfbdb030181a313ed2e44f029506ac0f2e0422c351c34c5e426d97ad544f422432e4e0c2061b8745233e330f4cf94ea586b23184b35a83b0f5e8fd48206f6f96b8d55479cedacc5849c589bd792e42cbd582c5a24b60dbd7df0b3188536b938300236aead2d226c32e90e722969f099c2f77018c42a6bb7398bb3245c2ad0aeec1f4fe8f2d619622de0fcf9f536e5167d856c11d43c43dbf2378ee8d269cc69fac3c4245c12acde89b5b73e327766cb731358a876e5c994acb6e0614ba8cce1f782ff4e7294303ab25b2424369c30b45837f00b240fda1c2f2193d456d7bdf515c726390496e4f064513235ffd44f257a6b1029e5faa5340c26b2125a5450a430f7fc6efb0e487bd4729d26474e7a0b92a58834b0f14002644fcd70e9e7af32c10407105f4c1beb3f418f57460fcfa61116f7cc58fd100841806b1e2773be4eae72257f432a26130cd7386e8f01d4b5871a00289b917211b41017cf9826f50039dcdb4a2f57b0e4961bbab9e0feb59039cd04248ffea62c4826a71f550e7949025ac03101f5ef22448dae8f35ab5aa0f4141025a34c176b78b653f20544edc40f7f3286871c61ea7f91c39ab89c81ee55f52c196c2f6c3b40fc5572a805c804a43b5c32d364dac08847b0856b6db211ca0108c14ba7a8b63175ded54276427d8df74a63c8aca9b6ec0e0f3732cd3806945476c6e68596accbff95e8c6c4ab4cd6fa3b9bcb55729de3d69277956cf31b63838ae439e28dea52b1053a529942872c0105e3a8ce8d087d062af86eab4a423c79216b6cf0bdb72b56a8267c89c36a540afd4a46cc9218a74c9aa780bec5841925ef2f4ad09701a3d13e111c9ba8dda7a09969d1f82e014751a434a8e65be55a08330071cccacd6e61dc3d9698c14000f19a992af9242b2e6b70d471661461b3d731496bbfcba90a7227ff6dba5952867e73a44dd21d346b42ff38ec6b1d45ee3d86fa147fc531b83cd08ae2dfff344235aec83c2b6ebdd6b2aaa8575ca248370dbc37810411dbf3875d8c12e75db435bf9dffc379afe60d3d4915bea9e707bd010921c76fcfd828cfc1c3b5efa8cb4543536988d60fc5ca48f7cd37326c80ec6ab251d0ad56ce1710a776a01fd3105716deaf8f98541ca1ccaeace1d9888323e01b8d6d10ab88902a2c7acc3d19ec4289f726758fa1d6b517c0ab8f8a48383e50260691833bbfa0d169cfbbe037b5429c8e7cb1dab279cffb4e222c18c4290958ac2ef1753b53e77b02cecd1378ba0bbaf58a76cb325bbd80841147347f0f28f0fb231a8529a88703d40624b0444383f284e08d339cfd6ba616d2111f2d8320b1c4d8da6576225a53b21440cab0c4da7a0d39bf3dc16b2174a395bb8c661e45ce4f93d2bbd48413f9f28b7ad496e1b0d984d33754624e66dd8af73eb5d84a00291bc409d3956c1943e052d746a71b0180c07ca32267ddd237f1eef85adda354d233dd773b63223e0d499d1294691d8badc04edd7420045cb86a6f06b07a6b07ed97049a88a63a0a9571e1037f14a1799e38008380df2f8bf7788b8fdb4f0abacd253ae0aeea8dc85be6208f20e82b427382241974f8814e6b66d2a331c8a9a199d0212381b2737a1aaa03d5c4a4f85301d57d1aef2722e651f5fbc59b1e2858c44e6436c4391185f87f3391a8001a27143a4fb1c2f35c1490abb6c577814c4c7a78837ec684eb1399e146f24af525e8b3cc415abefa34c1b7a0bf7987dbfcd88d64f89a22e96b7e71e66bd770905b3b22f04884ad400126e2186b8a3991b3df80a7512c9b5634bf6ac6eb5e2d160c64f6ed2106b25c57cb32fa06fdbfa687711abca6a3ccd682d3c258dc9c9aee0153bbe2794077dd893ba17c43bfac77ea3f1b842e2ccdcbfcc131f47e84de39adfabdea34795649578ba1f8be584ece6663d63273f76e779e28aa76ea4ee4b504a24c4cde2201092864a20b188f9f8ae8bce20d3a594c48c2a7710cee9b6a40bc6f3a1550c6f25159f9d39a64abbaf813e3dfdec362331ff0e27560524d7544b5b4de2a940c92b2da7383427d30d8e341b8752d165d6d236a8530b20b8865a981ab0f8c8b4ce804e3b469f35ee18c7acb7cddcad3f22f2c96cb7e3e8549db2fc12009f45f4c8f5a6509a34d23b7ce7a65388dfa1540ecab7630e2d42da3f4ea7b4064b5a2ef153e8e595f6e329701a2c00f6f32a8127c9e74370234f88482f94e08a5ac92abfe1efcbf06591fcf653232805aab1c3fdd26009721b415aee39bfb8880efda21162cd198c5b4652bff7c0992aa82b5fdef8e254ce33c59d51322fc8f73148db3e2bec7bbc84ed7b80796c6195d9c27f884419b4a95b13bd01567578802dc684624c635bd95d4a70a835948c8bdf39a15d448a181227c38b600bd102b00dfeed61f28d6100733cc2bda1982cb3eebc77377a529c5254d767805521ba862874ca23cc4edebd23d7d3862a3a3c0c36c104646abb1148f54df9405dd13fd56ba18cd5d3a43ffceae389a6a2eca751811811ac5c6332d370ae27ff0ece70eb2c1ac3a9061d0b32719d9983e557aaa866d5434d003722d881d2f8b63137ecf3472c91013d76191244c0c511c13247410414fbf7c28224e4079af5f94b8ecb4e5bfb3a360fb177662a336637eeaa2912b55974819f73c7b7ecb58269208488a135162f3623edbf16281a41bfd7dc0dd3d884e85afdc0d75c0f4bd765ab8e170fb43c845577dd78d6bd8372ff883c10f9cdeca74ebb10474aa93d0b491c6451204909760645c47d111585b02084d7862520e1c5b78e61d1368778edf62738921762e4b4f2ad4db8be68ef83ca6909b20aefdd7032bab6be81c09f733a7829af5c838bbcec3233ba9ddfb48f59bf2ca088223859849f4d0e01a74aa49c960acc51729cdfecc964ce983ae6669f61a2f9e5d698ac07ffffeb94f9d5a7fecae30fe06c84958c53a108833d499d0655421cc1fc2522f7aa745998d7a5b0e0c46003c3e5b2b3d0c3ea8f6fe921517179c0b2ec824e9e95d7d66761bdc78756a780c13a37228f35939cff21128f6a3ace8b3cca6a6d79741e2ed62c3d420fda44e59846846457c9e54146545e0420eca05652e15531de0fcbfbd7087aebf859dbf5960efea72e5bb62a4afc07cc6aa87f69bafe41a020982866e749fd593d5d98df897c7b931c5df66fc16da977f4996a0a569ab4653056ac5c007314231422726e0967c7650ace98b1f664cbb2b28b45badaa2a7b77255c25b83bcaf3a0fad35ffaa6bb2962e6c83591f992a46c760c5a23bfcd2ef92f86f691513fb231666fe1afb8cda952931d41273b9b12f79d0f47119507ac635391868654edd6f15985e92780e2e68983966d192d3122c118757c4fd67ac95769145fbf51cab9dd77dd17686361268c1b17443564e4d22f382958251d8c4a81f50a8aa7b78036a22a5f669a8659adebef6c5f1927c04b1acfc729a8bd1352dfecef8b0f3f2d5985eb5c0a9529b53612fbff9b15611b9c697f46f8b01860ebd8c0c9133e24835484a581e91bead10772e3c646cb41d21c98d2b40ac1d0a04c3d877c3943aaf1b92788b049cbd6c60719529571cd22e6fa73a82f3e5197b76a064c2a92626afe4dc1ef818c159481d440df61275784c1818ac2716f5ddc244ee8a149eaf506d2a2f609ab4bf5c757fd7b15d8dd4c78dd7190ea99afaa2fd03d881c26d563bdceafd18e42dcd8f6d72f48f71b0d5a31a803c5b196a5abc0b5b9841ad7ff8aa180b246dbb6ea8ff36a60de97952a8233e8606534e5a491c0e4ff2a279420224ed834481e9c932979ffe7ab039f1419f11bd344e18d2f3a541352044f02c2d8088270ca85ec11cd7d40353bcccc909b558388cd45d9bcc19e6e92c5c1bc644538fab7d069b81f8ef26b9cfb359037b3e6328a02193cd781ac0a34be482146abf9350b212be6c1ee58b390eb4d94e6d0c30cd9816991af56334e3c005b41c3ba6328bcc5c37c89c6fbdde24c8db10107c462bdc027eef30261444fdc515774b115f782d20ebe53677f177e1c69822a4de93651261565b1433318e5800bad80e0d7a562e299a86c1b5f7a4d856939d6c887ea15668744d7c1a2c7c7a4532b5cfb55290574eddfaaca3672d1890f9fffbe50c58deae91f0186e540ff7d9115517db1db748d5f2c2953f8381881a99a21e1be98f241b2ec627f7db7f2de8711c706025f0c614d15b4149934d490bf8387a7df76c71b25fe7cf8772bc5a28d71e63c75e6742b950579fb6087a26f7c087280f57bcf72df35eeb360b92b79d5abc75b6a16feb0197c5e491772c9a7781024e2bf7706a3992b3845222a6bada2ce90d2eda36e0d585e3b266a910e2f916cb3abaa59ad4ae45c22f24cdca6975dc01abcb9aa0af9ec35e8f09e2b002a1c591815a06caf8e68e6e4c851a82f0be4ec947477e44c3b439d6a6b218b81086eb68d6f0330790d83e388d170fbeeb78030a2695c57824e81e2a0f436062317c2f301de70b97b61a312f4e5955d70420b15404440c2096e0049e699cb222e061a5416af8944c88bde5442dc0044e6c61de02e8c73d17775847fe8404bc2f37f735d63456273e7915affc02762513b6942f51169f766f316d818760bda415ba2f403ef51f80f46eb98bcea5b212e9c3c7f48d76c9053538671932ee5a1947dc5637d96bdde250b79eb1761f5117143181fef099991e8e5f3552ea19759bc533a8cd6c4cf074121facf46eb1f17ea7844afef889ac5cba762d7c14fb4189c699723e419ea82094ffac114dceafa86511fc80f6a5d6a035a51d152879f0b3469d9b737111fe91f6fd84ba4b3c7d7ed6dcf17234c0f96337d7d5032b8495d2bf27674734ba3a93d41369eac71ad608ad82fb0cb897b69c4b2dd9c9435f91a1cc02c022620bf995462506409208cc111f8bf0cf520af345eeda1db5103de44bd5d1e50cccc5845525ae77ca20bcbf79eb8283bf6220f9b9e5f49bd7a6d40cee214487888934f77b0c2c24cd1f59c9c32c0188e6c300b49eaaf89a1aa5c8621b5defa1540631f7ded0e20d43e24d36f4d6af14dca2930ac2d4e66e1cf44c1f6e4b330d0a17ad6589f0ed0deb7edeec71e4f833fb3e57fd6f426ace06a530ee1119ddc5517189adfb746be8fac4f20aec5f263f946d323570354d7938ba5e6b1d72cd2bfd466e9ab87c29bc48f516b68b3a6ac19b50d727d6c1184c73a62e2343e4f8e097933d390929610106a402d270d1ff88a88fe3b4b898575de21d8ed570490caf9a7689f665a412268b821f622fd01a4e1568cf4947b58f79dcb815ddccec6eafecc7055a4f7a28aac73de4a0b620fd832304d9ce9e86f7ac060793997abc82384c48bbee3cbb2dfa27cfd902ade7a5fa26ff0f22fd8a43e613e08fcb5a31f096e601bc6028d29b981a43ef2580594b8709e95e481c78bcc2df9ac80cfce7d64d13df91ccc78f5863b78edb8dd0af6bd8ae0667eac549d8acfbc27d5b03fcd93f49d1c3d9e0e060a8f8994997d081b036163223c950d4f52a26c1422d0250a25458ec1848b3ec4c01822f09f44c07f86e2dc58c009eb5ad06198dca0046a484028daccdab570f08054e4c507b913b7215aa7e74b515857159718de11fbddb0f8357b261187bd3daa27f2d4e664a0ed2949f17588226f122a64dc8d40b77eccb78381994a0789263b54ffbc1f5154feeaf00931c75e6c3ea208416b0be6b5ab2ba4f4d14dabe43291d8ff91e3a8d67cdd9ac085ef40316b779c710fd5d9392d2b11ba19a274c9dada2c4b53c08b5841f3702c6f80ddba4f6f669cfb297466895835b24b894ca76eb688e418bd63c94972a4fe077d5c3d195cd8488d3b9dbb160d19c4e880e65dc697a93eb7c61ce18c2ac881162d2de44156ab0f28b4549fbc7c456fb8ef0cf306e9dae46fa123f7feb2655ef47678c16217633552ffbb793f2815d64c66d04ed9bb4a2c7bf39940fc61aac116b4893e45d7d2b920cd36e3c1666224efb59974d9667b066d81f85ee8ea4c9015602fef53d5ff18c218e041600925b00ade20cf6b3e65220efae25b167d1212cf8bebf48a635d99d2850123980d6efc0ec19dce9ea7e1bec871a22db9cf312369e0ae7f25e02ad1b595a496a39cb4235871e6364bf621be17118432e28a1df401f9707d0cb06997dbcb2423af351e2412f793084a5f9964d867b8d8db7b85146d64d7c56a8cdbbce31594338f01e10702e6b1dd8ab8036124785a102139958d97ac4a07545b84ff0a264a85e5a6039d29ae24b9529e119e1408be059e041d49a2a0a5bb1c39bbd0aaff35ac10159f53c4c0d0a773b8741be9c90875d608f76e91b6005f8120afba88b22161140b67cf0b5bf713e628ad26d4ce7bbd78aff080847da2ad69a5c83d05ae233453b854eecd0a34b11611c45ac2f2cc73a713226bc7d416d574f64501d797fe777e558a3c1e4c0898a40a7d79e97edc7e041f3f16638d72ff9aaa37caa035ccbd4ff7b914d94526165f3620e46bc2bba8ce90b9cd32e8e950912fe519dd51ba88e505a45770d273594bae5ddcca6fc8f8a9973b09644454a578f08ea4ca7d34d1be81a518c56d82784f570720b90d647b0c211418a09a8de5aa0e0b45cd695c0e055b8acf396c943e4ff264a4fdbe1ee6eabfd9e7d05a9fc6c26c875897331ee7710bfa81c1e7297a126c6ab1da829e067c624f048e0d9857344ebb15a6c70d32a4201bd16858d9d121c90053761921f34c3556c0af16def823fb64b585598100075d20a0ace0ac2ccccbf9043dbc6b4a1c4ed9747dc6ef00d2d18f9d3f8f36a136be2b3891b96d4f058823b8809ab71fafeb0131b437f34235ad72d400ae0256c777b1e1e80e0d78ff2b2c4fea168f99caa9ca8cb5c439ccea0350df044cac987f52ba54a15d8b7c2de0f3cac601e6f9dfa9e89e7324b0355dc5df4a558220ad13b120cdd6987c677b82a1b3b65624a229b4e1a68902daee5ac5dc7c7df8ecc90766593a356dee3685c9175f183a6ae9beb09faa35edf98ada803d6fba3a02c5111060ee6e8d2d1c36951cb84aafed1900db4f3bfa6cd10d0b9d66596b11f149c1071f03ba30f1cdc130a87d22f2da457cc67a8b089f2daca1ec6fa64cd121bb8535eb4417a7583af943ad473315a433e14ddb7c4390708c0e1069a6fbd403eea24ceebde3830e9136454a7180e836cff0ffd7d34af318ac120e3d9fa463dfd3e61f92416765ca15cf83d74a46e285bd62cd146d1272cb508a576ff6f94a0c31d0e5b545dfa89a0c01de8494dc207324f966bdaf76a7cca7623423c330918ff178e38cd9ac8670cb60c75cd72bdfc59d24eb9e0246f3ba9ec06876fe798512d08f936b978e38699c76aab03399e4351632fb6070b955e8b0ece16c08d177540261cbd3baa3dc6d79215a878b1a922ab13dbcf7478a91245d72abd301edd28126890d7e5a85df6b58d1d3962742b7146422963e9422df299c57a79a4d36dd487a44f3b80681f7de40db5af85a723ebbf4c71264092ad8a582646535c3472ebe03e45bc7395c8bd6ad9785ed8295356a56b07240a9ecb02d1936704214a177719c5a9ab47bc866731056a63499186cef1289d3837668a2a6123580fccf905bc9866baf4204ce2a8efc3e8cc8806af650a749379f7d7e15b2010e724c6db3cf93101b5ad9a030532cd492c6663d54fe11b61c327241ab715a517d1ffcce79c70e523e5d971eb49fb2af0a974fad686f05a5183c52a3c84373a0f726922b75c816a1bc220e4e6ccf43610fea26b489098bf592381b976ff2d47323e5e45e58ea5b1d0ef308b51768e18e1f730207036284501e1c9c88cefa38c11da72186cf7f8079e9bbeae597b94ef07a0ab9a6c5427ab3a276723a1e92710c88d40da0c7f3a2c32f62c78580ee7a80b3b9c10470528a630b631d92655fabed365506ff3b07f41b16d37f4bd7630dd99b177b452b60143d18a59c4f5ec8deacd341d8b60b22f02a768dfc70dc11436d849617f9770cf2b8be98d385f5bbf759d28ac6409252010c712719c7f84a10fa815f6cf053a8e7689962ee9e14dcc4098082aa4b5a28642cc213beb42a0c512f215989f802ee52fdc73d01c9afaa89f54ea76566f7483f4a26f790e55af63474a90cd7342ff561ed16e80083b7ffb4495bd116ca0c830ac630ee299127ed788e9c19be87b59ed5822906fef4a0090e28e012ad52881d0f08432a4153ecd1092c82901dde51860aec6aa03700ca4c18c748983a1897b25f67dc546529a46306a62fc90dcbbd6fe64c88ba3d417102b4d029057a963acf14307d8999046ddb2e2e95cf2f970dbd3b261317c26aaa9dee045b27ea034f5e4451f58251cc0b647bcb84a6c6c86336ec19323afa4c4939b1a1eeba6210edc683c70474097334facd5d5b9dba185baacb1ac24ba4e4010a73d2035b7f0981dab672e0de472bffd4fbcbca16f608ddb11e5f48937eff590d932d7fb9269c1d74aa116aff5e5eb1b4467f7a201d7f4f8a306f220f09b715c4c2411d364f663e1bdfc891cbb450f072bcd0fa898d1c5e11c5a8318bb51c6ef5c0b4a8e12327a9099b25108c984e5b09abbe0cd40c7abf637976d03222b1d3d9de1ab956a745575c90c026ac693c9a153918ea31cbfe79967d7bab38f1adfc0470de14b4661a41c8ebd9fd8945089a2548f29b00cecc3a476c78a8ba9bda09a4798a06c716aba8b788e3f83dc2345c7c2c545db9a999b682fa3f6efe9ddad6cc10be38d9174c19428d0c82a8fd56c4542b14fc22b57867ff2017bc2395087c0cc96df72619d04a2282c6832dd42f190439f8d49447962c5086a4dd3d9eacf92bdac30ea4c84f2ef261330e0385ab0adc5c49b7198eb2111317ac98d17fadfb6bfedff932c89dfeec204ee97c6992c092660c5d6db147eaea46692121411c6b61b9ab90d5d2de9f09a718c8ebe9c3ecfe869241fb151846afb66abe7ad00c47e39cc6ca11fcc69cf48cacd46342e92662125c11c211e8f846b52ce3a47084c239072164bea4f7ccf72085b013335e6622a129b327c748953078b7ac89b5f124608277ff636e15a6eb0cddaa426fa5f2611786368bca9eb4633b97dbe7919963111cd799008275e8e2071bd97807f02c90213355c7da8cc022af8d01f2d211b8279ca757905c0de7fabf3fd563c9e613f7bb437b6e696059baf026b7c7fe0ca2acaa3806b0fe44bb47f3f833e79e8dbc24ecbffd0ed1d74d1cf7a524deef821a04a7ece22b9264bee0fd491b719c07d4f2ba032904008c8de329739e5d9b13643a1ec6cbd801f8c59013dabfea805942c1aba2101f315c9f162606199adf2763021f428214ef626ce9041e3abb997fb9f0d5fa988db6a64b03533561b5ff6c57f2d347c6548cba645eee57106d5e709a941572866dbeac0c03fa902a03efd4b7fc70b2367d6fe060250ac204931ac7bad42c2a9138d31e9f340be1d2b3f485af4494d4eea701453927404ebdfda7969c923e8c764390cea4e91d907ac04d05ccaac9ce871565971679698db61edf40c402cf7d1acced60e33f2c232cd407ee49883c24dee3b0246531eb51fa9bbaef54c89ff5a558b6c91d9e5ff39f8b0a7e5960d4b22f169b99d04dcd597b2785367965d5e2660bcd9cef55bf42c017f27efa569cf921aedc5bc5562ba7be19cad4ef07a2af2263eb367ae99959939735be189d39f0607a802759bee7a85dd7610c9a8acc012db106a1633e91c3c9ad9325fe631333fcdfcaf5485830abc00b2386997c00abb1e534d518965bd3d999db939f36332574c6c85a374277477a7ab6387968d427ccab5107c665fe64fcc5c9d388a9ae040fa7b3041398280a04c3892fda40c32bf9e49764fb4305c64ebc02184bdac5efd60e7ead1021538edabca3c32a4c96ec816c9c1246bdb20a71a52ac7f02e3aaef1455315a658dd422d5e8458f8b05ff9bb414fe6846bb104ab1fa09c43772c44b472a7903249b42623c24ceb313f76224bbc6675caf8d7cfe1524e9005925ceab1395f26dcbdaf9066ea2f6d4782346a28db5a29cbb3504fedc3baab1a15f0a930d6ad4817354f956fa900a45b43051d192cf81258b45819a4c496cd8d14fcbb721195881bb1ce51c1a1a603151fde6183ebcf48a01c69ceaf9a1c805777c0e8bc6f56d92d50cf2c501589962882f0baab0c353237d0bc1f2b035503db6348632ea7ac0ed374e2ccba9a09463ca054af99242e6204e2c3b68be7861b05af1b21ed38cb43ede50b310243757bc9aebda22b1f5e21ccca2ee233cc5258a34a800d120312f895b88bbf51b56a774cd1283c3a3f587c88bb6fcada2e3b7543a72d19cfa164d9118eac83b85baa821d986298aba359e0a425c91d589ffe25b86f00d38cc7a95d8b29ab8c39f9bb0faaf2c485ef5be4f6a99c7769b23dd2a38c0c17e76caf453bd927189ea2375926d5f4ee462f4a8bf5287764344e12d4c4ebc705107acb044cdff72127f3667aca5a67e9570f94e1788a844f00e978c73ad074bc3f661a5ace51242a31bb8db5f11791b4c8ad0ae59152b33822eacb4bb2eddc7648aa661122b69f106fd7e5ce6b1810c1f969cf07d40df3cd6cd55c55ae8802e90030954a27db3ee8f9ff91f9d78eecde0fab6c4ffce60d9c02ef49801171c33b5c458226c68d1a1294078199705b9f69910109438472332ddb6cf94d9e4400c9c192999126851998855d27096e94490127aa7f2975c04a4d7401fda031b58e0c967c4ce249833260591e4f1395e2d5ffb4c95a43e74403ff0c072e085b8efa3d7baf16da8ea902101e54bedbbdd76c1bba09407f8af26f718fa60647f690e4be1b16ac1e2118ea2e74dde52e21f1ba454aba01d0dd2c3d325f630d4dcb08417ba576474422093919bcdc49bf5f1d76c7e1bedf4d9326bd93027c18b0e51ea9e587198bf1cfaa01fc84025b03e023b34e6b1b9a932acd2ffee875a938d6f79dffb743e37ef7b8d7d24bf36b767329c3e891a6884e04de1a0117a2318d40cd21e1816c25356e8a68861e3570af7d5427669e92e2d41e1ec8ddae42c97ef726d2968f1dae3e78c674d63be2f7741a043335d7e516d6e30d68c843b62d324907ef8c5aff9f0da0035f4402fd0812e9039ee57e2135df5f3b7391f891d78f84d22c912b19219da8c1a02c03f404c8e3f17b07e3b55cc1f4298c7fb12b784d7823e5611c64dfaccc2cc19081eea011e290313825e6337d89287e80826f089ca028cc08415d9525448fc95636d1c062452b2e14819565570863074108c2c14145874ec31954b86286ed154533f1ca77fae93a7e528355b0ca01beec80ab2838c1949acec0456b156e1ea1d9c436423f1e0a0d8d829a435f505dffba74b3540e8ed908a3aad97f7caed14c99d352e5f3134c794494c935488a3852179d71e868191f35aff5d24f2b5b7b6a9ea9214cf544e6cd81b8433b5fa9565b20bb75f7dc3aaf7578aa2195eec22a94c589fd55526edc271070cb89e06b9a26250e872940d110ade8c1123658a422e14716edf6874e1b97a71f60486cdebd727af7fc865124d93c9da58407128a8c2199f614a09b1491daed76182e3e42cb299345b0540a7531df83ec8b35281b207adcd161b72504b56f2facc3aa5ccda024b29aa4745edb71559221e266e848de792fe9c69bf0abc364d0e6d3bc18e3c1ed228f121ae9527d1675224b136586bc3e047d9e8dc430c230c3aa507ab8377890bc8a5b3fe32b9a317269a5f5b96b601c4c8c688e3718ca71f6851e80097995832a7a47cc0c7825214e34a36df596de4b1876d150f6430560d9e7b327480c2deb2949e3b87e904e17adfba30f8ad3b47956ac6d246a8365748395c25d69a9793b416167abc152e9d7ff81ccc807bc4e79a6e361de4d47297d0634b50e90d2eaa12f800a33e172ae20774fda2bfa353a0d472f8a7304b3327afee79dc9ff8e333abf018b89454b5e0e0fd5dc94ece803bf3d61fa00d56e8c0e5a8b1d673166438403042d20cb80532694efbdb225cf524ceb1806506e04ee25d6c647724e5790a930165de090ca8f21b545bf9a2639704d7684f1042026c3492528af2834c2c2d389d667607f6572d57ead66f4b0f91823f61999b1570a55a5edc2cdf1c71a79e769ba3c6681ea99fa677150e04fd00cd4f0b148ab6931793becda24917be0d08ad945048bd2972f7179a49f9f3299110d87154d08b77d15020ce714910dd89ce42dbfc7f98e99c1ad3ec25888f47eaa14b8392e3b6f4aa4d4c08bbbe93bf275c9a55d200563308017ae3bbc427c12f62b1746f259140a1f2d7743a219ddec371c08e7c6e94b834fe650a04595b1f3803ab5f0529ed62b03b3f6b91572ce0ba69b18dcade0f7eb53ddb88ef78da2952eb25d3f2624a4e498db7ca0ec0a03b366cb55077d2e587d9ae5f93aac72814b63c0f1ffeec5688763f295390fc3f63f7c46f0610105c45cf401a72f6a351c41b7097c5f3dd561af61d21e427ee430b77844e6f9b51ec57d90176c2ba2c2c059078b6789aa2e9d6406a8ffc9e2b14a7b0b3778f73d5b68da0051cd2f02399538a6bdb7e763122658b6d9f98706a48402682016b722379fd6ce449c54018da5b80fab8b66cec2e79928e5fe88034ebcba24d077cd536f0843fcc3dd899e2a2f5007e879a30bf2a33a1e133c6f6f3bc9521ed36c5a49e6ee64134fea61149524622aadb2ca11c6c2435b2d7f208bf4020c0513a15d11e78d99d098edd522ebe34e08ddc9632afbfa570b2e1b36b8aeebc1a8f0f0be972c3e684c43d7689ad42e35160f2f4ec90f3653c80bdcba0d25c8e676b8fc0960da4757af8364405cb667fb7e50bffce233ec2ce42b313c288cd2cc29ed1a76bd3935fc782048e6e0bb427c54e3e75e63c305422f47c1ca61d5f01c85802e623257cae11d80268ea7b1c138478e58024e0533ae818e4da4709033f56cd1b33c5b1e28ef6cfca250ad25d26953dff80138e658495f414cc32d804ed365ae5926246f24572f508da25ef8eb77de5c3637e6f13fae9715fefe437b66c710b66c7e6d6e3406f2ce23e80b7806abe0ef87b85813774f41aa2761617590ecbfde8c7eaec1c2e197507e60fc966d2181d4035f6528aeb9699309761409cdc7d390d73a5c60170cabf5e7f010af78e3d7976dedac5c49e6c47e41aee78e6aa719285970e31c720f161cc2478ed2c47f51aef4cfda5aaea04110595406733e89701a69d9e8ed66d439a6428541c15746ef7e261c1e1c9b3ea55480541e52a402660eeff8341802bef6c3db37c7a8743050f819367b7809784b83df4c83c93c6c9822708f74144b2c71433a728f97a20c270c1e2e200e0934a6e9c67f3df11328178c64ef790fd06865e6df7ceb3581a15af6e31fb51292db34d68b28d71ef404b9f82a169b1c2de60be9f3e2631563b99316ec369da60c1ffa54e1912ac937f8064489979276a42b233c52235d0fe9b3b11f6bcb171d5118874d19f7728bbf877566d83a65a4112e288fe3a33595546df9fa5c60a049208a0a878ae39a1f1317e438d23b1e81f0e0de25078fcbb63c1ec38f14118091098c910d3f03111e28f108aa3f865141a7f6432dc784c7ec4b316f31ee239d438414ba2e088f1a3ebbef41f2ed84383eb30a083088ae5e93fa69e3fa14b22f9a4748fb74c55713e508593f8ee2d45f83d22166bc2ac16ecdac19cf959885809d65853b946b6d66175add272f9e886d674654eb0a26e628dd3f73712f77a8e456320a8b0e5b3691629e50b5c0fb21c40760c717b4297bb512fe6270a0da9ca0c0000d2191d8373a8fa9cbd7c356fb9aadce25caf47d02d106dbe08663a01431cd92586188f058cccc2f2ae151d2887695030fc576ad9fb0a382102657a9f470a171977c5f0e8565508ce5bbbee76d56303c8d8e02e763593d96ca872566455cad7b07f9d2106e2e498d1d2dfe449de36497c68d9b76a4bf4d167e6594d4f52173e4ff621ce686d12e2aaa51e6329c8b2ab76e066e63a1e51e1152c5d915008f0c6d1c0d10df077b809dc25fc0c13b5de70b15f5e8ac25f24e2c83831c48304414ea11748a817469c5465bbad8fc5b3ebe3f10fdb8124be4db7fa30d63b6efa4636e366fdc3a0e36d7f6c9cf63d115dade23f560d466ac06bc5b13da79c27ca76e61f9931bcc3b10e28b98e4d12cd4c646441bee0ddc54aad6c9f644af9ac4fe22c2c007aadd8b86649bb1c6bc90d0b3738bd8b7a9b4db9522e1669110b7728bd417ab7e689aa1c8b9f370b3738fdb57a95c9745353361ae35fb19b06de128361f81e593afdf0a8ef218ded9af46ef5e78e79e4c76b952a6d6a3b09c56033aa5acefbedf05272260f4ef6b62024d5c6c32a3658f12bb4eede631a1cccf1614c678f36aacfc7f5fdfe70dbe26a73d0cb02c4918aa710e4b650baf9d542f47ca0ef423282d30a29332f367be9454fb2daf88f97d1873f1245064b4878dc2b6c3cf40a98f63eb3c29bad508786137572e004c4e2d899a45a1e6df1e2a9dce031fcab192bbc91ac17a1a303f638a5700ccfe4072bbb2ed452b2eea7e925e0f12092abae8a0a6f082bb964062eb681c107cc0efc46d3b04081e83c97b0d7da16546c231a1f56c7fc1145c4c8a65c8d37f83163054119eae46eae129809a1ee3484ae8718ed684494427c4a272ce95c1b454e2b020a2c311e0df45c4e4d6b14f2271e479aa47e8494573f492ec289b628f57020ca346139a25bf3f80b9081e1d950d010b06e8702a0fd340e4c4f005df03e3a502748f16962ecddf825f0e845fe1e005b70aa2627228461f453afeb32a4473fa13df95ce709d6ea92f6f61f9805d28332c604d8efeb8dc00ce5b6ba2039a104cb76dc5c68ac4b8f34e686b675c668670cb4c0e37682f7799838535857d60e3cadeb445f0e65cf9d7498b6d9f63c6231b5bdf19c016cd162df749dc221f30b614625d855e8bb134469802be69fd055e8b31e291e3e20165abdc971490bf254de3f0e253d6f998d2e93e75262464931ae3f06bd57aa951cf718edddcbb6ee4c0298bd870472bea7c01117faccb6778e04f01c5ecef6b6ce0b83f520143ff95e64ddea55d8db4abd4d00df7b6fa59a0b1cf43ad5b9412da379d1cd7726645737fb076cfe21204f0731b8a19bf10bf9fd7cd90be609c1592cbc40b047bd0e341d6e12e4a6688fd7ee71357dd4746c797c34f2fc11b0286eadfcb7ce0b0a4d8e8b3e8a0b96b1cbb5a024313b498063341366066475a0a5545a1261fb9d766d037e802b0e371415c7e620e056ccd18d64e6842232051a65c4eb3d0b689daae7529a16c127d1608388a6f8d372f876b241389ff0d1adc0e2d779d7e17cda6a26a29429fb7e70c051b15efb14300d0ad1da58cdbd4b117d89848f8fc92d1da3c1cc16ee5bf0d5c12b6e7c1e373c7af4dab740ea259e17e144e4bc0e636e9fa7971e9ef9bd40a67e153c84d04e22a52cd596519fe2efbb8e46bfcc90aa67f66c8283989066b15fc4d494e16d64c525fc2247044c6d5e315e8867ab44388910fd4b7c6c38a4cb20480d80752932a30ba252260918131a352ca15c873a80b887285e95fd0f3ad73c0e35a4b2f8658564cc68366b5e70e744fca5c8eb32b8033737e708538ce8628d459faa307bf09dde924382c5618004dee9f5059d56decec096fed1691f43f19d943a24d052a1b17a7a816b5f403e6a353a71195662baf5bf120ce0bee11708c8e89e54cf86846c26e7a6d8c9489c48602bbb5d1614326a2664bd1eb57710ada4846b6828d7df5ba70259fe014501683da152c0bc6ddbf1854a7d402e81ee33d9ff4aec76c492b5953e7b7926ab2347c7479582095ddd7d4624facb0ec832628a283b42c3fd43c9e157a56b01812c81a1d140f0c5b4be99380e09d25143c768ab8a799fe89a83f0c7a1dcf8c8bf9e3fd4f5abfab5fe391f29d9eab87db767ba081095698bea40862760585da1eef0381c2dd0944fc0a5f6a0304e231aaf40e63f39de096eb56bf4d3a7b4344018d48f652c49c545f7069d7b7a263e6e89a9bcaa65950e78d51437c99431ab88fb7fc2ef501b86cdb575a9a900e868cfe7077404a1bca52a83717db642d64d83e497f57450fa170834429d86f5e39e594535c87270529d892d7ac99eb2e7809f005a2f85553345ca38d48dbf5c0412d844dfd3a922309e563227dcfdc4439177f1df6ceee4d635ab6c51245ee090f72aaca4960c222d409648a1e6c47e93e6d6b723325dd9b9f7e8178bf593f99e3b667ee471776d2555e343677883556a6652b5677e266b04e19912e0db6bebb400a985be8e5ad1fdc7ec7af7775c9370d0fcc8bc87766e1dc16b42e9d65427f32f8c329534e5f81c0d294f9b83fd68b23d7f024e445bdcb28bd676fbc4e77b98e20888fe43f2c4aa8d708fd49307c5df39d716b4c68c062d37232eb73c14743c2cae17062ba2b62c3b264469affba77e434c06b8c8ac11aed0a4f5ae2a1fb49a0ce3230ebcf73c3154e08af67d72caf2a5af1f1df4a0b9a074053d764f6c4f2f218a95711a7bb9776f3a0d516a5eb0f9d5348188d685f6fc790592c1875e20a620d387b898b57c8294a8caeca2eee7b5960c58211465165a80ee919cbc4eae91184e1b09635459a7da751853652de541656b454bc2ccc6f75b73ac954151427894f38aebbe349ae780011e371c5231eef09ff3c18fc21617e861c47acc1eaa6b5a214ed5f0a601b04d4404ac78ed2cfdfd3d16e52c2b79b50df102580b62431855ee48d40f4b5c8dca3b820d30afd2e4aa2c83a2f149cb27ab26742c2be5900a0d8921aad001ab13744c1406f4559cac983393932c1f41667baeb5b4501f81e1b674d942709c41e7b4c267d32ceb966aaa560d6d4a297810304f536f6878e730d7c4938d2c1a9d5d739bbec4e813bc1f9579e19d9e73142186ceec428c817a9c61ea7d3db23c80273ad710199f279895f68aff9852a63d2362bb99a099874d5f960e25f805e832e0c294a94d35ed9a630fb237b9a79dda7dee217555739008e8c95cc57484782ffb4e8d37e5bd3c8b6a94fbd8ab81b457f8fa030aed3ec5cb6ec48cfd5a36b247244a2cb9e01ba603842e7362db85143e12ea8793e6591b9f620f4af2da331c44f09b67c917b84f53161d98f2ded2d545a61af8f0fab063b795b2f69ac4dc410c6ce98d46f68deec78f110549114c3e500beb4d94e82644f3656abf0a2d9ca2b2085c672d88dd2b21f9ec0d13e5fd04f16d661d11d0695f34aee02b8fbea8faa17f1709485daa8aec21c2035dcbd21740c2b02986df9f50d65548e7787e0affc3552e86e58004aee3e6bb56632e95cf632bf43d45c03d17266aff3c2805057a5b56d812aa2d8edacc10c88224a743a662045ff2d22da3ab854b89baa16fb8d7dad5b12fd7e920f93fa5719c64fd526a9f07de22b37da19208499d03f24be0469ca1a604c457f8913fb5ffda6d69a5af63055d2eb1b9b5663c4ea5293e4bb5e5aac891364f2494bbcb92a8e92eebf8a431ec43a708af41170e769e0bedd0aa4fb03b78b36104093c9e181e6c5807ebc4bfca4a82c0e50f5bce503fd0e024bcc31c98c9f761c121f7838a7675116105771f7b0d0d27ec10d935f4cf92b4e5077fb8164bc226f3292328883f16b1e1b3f1ab67458a514403f7cc80e16ca2efa89b7876dc1accdf289c85d2ecd239bad146f51aaf5ebcf743d40c5fa71b689e0d42c6c84e796307003b3c37b514f65d7808b010f760b7344ec85e2042d4d8e3461943fe4ebe17aed76691f454e58cd0f97e29ea39233031f406348a81825d82c5c7dd8b2063bd2a92116c672f965dd5b5ac6e756c8fed44f812ca076c8b6afb1823ef11824261c4c3d2e5cd01a8df9d3fd8d2a6116c1e3eda6fd794dddfceb751171c01d1cb25e8a94f046a3445a56f84c33a997ced7fe775bf529b8a42ae5d28d07be9f8a89799dffef7ea25432bf70560fb424c7104a924824fe30f064a746978d590e1d7ce37eac938e8582920cb390e0629fe83fc6d85d07ffff4993eac04611dd9d3bf9d6fd593e137fb0ed63e08b5bea80d447df9ee37eab1a286a241801790c83a128780e2083cc06133dc89a71c454ab98882e2bdaed295f8c3f276ca778efd1711f6b5ffbd75b2f9e13ed218aedc864b7d997dedfb1e75e677078592b4ff48e9d9cdda7e37ca1dd0dcfea300a48ddcb6dfc2007ec9a5a91bcae495909ff94fa18a08b385853de2d83ecc96c4cca7209ad81f84811160e0021aa61c0794e8b8100196788d85a51e9e4aae680eab54a935ee9e79be027e4a5053df26d897f024b02fc82a2f34e257567c5d4381e9d4766bd381ae903a3f83c4cb29e59ef76bc9e4b3f7df1d2922b45b0c5ba6e7542bdc6cad58be4665f67a41df21bd39999e3af4ee9f4521b95d469d25e28bc8563b25640fffa49088ea5c6477beb34e367ffb6c76bf22f4d996622b8f1b837c49102d31fec4244f6d37a97f4b47ee77c0f5b86c344b31e6d324b0366bcb7f331e1ab9e57d3c04a0016480d40414230dca9285c22fa58152d45194f80e056e5f65e6133ba49fe29ba0ba4058b267b34d837b75d5418b1153956afc6b24bcb4a10b599dade39df2c1804c41cfa2c40038eac9d46fb777acdb44b6229f526bba1ba955027ad989dc76eb9df31bc4419dec7d77bdf3babc0dd12bcff1f98fc4b4ac00241fe62befe75998318488cab44ca2cc11f2518f5c1462002833e16aa0a9e8d3cad92d261bc1930c4101b29dd7bd25124000c73f30eb011fb10aba6b7481cf34390f00aeb8a930c6286d68aff60843f9f0851e0168f0f4f1fe3a7a1bb53bb0a359889a05ce2fcdc2ba651959dbd32c50f5950bb6bf4506f97a5ded5f036698e883e0cb37b81c198bbc46a24a6e195073c3a8d678900f8d36809c0a5d1fd9f531260142f56af29312c1c50841361d168c81e83d86cce47c6d09964016349135247e077041e59a6ecabbd25373de2ca8c8800d4ddd24d1a8b0d2d676a3dc4a8ccffba3048ce45d49832039e0916d87b807b98ad454ff0aa49d203165c59bd9847d10df13d1e8dd207ae3ab9a0800cf90cf8b19f56898df68a88017780ce8e82febf0c5b5a1577388304bc48c58eb0ba2367cbc138b426e385da4fa799321eaad9dee396c28f644ce1206299be8a7a7eb617b37f3f2b7e0a4120e289a66327391c8d5915d7011d080b56b08d0bc888ada5ec39afd3964e2f995bfe9760ae5305cdae50fa5a8d704cbfda8e3433e5d50ecfb8876255ecf0089033508c491cc09e6038111dbdd01e2589253f548c599faac1f51832c7f3b31525fec94b842ea180feca4960d0eac3ddde561e0be1f409139f88a20235810ccac10739c23b5217ba9425e6da6c383fc1e820ef18189c890d92a715a6307c5cdf1d4829d15854194e099bf6ea52ad4158d05341b04ed7040418cb1234d83cc3ceac91cef19af04eece9a79e9b5472633d1e0ff101a09c8dead26588c9c7e31eb8223b8c0a4a04fec1cb1b9dbcc8aaa4cbcd14f9c1cf828270372358fce1b414a4d0dccd6281bdd6e0cf1ea7e50805c087df1d810322de64d09d898ad98764da855c57e67f2f2dbb7862cb2167721a7b2f07870ea3b2f1c7bbce839f02cb419e8b7d23e39a5e753d66608a8537ed79c13bd39465b977a911fd447deb9f44a932a42c5d4c16c941779c980575165400354c457017f6f744d071a08691bdb1b502ea307131da0b4544b800b8e7bc711586443eda568d6b7e7304a1ddb853acfc410cc2237b0beb9540b29b03cdf5751d090ed6494af0724845322ce433301c3340155aa6b271372197b3198a48772f8e3af3463354e5b80c022b707f645c55239bef6fec617bc868b1a1a865a54b09c60fe64a729db49b55722b136fefc99b1f1e7e0a29b958d667a3d6f0b8b62ba890a13cad9c956393948ad255273eb9125784196de2ca742541c3fbd24c7a25024653ac2a788bd388e76b2ef5e7307f47895a5563fa87fb4f4d781ac6a696c1386d941573efea58e3dbfc6885df7261fe7a7c8f29aff918b3b003179ff303cc6d608f0ab8680ae6ef02bf55787f1a20b0420404edee2e593522dbfffb9db07d7fc061d00e1d6111c4b99ef995c0fee4472ba540ea199ee95f5b1be98e018357d0fd29130182ac206b6f4f1da02fdabe35c25c66237a606ca1db5744d42152c4a4ebacce115179c613efe8bcdc8471458747b4823943f8ca42dae1373718ea28c72e85411336b002f736446f31469b46151901d7dfb42736e0dff95cda8df40f3e653483651e3843030c645f25cd176f43af1c02161549e001b2f69ff39fa19e131b98ff7c710213aa663a14b9cd67e0636d63865466cbeb2f95adbaad84d5f2f83767e3e82213a60a521b46ad8165b785d924f79fd3999a2e4db3c5df6eec801832da539c8a22b0637a7edc5934b383a2e5a261c3f94be75214a5d4bdad5029d7bdd456f433cdae642516a99dcb82057685adc9c1f88080b37181b0d40b7cd80c452b5d1e9f93aef36c7eac5006cbccd7945667b103d2f087c0a051c0784a7324988c870444ddef5d3342eb195048d15c232a0d0a9e6f2379168755384ecffee75da45b11b30c3630a766a20c415b188c196ac8594cd75e5fb99f1e4e11a480398691cae19dcbc604d1254c0d0baea3c8c7bef3b81006560be3724729ca176cb551b0d31a7f692b3fc9306741d93d92b31fe1861f08cdd0e99ff75f26db5d4d6c86bba0f235089229e82c4c456421be6d3b2684037f6aa7cb8e625810a4e9de10aeb1a6868915316d1d599bd41aeda49dbedb1683a758cfb170ea3f86b9ef3abcd270c0611431d5af1be70fc77047e9668412b00cb0bb6a963920867cfffb2776d09585dcc0a61084dd20e101573eb3fd239646a93a75f4527f8ec2e8a4834624f82e56322f097c28c66e8ef801abaee254f3759f53a042cd4a9e9a9a4d4c4cde0fc47b3f505b4074b5ac7858b668d8d2f6e6de6a3a7de9e89d6dd30231fb5e5eba9d1c2e161d607f79f3e9942f22e9a9efa3db0b0a3cbe3f4f1943eea06f54ee246b84fd1b2487805ca4831e6e1182588769e6621fb4ecddd0a1e02284c1fe7c87318834f0f0be4f758680d085e44dc9235368b78633975a37e6b0c0584e4a7964880adc8bbe246b7d40fe903367c1f498a805ec3e4758d24759e0788533da737ffe8363299febb8da822029db96dddb16015e53a35bd3fcdf4c4307df01b14734906a5e1a9c825eba54f05cf1905c159010b9bf2d8c1096bf955912177f8e09d5c0be66415b952528c0831ad49c3d37f4eef215932b6b613211effd16b85b02890a1c3c551f884e457dbf0a85263b7a57da28aea883d9fdb675e1e3db97637cdeab319fe7791ca0db461af4602e190cf9add78b09a26ed9319866f60ce4a857f29478dbf7b6d88f0809f38441a85cdc7eda2c338b9b1d7c77a957677aa8ad54bdb48ddb83a245af3372fb48a05060c92a97c96a7ba3164963a06b9141314621c9c384323b6fe7321a53556373bd26d49469926d0508c6004b5d636c689514bd6a3096d354f3be22201ec0c2e0834e684bebc9624333fc81d61e1eaf3bf48958e264d085d4cf334b0f94723123f230ab62ddefeb44fb668ab10c0ad3b2ae4ddae71449d3ecc8f279569280f7f0466fac6c5751375bc17c45bbba2fccec161d8b98416547c366f4fda8173bc80ddc5ccc397572d25bd3652db00a21a069b3ef8ece5eea65c19ff64dcc061fdeb11708bfae981ef6befe08f206d114aca1eddff8d173ab7d999bd6e021f15d3daaed0a1633cfc0f9249350f8b13b0e2f18efe298bf32465d11b0c3a1c735896a45a384f7247e696d058cb35b5109559edf02c430ef03352564b38a57cb692a87a79517bc1bfb2779557360daa882026576d47f10cd382d5742133ae6b430f23a234a6eb1715b8cf1da9c142194af78ed8f8c67f8c2288c08dbce4d4968595fe8d6ae930d0dbe1a0fe6afd7df96b288e7ba30a5af0cf41bd7182a6ffd7de5db78567bb22131ff7972771e5ee099372403be81488534f4446859abc9bbb1439eda10f8b7a203f2b7e4e8e395884e2546165844075d72e7c5a7a5688b5a2331ec5fdd77d944eae5fb8931e6215337d13cd31e43500c72695be8b4f3e5b851051865630a6694648ec43be9338e926ead9f7077d53b793c2a93300a58aac1ae05daf8e1ef83f0babec99bfad0887e6368cc34d53b3da9020ed8c69d1cfc757704a44091c55d7863be472ee3e4d9c9d513444201d1a47fbd56d5d6d1a2def9569ac5a99c6bf34aa280ac4883a10112e61c70263339020ac548093c6324de4888bce150117d1a64aa79999b704be2c14c37eefd8a0e870f4f29f6930179047b0abe9b1d1024a21c4e5339176b45ceeede4a02043cc62595cc2100be091129c0b40239c7d72c2aceda711ff9c9e170bf8e5e7418080aba229cc1acc591b3b60e04c31023022eaf9a1f2bfcad52aa1603e3ac5a2c98bd16758d423c44180aa1fe1dbaa008776744696db375ed15bb9a9e148098f907a80aba0d003ae277ff8369c44ccc2886461d639b1f07550e54c1885e38a008d5c1cf9e69ee050cc2e477406468ed5bd57d4f1db3b7388f798eed388156e4454d1404a1cb29e3aac69c8b553fa4bd79e9e92ae95e4011b0a7280ba6bb1fa96f559700fe9bfe82282a8385d5438f4e138c0801a22c08f990f66925e17c1209dc1800369b82b68d18e75cc990c494cc5ede2ef6ba0af2b0aa5c76bd7ad5c117f60bbe452ec32e7a0bf200b47000021019442ab2f0133411b30f45ec1cf8930158f6d753d0b76f87ed16d4ecbaf625d503fcd545d07fd6bd0c36a52164ac08b223ab31c2fd38121625ff329f42f47dfacd1b1bff1c9322a7dd88b69044dfa98c4dd931d841294cc016f5f086913e34260ca5a00e3a9408d0b7708cf1cf44edcfea3b498143465c9cea1e61dd37f656a058ab9808773aa5e1395f486d589d50218f7f97ddd4383cd491b7a6983d2f222ac4d41b0bfbca41a89728813ddd4772d34835b5b0331f72a07834ba0c2c53e60f9ec88e6faf2e072bb3517d3b8e727ba592260a9341705ae37d98d986387e4465b3541d0875b5f69ff2045f15db99fc0ab6a0c0a67fa118717a2f99100310939d9fba7d2a8edf19802148f1d0ebf501c4b1dbe640b8984876d0a606c0aa9d29ba0711900a4e3cb8dfa784abf8aa97b53b9980180357768b853c55409f7c28af1f0780db26aaa2093acbe4f9b2a0fdbdc47e278e79129dc0553dc70bcb514288b6a08967bcac9886cff12daa51ab53b4ccec7665651dddd6447d76ce392e7ec9b3fa85726b47ccacb84527d05235df228932c4ae9bf97fe6ee90156f2bfa475e395242e012a44994354800be47ff97131566703b30d205a5b9385970256e918395c052c0a61ff5d01ca6892ab4aaf9d823e5aaa9ae0835678aaa649fcbf21d7caeac9a4aac1df25d7eb25cc4c5521cf08b8fe0260e500a3b1cf477aaab761bf3e405c32266bf279aef078faa9a790edfed4748840014be21e3380a1bcc3ad007538744d902b5c2883f85015e9d1999c1f99b12744b9abde3a3cb7dc951bc438d37a34b23e3e007f3ba49c30c9c700ff8f1e38ec22480d857a96ae3b0b8844f27a59fa7f717f6f42f881945e2ebdeb1659e5c1a720ed25cfc87ca382fd788b19bf1f2e6203156fbc853ec4b590b29ae7783ec383ee5d3b07e7d5a070575edccf99c29b68abe5ba600d5018fb44877a4a3a6c96baf6a159c22f19bab3c9cc4619eed8da910cd959d3191c38859bb49f69040efef7565e46859a32f9f38507824ada94bdb9ef459643fda3e751fe39e64986df349a628e1947e445c32eb1705edccf5f522a1857cbb2e01ba873843039281b232f068b3cb54cb3d6514217a3b014ff69bc1cbb8dcb62021292ad083a1ef8dd2f3cf293d9362be966ad4308bd37f744e2804e9b7e8678b72bd8456c6ec8116c5380ad85acd137cdca2cc966829b97570dd8b0b5d8eed8cfa386a5be9123dd5ab2784983466cf40ca436bbef7c7e0df455238665016485435cf825718e6056d95e6a5b2710b7cb681f96b11065c503a914fd79fa6e05881ac8ff1c2e34745894552d8a286e370b03179da8bb13f967020af68324927f86875241e73d8113c23f7038681e938affd38b33823cfd5d4af2d303e06e6c9a521ea9df864f2cb80a8513374d9d1afaa808adcbd13b9c876fa08def914d91bf184575de47375c0a0043ac5960e4063c7b11f34cbeea30653ad8ba0af319be03da2da2f17d19fa29ab9b1b08c9e1c16cc32aef63606d97f6d77b2243814e8136a7a2aadd64c4b100c30c30a0991a0e26f9b66ce21ad2f1edb7a05914b45d17d5212381d717237648ba8dd7b235a9a200596615fb6c2b2f042aa770ad8b89eaee52671665458fd9c481f51eb921329a4637a035a282ac442e919ccf430f32c4b0aed6f57bfde5b0805f1d5e0f19cf0469d5f2b61653bec2606db97d563d94ff4bd668f62e79c5b40f68bfe46618fd631fbbbb9aef5a9f90d455240b1acd050cdbb18d9d956f72beb7561f34a0ec8f140ec4675597e386d2f8835365e172673c2ef83a3c1a300eff723c99ea953cad894d53aed2e60b5d683b68c20d48c85c93d6b9bcd56c9aca0e51332b6a0eb7f4770da91e0d489e10ea7ba210628b2d9577cdf1a6dd3e3dc1c23a0164ad8df09a4511e59a3463a6f52486ec29322f11fbaa7a48ff5db43429f92c2bdee70d5eb69a80c4c2be8a17d49f16680f73f6574bfc5f78f6f88064d5053a6788bd3c3eddc274baf326649fd0dac4374f1002e6d392394c75bdfce05f9fa98aa086bf51d68950edd3a76b600108df570af5ddd9324227041dc6ff4202ab40c6004ee2e377c2b205f08a4bab576216fe87ed91ac7a78b775fea208bf0fed5948bc6ea14788e623bdf9700677914c9c2c4cff407d8787a636e2aedae9b3e032ad550d0ae05b7f38023ec54f45053e0d8fd7aeef7bb6b4fd497d86412a28825700fc2013d4b3b2d2fff581012c00050ae5e7bd55205c554973d998c4190d6c011796d3c089988d8141e0caf217f409ce3d1c8d61a49115c31aa8805cd1311200012a0500a8a068daf261e90b3ae01b6a7284dd2d6d848234618d6b5d2d7f6d9bb5dea41b351121213bc40bdc0b8c0ba87acda3ac896199dd1ec624be36d40d76ebd825c2dca0e5786af5b06f02ab81c7b6bc83b1393b7bc3bbac089e9aaa063768f3d86166833dbb7907aed7e2589fcf84e5f481aa18e1eb4ec0849589965bae69b06ea145830790efd3be7cef1076925aabdd4ef0059a800953544a015763af08ea5d0d4d57f9d430bb9e5adb1ddff12fbbf1038e563dc0090e00dead41abef1b5b0f1d12022e23c205923318e109246730c213d9101896f7e61d38da58b7bc035e17a5f45950c7bc75ad55d9cd35b26a20455396d96869d01c76354de975d149e7ece7f82c3b4a3aaf79796d43f52ba487f49dddf801d3cb4b2d1c31f458144b48f1766aad425db7ee708c8fa686fd5b7d60f82198f47af87ef889116c5f5a12041c831d923238c1b201f270663731dbd1704ab7cfa537bc7da552e9345dba3d4de76078de6a3887a09f375ba7370a93c351fc788de93681d54831ad9b77e4a1097cefe0cb5ae701cd21791204c3e3b0eca3994dbc385786e37ad90df89addd4935eb31b787bdd60178e1562c709315b38ac4b4dbb795ec2d2cd70dc72a903182783c093008510b850062648a1a786663714cf5f1b0e69f47ad997dd201d1ecedc2761f1389610f1f030cbf1f6e6f84a03eb230e7cbdf9c21247fe0338de5ccf5e4b5bf481afa374c45f8f37932e7f91a1091e5d0b82479faf6e029a4392c5107c7d6636cf361c9479e760b019b321aefdca6ec4dbdb0da583f478d23bb3b294c76d741cd2b35797dd700ee61a7d5e54ca5137a5935e3ae9a467a347f1c33920ea353d65bbb183690aa56374d24727cd64435c1767745f3060c7094d306067271362c709190e89ba893db008b0cb6eb45513d85d3b185eef0cd39949f05000b6d7999887859d83215911f068c1d75d6d0253ec5e97de2c3fb31c34d83919593ee760f2a887766187c489110c2ffdd18454576f680e89908f31d000d7ee20e03e8b9379356fec90085501cbcc04d681f4076d6653bc0212a120fd1c2c59d7766a5e0b3b244247b0bca6d7108ee275e93bc0e8fa269334bd79ca64c68b32759f4e9d328141bb89ae0db5ed7b33421805154170702eb5c618314a29a594bea226d1ce182311ce160e9e935661ede2d98dc17501431c4ec686e9f035ae06cae117e054d6152083e10c0c03d082618b93e564da86f61e0e0f9d4a1e5efae7e489469f5f13fc9a3c27bd82322d04572e6e3e692b2b7aba996a2d6ed64ea7a7dcee59adba67d543adb5566ef63d6cf7dab6bd85a00cfc812b28037f7e28d5b4d56ab58256d28889b95b8b6fdbe91242a12121a1088566667c78a6d9a986b313ce00702311e8c31d5d075f52e290cae1a554fbc92466674e36987436f0d886c361222222a22bec7041f0f9582959b5101e1e18042244a8f0b199b55b64d229b8bd269dba8930b6872710283383f2e96e97dfaa1c34182d4f27415a952713dc3e5a95ad2098a755d00ab86ffde8e9a46fad5ac5e4a9a21a5eaadf8f69706b7030fd6f91c94bf567d0e8a7a02079e39083e9c741b322e6c45ad1e7e904f7b1133947e48888ae7059dc2213249dea3fa0a782335b8453427145c931624889132a3922265c1194910023e8b221321c1147105e76a3abc88290254c705561c18f1ff00e38c6cbe33da9d8ca5be86d66b392e17060681594312c9c198e766db9585fd962cbb633331b2c46166fcd43ab526c0c0204bbc15ea3c9d4294ab7a7021e0faf1c07563c682c37b56ae5c8cfcf8c1187788b898592dd906428b53052a488194c7230c121e564e3904ef55f4f128d455ec678bc09ae6437ac8bd2615dc6b062dcdc2ad910f296d5c262190e78f9d9f34d38a5f5eaa825ad673dcb7acf072bde5e53a74659bcb50aadfe4497e1884c3a1571248a43f42ccf7188be9284c801d4251b930405619844b131089a21cb64591c8631443cc4a4b4365bb9c4ac2d4a20202989e0d30963f4c93438557c64b2c934b8a156a1bc271357c33a41c984c8c1b415447816b99c451e6791c35b240ec141cb336806b58abe3f939062c9daad6641a327a29886851225dd12b3362587b81a8ec9d01b82300ed964e08a622dcbf3e31007e3c26097742a4587a4c54223a51708928e0222fd05e92e809e1ade0c260e860d2ed58f446abdca2562ba299148b55b0d8a69884371280e495189429120e06ce1c0cea8d9a9d5a11736c6218b72383a4c8a4317ce71c805ce71a8c9731287aa7d9e76d6381487e68c4356cf6d1b1a402ae7e5869a115a4aa53c8cf313ce29257defe60fd45a6f92812bb4915aef58e31e61a43262d68b94d26a82109a5e7d9713529de9548d113e296384efc9f7e468a653cf1a76f6ae87144464b7d9a5346b8d747b2246cf46f6f690629e7453f0d98f6e8f93a59f37efb0d9632c2a6acd3b0fd67766b321a053562c8126658c52c2cb8ad91de9c3a8b26ee3fc2ecd45401a348723267ee659efc51a6396179d326258c5b0d13b128380d18720ac4741d57a9ab5c29905378459948f9bd6a99f4ed8b2e0e549dbc9c406ad61b0d0964934ce2cf85508591e76d1196d8c27ebe46a5810b2bc27a564e9da970ceafb19b42e02c2ce0a1ab000f765b0bb820e40b0cb6c2a905962da6187dd71cfa089761a42458102fe65f0ba4ef5a975266a63b4ac2725c52e3a2d4bbed8aa1881e098dd7081adbfcc653a5cc82849f034fada96768cef0ca65caeebbaacebba9ab29c4e56502124a355ada256c6f4d2a1698b81281bdddccd3931325266d0eab38bcb5d3785104288d9c057d236fa0e2843a9f6e4add2f67e349e13518c423b4d6bf7f9c09726c3142fd57f3cd90967d9e9e3811706b9a70407d3cfc78916c4d5782757031e291ac2f98d01f71fa552522967c5a494544a4a29a531d26ad2b4ebba324ae3ad59b1686516c39e8f35aa14675b7e4dc0fd28c33d1f4a63a47656cc8a36466a5d8ed2f72a26a394d467d4a72631da8ed6e4d050873434680ebf67ad8a2c500183e72ded59efa2d2b2e6b4e69c5aab64b4e6dfe0ea19b4db566d285068cedd3977238ce67a73384975b3643b15ef4c35c68777e8eb1fb59d3b7c84dd129ec203c4c38dc201b8f7783b0f3e2de4e81728046c1140114820a662a29315b0af76661070351a7c819f2fd6377d76e3614adfe5e8d6438af726b09afaeaaab52eb3512202f6f5f6a8b78f80aba9578ab7836d06011428a0c6de137c81b779070a1450535f8fc17aa3617a9aae994dad36081a2861c35196c028b3a18f593f6e42ece854bc56055a7640cf9901277104cc4408db5ddfd12a382fc4dc5e1a70e4e814fcbc178e1fa280e7ddbcef45fae2a4d3a2b604da9c77084b071d025f318965bd675d4f5ef1bdf9eab358a6c3fac4550911adca301cc2c21183afdb1268d785b7e68e563d1c9f43881d241fd5391a739756c12b304482071f1c85c0da1246291b75516bbe6ed83a60efd0d730a8ee07936b244e8070c32763d3aad09e8d798725d0a47cb1077c0f65bd1861940f46e89ceb547f61ce39a7a4d69c11c668fbbd47239ea639219451526bce394d6198a639e9a7a4d69c73f6a443a8fd3e6168b543f153050c9d0bfca142ad19e17561d7b582dfeb8b17b5668c11c608e194adc1e799863d900a385b18638c3e708cb17fde7b60a8011226046113a3365848c137316cce3963b43293a777f50ad87559d2706b8cc24eb16004c35337c2ae0a2cec00d3c36d84217c74b69d98f56aeb65d58935be367735a6d8c468137ae6352ff95aac9a50863254818625381eba2ad0d0050c1f71541c235da1208b2c22764fb0c118a04007222cb31b1187058519a8b006ec5c2a36c1049562f3b228cd4c572901299a4906a536b4c1426973350f67c73621b6da8d2bcc11a349d35c7e39e79d965a4138187a9653b638e680148b568c378ad25d3681ae29dd1d1b11fdd24f31e1ecc8a06d42ec6803f4ebcb82b8d80004d7c0f92cab5a5686cdcbb26a8e186766135d7d167da49466d8bc78f4c0d4a292526a552b6810bb2830f98957d82b7268879bfd8357b82c8a127699d9e410022b6daf46dba2283de5fe78cd25945db756a191b61c428c81e4b21b35eb0cc7c3d92b96d592104ab86af6daea4f2cbd66591cf56834aa23fb7280afc32dc332b389029320f87a25451289e4e4282b8161fc8e4edd1c42d01c96655d97854d7a5da4cb932e2f8d74e588f1ea548e6849694d3b2a597a5da5cb01e5a099b50940707640a61ca4fe196440724ae7542fca485531dec167a3e3fb5297c4c32e7ede48297c6f273e5845855bff8010c60837fae406e98ba675eab91c3999aca6f08a98ac99cdc83e2db3816d03ef8c66a38e7cefc5930b86105a27e541cb3138de8b8407cdb2b65f2084f03d61e78c3831ebcc3b5f113caf655d4aec0da453b2093c65036927de8c66c19b01fb473b313787a53c84706b2072523a1f1157072101ca6e6f3eb626fb8addec82b1cba294ce39e79c319ab0770ad3c1524a29a58c903ec3cc666a2c4eb4ec823ba55ddb122d58891d0c2f4a07bc3a0fcf63d98d3c9cd7e5c6104e49218410dac88610ea744755848d755a07840d2184536271ce39e7c4b09ce382e1b13eb659fb8e9c25e63d4103fb75c53b61431c5511367cf2a46dd077120fbb274f3094f29aac4e630863ec4b080773bdc2cd7441795daf177417941042784179bdaf9c73992eed65296d8c86e11374c64e659d88e5e183f219420853f3e6eb079be4bd03b4f55993ec14b476802fac67938d125e1a3873dc110c49751875066dac129b97654dad6a4d6e2668b2d95e9366b21147dccea92e6035537e38eb407c499c5dd48dd489d83ec78f3eba39e29ce382f0e8f095544937bbdd46f51a8d7e8dee96ddcf4db51eab17ed203a4529a431e2d8704a79032f939087ee089e91d2471ae3928818bea6c371cba79c9986514ecbba516602a372a000c393708e9180931fdc4f6659a49dcab2d1352745f8c1c106126e8665c12ed8d91e687d4a35ed8467cccd0c36992236f56824b9d91e2de5d921b1b44536bf32af326d5a6eda4e6fb1ad6c3985b3ab6c3906673789fc9c19fcb7cc53cb35177474f3c3a325339ad3b1993158deec342db35b1d6877f4785f2f9cf89b72f3ce0f9cf2b945d336832702f07cf1377472c1f391b6b8d974d451b627e460ba7ff44bbf22f8e20eb978f834e35e911c68f63faebf1f3cb6479b772172d990802ffd568eb435915764f52d0eb420411a7b543c9f2473be873b0d746a3e6e3ddcfcb020446266837247308cc34c47132e40c1138af0a8b4398cc211738e2bc230ce72215ae611d117681957734c133373c40cde6ece0c3ec9703562bcdff788198cd372edf92ef8fa0cd6aeb5e4cce0f996d782057ef761bb007c7944ae533136e7605c36075f606c47b8223cba38f132b361b94e8348b822962d5a21da3c0d346d4b5838efac30cee8adf07c7d2b3cbaf52c936fd5aa1757b938b91aa5f79fcfa3e2fd2001655e5cde7f3fa04cea00b8eea04c008866f00db49c3383536ebae9a6ab6c42546b032d53d43b099eb758607bf7b2bd48cdaf6cf32aa66d1ec5099e01d81c7c01c0e6c3bccb36af11e179f3e8d9e3883a24d92db9a885e72311dee26582dfe1a663e137d4a9d773e7768b202bb4e0439a772e6d80bead8186840e13efbd4b9f982bc00215d880fb9647cb8ec8c281961d5113d9a03ee7a69bd339e7a69b6d44a756fdfc922c49f294086da149abf27bf29ae0225312dc3ff50abbf7de732c9d6a6c09da0b1fcec70cacb6823faa7301aa10fe6817703522b5eeecdd1f107c41f029818fe8357972784250a67485e6f06468bc973e7d79a0710287c4892433c0ce75e1998087070965783c1870369e11948e8bd34f49120ffb143d25b89f9167e3257933fa3eb97d92d4c35ba110dc2fc9cbe8bbf17551177e78547f8d4a9b96d59e42bbb0edba1540b9769b404de0d5687f35dafd616b50fe23d6f8d07e025a533a697b352f2322d6a0dc13948054a3bd6b5c6d0263cf0151ff321b9256abc0d79fd0841bf784d73e706626262695faaff6b9dae7baab02d7c3bb1d2760ec10b3bf6c853dd606874021b304da03dabae66542f0704f30bd15371936f027ebac8aea5cf20bfa69a848a808c8fdb8196a62d9e1649888713310657b3d0d638cefbd0cc7133284661a6954b3cb3f57a3d4f384f8d4cdb5cbdf0d594ea7ede14724081d4d1af692461a6d37f21139e260fad9b3220a478ed01f68ae536d4fd3393a0723afdb227e990dbd4e9f106b88a45694718489094a8b6811bdfba1c1f2d78d1816a12406fe74ea18344243752e48e8b44c8552a85fe0e7dfb3a665cd5967ccc914cbf75e86e361b1e4aabf6ece699eb7a4534298116d3e3f2cf21b7ab04890209cdfd07b58b46ad2bb0b26712288da239a872ec14431189a40261ceaab712e5368efc78bf681fb56b3220b425e103968f0e688f3fb912dee1f16065a6e1fede347cbbc81f8c0c210ec47bc815a853c140980f67025f9d0dc314d6f374d84f500eb1407ce2791f6a4123bd85d01b87f8805dc5660c107eece3399cd7b56c999c133e5f27c3668f9772fefa2de1f0f26f0fb8943d4828270dfc422851593b80096a77ed635699deaa8234d9dead47b226af1f35d94c574a0652da59dd62000cd597105216cadb88212dcefef8500859d1559188924cb9252c648d3ddf2bdf8704768450c1f5bbaeef85aba27e5bc2d812663bca5e35d6b3275ea6df4c1e0d806804d31be68d9694ba0bdcb1be13c5294c4845d148a80851fb477876d09b45a1da6adca7df8badfb3a9ce999e2dee1fdec14bb7c636a369d0e01d8cd8662488b3d15badf07b6f25350a459eb0820a1284514a88825142e8a8355d6fb1a13c0dec6c88f83ecc74c4ec2f6bc0756da75ce376cf5d188698e384e2bb281df03a2d54cbca4c76a5397993bcdd87b971ecc6f103eebbee0b4d0e765f545ace4d2bbaa7e31a02be3bfe650d80d98d950d011ffb7ec2965730010ba29473ca39a373d3c9e85c7f4e17bbe5f62c7e94c637e3b9eef75ed3760f2563c78e31c66e391bc656cd487b4e89926e4a79c137b56074cecdd66edc1b7d6f3fc21bdfe5a4746aec9ee1cbe4e74b05117324478a860deb7d083c1bf412bba04c2a95aaf2653704802b1d344a04847f5b0ae6197833bcbcac4bebaf88eb5ac0ba49dca217bd2eb42895ef0a91a3534174aa8928c202312919294a29a59452d2503d525444b143128628c0c3274fde8c57802341d83dc1063ece95b0b3c2481016f5063ec9c34e1454602705a2038c01f76f3c350481431a709f810adc1f030ec47087332cc16111042f8801b71594e0aeb802922b382bf888c10e363c1cd06a045807fee19777a61003ee03a8404a6b946f728063861560b6805370501d400c0e89156e162f4c50b2020f488f48c315ae8ad508b04e9b70dfdc8124a5195426316694917002ee888db207823ce004f7e3124b60405d830684d41b7073015b65c07d59836c944140072b0be8036384304218238430c60861ec338c32964fe0fe7504b70ddcd7edc04759f0a30926d9aceee4071bbf2630253d23bdc78fae5b273ddb9c8321dd4c294ebc4b3c0c9fe11bc36390e2d1e7064f8f7586e3dd399879baa1746087237a923b82fda4c007c60e4ffabb39e2077b098aaf5f97de4c9f53035bd65d765d1e1043d296e12dec4a77644502ec320738fcae7332b2d13c5c3918f99aafbf1868d7cd3b0fc7174f9dd546cf4202840d6d6cf860a71e0cea7bced9c0060f3b2b6cd082878721f10a2748362b6cb0daaa700739cc392724c2ce0a215cc0a63b778515428e6053a79e7b33dc9f8dbeb36208931a9f8dc602c033c3c0796564c9e7d8950eda74710e06f6595a5460df6c3dba19f4cecdc8fd48fb26716cfd8079b82779c0f38d235f6c5dbec22478bc2738892526a6d7693caf43310f05607aadde227ef3c2eca631842e9d829f83e6f2ae78c10f2c8d509dcbc9450a4b0a5f6ca4de8c7e6edc2b495d923e134146b42abba21e17d040abde8ea7fac1bd1495917a3660dc8c46f264e0a659c60c1946c8c0dd1a80cf4e480684524a2865c89011e9b30c4a21a494c227c30a69ab86b3610f1ceb5115bb68ecf9624738449c114aa8845ad5f0e8b356d60150fd1a64cfee68abba1f51743e4abb2b76c539e97c718e32b043f4447675d0b65349b46c4738db76307a0607901d92254ab00ec453471b31b2831d3220898309d600d413448601e782c88e80e7c59957625e0fb4e0f83805bc73913d7a87761482c40bdd5ab712dcdd5d4516a8c041ad697003bd3791aa1ab468861f6eb079d9807977e47b5360d7e91bb4f91d6ed0ac1c595a16462df66959b5d65b9685d9f7ea76bd1edb5033d2cbaab45a35d3719d3ea294c8116f9d010ed75bd6c5a9d33e07c3f5d5ca7458ee0c4678028910ee0c4678a20ab7d73a0dc486c070bd1646afd3b76b3b87b84cf8391769b5a066139640c36e8f7a139bf376cb6ddd1edb503d1f98ecc14e84abb55a1134ab75aa2f1bf3a67d37cf9c7e9e1d507641d8cd4e8913b2cffed55fd77545967af3cbea6d8643fb8bb3b4cab970d80551ab5cbcbe6636356563f9dc68294565e50ecab478537bb386dd7c7abd393561f7b83027184a647269e0853a867271acdb19dbf06ca01c335dd675b23fdd4894c262e55e272429f7ba3dd775dd7c94f97c3de7d088c1d82f8ed5008bbd66d4d5696c1dbba86bf10e92ee602647cf5a95535b1f8c9b53a87bba2deecaada72b2af7ba58b00c07765dd747d765769d74ad44154a658f655916ec1e2e32894e70a6f691496c42517e45a2d8061a8964b8b09862cbbc3beb3198c6abd328c79edfd569202c7e92f820c1b0873d0663f514e515655e9dc6955a1385462629316edade3524e04b3fe7946309ba52a89b531fdda02bbb320cbb2e0af5f7f9d349a3d7270d8c3de7d0c0d961e8748e2c9bcc7060c7c96e7e5abd8c19979caacfc75d6f8671730c9e2c4f4e8c0201b66d7836a6e302ae775b88a934ea6fcea181ed67b5cf5eafd2619f7d3359fb6b8b1895bd9e5a55cf4203d78a005cadaad5ea6ac8fa18d3aad1eb25e06a9c5eef621b26f06c8cde8c7e7d47a2393b5e940e78ec38e0b1efbc1eb85ed95d564fd3a6bb83eba6d338bbb10d6fc674f1b7bdd02eb0998e5dcd737d88646bad774fd96c8f66e117d4415a507e41da0bfa99250783abc93e6d66cfbe5edb85619b367f6df42db6fcf01210a7dca67c3e25b3a18d535a983a356d8fe652b69922d4024ff0a55b6437b25b8bd95aab0c3c31ccfead6437deafb7a0afcfb6235c30cefb75738bd3d7b75517561ee182b15797e16831b3bb96d92e473d15e5baeca56dd97d9bc5957ea37f1bd5b4235c708e0b36754a3e223dda115e0f56d55715f6ea4cad6959ad371fe3eb5d164fdba3b504d5f75bb65a6bf66b239d6e594766f5a2d87889e8542bc9e15c49bba42de25a35231b7cad19f62cbfa191b517bbf95a3644c51729c3a15d5eab43ad8aaf1f6536b5d69b2366adbd48d7e7bc0e4bb1a44b5f16cf066629f686f0757ddadbebe2908eddde62d7eb7551f4f6d66bad37df5cafbf6eebb13b8f028566ba6549190eec758459d34db198b52919666bf39028a9d6faeaaa8cb07b308161b709ced663768355ebf64fe8d9b86e1f93cb035e96fee63784ad938e51d2c59ee3c5eae7b167233d1cc36cc4a4add6d3792fa137c33aca96bdb48deeb26def61bba7819a765f914e5957bd2e6a7fcd9bb37836ac67a4be40b18196edc5b97460b7f557bda879d2ad7a8b922e0a14d9efe620e9669d38b26e331cd771eca5afd910165fd7cd4aece0faec2f3b96d96472621058fa8addc2b079535b5e6f9f9156bd2c9e8d876bbd4e7b43b86566c3e3d98b423da0e5f713f8b7df6b827ffb0eae5bd670b5dbb639f852e937ebd8b683af4d27627a5f166f863537b9b5ac60741053785052ec878a67e4d93019e51d1e8c33b21fdd4ce539a6a094482f9e59121003159369bc9bebb1571728d45b7c4096c36bf0a48d6543bdb4c4bc88f113bc06c6e14f315c905c1bc0c5b6b5000a4aa2a45f8e1ba236d87bf42fe28b5edaa998173d9d54172f21841042d847cc601c991f35ef73a66bb6ee1c5dd8f349559f535986dd489b832fa3ed5178b375eb46bf6f1370a736144275396e084bebf4aefbefbd07f48e704318e71dde0c373784c3f5cd366d5dd7d66b9feadc3510638c313ee8fa29498224dac4089524c1ae8bbc927c48f10ec49d8b26814ef01a29b23bfc8bf91c24dc903b0ebc4818e186dc95483ad548bc2dc7082036cd881cf72cb10cb24f68b9993413a056f5fb0b249b042232572c3dbc3f854c16b44ac65dbcf8b4c313dc45120fb8a70f98e7f903779e41704f9e3904779658e07e009e2593d4b3247a79964d04f02c8b708b67e904f76d0fe4906e39a4e5909643e4901f5c9d730e4227dcc5da9677b0d3342ddbf253b9cb1c49a6002887f019c12baf085e89ed4485dbad1fd269ba7fb4ca09a8f5db54543e7ab3c08906d2aad155defd33d2b6f7c39da6f3ce9343620f14d23c0fc320d8b1c0659d86b0b31b8f24e1cd2a77a6a3dc7549023948ba3a10939ef2fa0a6ff5d44abaf51514283495ab13b1d35036142834d3cd2917653e62e7e24379d681af88c4cfdd3acebcc4ce2acd971b0886edc49b010fb7d12b85f0f46e6c479c308e7b0a4a7c1440bcf4ce70c4bb9b08b069fde2ee522e53ee4ed3a52d0a201e8198b75036dba9f9026c5abf9c523e37021c6533dddae4dd264797db667fe59c53ff6895c3a3b713cf86c3a337104cd396075a763794ad2b640924911891d384c8904482fb6a69b9ac772ba58fae72138efbbcdbb23b77150994a4074012496ff25917c5801deca6123b385e0fb46077512477d235e2f396750c92fe9668c1f2efeee61c888a28799c78eb325a276dd9ba2d3d96ee2efce580e71cf07009050a6dde5feee2941e2f8ecaddb79c135e396195dbc34c4776a37263caddcd23932905e51aca35144d739b360f2ded0ea2684fd1ee1af03e6f4eb90ebc913d4757b55b9f52ef3657533608dd530e1d74cef2e0dd95bf8bbac9fe9efddd8c618923df32b3b7646f794bcb96e546caded27219b77c32025b9a09febdbb2edd4c6f5d3b109beb17ed6695476bc35eb79c7322656f69b9599ec4801d477adcb43f683dbe5b7fb7aebb761bc32dabfc74d2276a1ee7a2f468efae3cae90eeca96556ece09cbdb72db826dd9086c8f693927bcc4b3b21b159cbd566b5f96dd6e138ba9a4536e9b4239f821819f0e133df02bbd96b689c510ed274f25b89574aa3f148f740a3a2863fd45eb3eab47069e5b964b70117ce9533a8b7a66519192563d2c844a25205e2a412036b9a45ffa755db1efe69cf0fb4aa643005bf8c116de77f0e3e954aaa5908b8b123ca30e742e2705d4a9bedd8126a455590e71ed438ccb09f7107f6036cc071f0e7397ddf0e130a76998fb706cf321e618869db62ae5d8b556cdf80fc78e9da555f4d8695ad3344dd36c0fefe1a6bfcdf4942d3f9c62b79c03dbd248a5fe9d5a1505106ff9fbaf77738e0b4e398caf58968dd6108872b78d64e928b7329b518ec5a5d116b135d4aa9556a1c826594328816894b694cbca761979e6f494cd61ab9dde83cb4f97990d0c1517ec72954c078bcf1bca805d5e9f85b1459ce27269cc663b857dc6a6750afb0f9ba953d8517463e914f6d3f361cbc731b2d573c12c21314e8cb73cc65b60b61897217fc82127d4bb00d8f2c300b8db9ce5a2d3368904c899524af074b70de0295b3e95a00402ca03b84e7b2f0670ba8a0036dba3bd6739c4f60cb13d1a0c000ce2417af2e409a641310e48bef7de7bd14598ad1bd6ff2ab7252c8c63fda59b7756b8f4b759bf5b7ef8de6dee488a439269e097bd09c4e33d79f2e4c97b4ee1f7e254994cf8ddecbe1d886d52d1f3081094994cfaa5df733201326d0faf00a008beb8f98309265d88a853b647b38a583d16112036a9645a3d5309864d2572489155d4aad7c333c5307a388cc993b2cd2073fab40aeaf4cbbbcb9d0f4874eadd7a0f005b9eab4cb1cbcd938a3c85e03e8c593483d099cd769df6b096e382b08beb3762ec5bd6b1de4af2b40a0601de9740a00c10ad02ba122843f4867ee2cfd08f8b3f5b299a4e03e71212e3986e98de627a565f3ab52f9da555a3976e5b4602c197ae281bfc7b47cc6089f2b884c438d65bc5437334b4ed31ef02d8fd695b76574762f794755de2169f72b9e51cd90de194a3d8944ed3285bca8d9d2add6ea74e953eda583a55b2fd52facd265cfaca71e99a4e0369da14a3658bd1586c460ca16ece0c865046e6fd4885abd1e22da1cccc55a7418017a7ab641894723f4e4934823bbf00cf33180818b484b5919e23cf6095bfcc8606aa6ca79b3383df4f40b84fb77fe0eef1f94182fb3332aa57e4e00b8dad009b832f406c38d8e6e0eb5d86374ed397405289249a3fe6115703c80c3279a28c0c106fd50df00258568f454302114002c1208805e941495aa5a455dafbc7eac97a200c6ad58aeb9b5d12bca2b2a5801c1292eb977769001a05006273d98d9be1aff823815c121d0bbf5fef52cbca6c5a4208df7b2f063b087b09eef711dcfd63a1129c248921dceebde9ce8c9e40fde7a455f236d22aead5bb8fa853fd8a5e51ab56fde3350900007c70e9e16e6f89018305f5c2c5e9b5785fd91c8c2905a5a4914699837130efd74637ea60de99287245f739e954afda00fd7ca2af08664aa35326cc847337bfc7635668f9bde85d427908af965d11133388242354161476294db91a3cfa381c4e3916ccddc5e5ba8b0bcb75fae6b13668d6e771e6edd48bd80689541f52c09422429cb34ece1744d6f7de23e2601adedac46d95c8dd1ff0e552a0c2926ae1569bd4221f68ef7d480f6e9d2258548b3ad53ddebb45b5492d7a25ea54ff55b7d5a2da249379354dd4aaa28a7aab378212094aab37a32ffc2eeae6ddfda26e72c49727c7e221b8b3782be6a3d55a89dee06a7469a569a7139056bd5722f812a465ac0fecb5b4aaa5552dadb05b897cec4a095a9e4d2a1194714b7cb46541ab80bcb4228d321b575af9c06dad2a760ee6513810bd19ced570efd337381beff00d1b84c39b319a440ec641194a74a4652e1f4d8964b8b8e04c8b10657aad3a557f7e80829408419958f31c76198442506888a868056562cd0e842520259e524f8988abf1301fad7db45c895c8d11381bae34821209de0c5b448b3f1128280a41d40eec3b3545f109a7a2455c6a155d8d478d2004f7ab7d0ff5fefa1ef6a41eda776a48d7aedd1e8e7435927cf87d4712107035396e0f9aa06741f6a288c03e3a76513b7ca41c3b8a88fabf9ad195e288777757839277878fd1b38f6e0f297664b7c7abc93efa4e4d763f2bcc6eab941d29174504ca538e72537850fe6a502ee5812fdb4e4ddd88281d7ba9b49ac0abc1fe6ab04bb3cce6dd11ad56b87f56ab677dec8f15b24396c81665abec470624e3c98632a2114f56345a59a09eccc7f6404ba42bd1e887909220a0cc1ecaa0dcb1d20f28837217f580d2b12b02ec25eca58ba0dedef52b911012160a2dbb7aec0f059a46c4d5b081312257834e9e9eebe08bbbbb59db6a51252239d27b5c4b64a786a66bddb08d07e57a30ec472d822f6daf832f3b353b35a5213d2520ce95784a41302c68f1d40737cf95832fb4872757a20aa402e181323b35b016a93eae86bc181ab44c79268659697506ed3d0a090d113569d58539f1517fb81a0f9b839677f0752db3b968c4d81cb4f93c57a3cc46d28b723649d0fa637f8eeca844f0a52b503dd2a9b9c28a603e50063501ec15bbabe91fa02025aec6db7ad88b8004d8ed454de08ac0f6c06eefa4d90eec4e22f0a56e523c27345b44cbd86a85fdc080603c580f4624284889d01051d1d573f55c457c7e806c112d5322dca714db766aaeebba78ac6985fb8f044f7397ce39e79c73ce39e79c73ce39e79c73ce39e79cc35627ecced53cae44455066a7e6e707b440a04cace95b9ef956cf06caca5d4ac4c1dc4083374f221e28137df209ced55ca54aabd2caa7b48a3e3eb754895e934e35512dca0fa382e6707442832b288351ea6e1ea500f72b5169d5aa9c32045cd4aa5c5a957c54a256392bbad0a45622abc426b141d89f111f28f363940227537fdce805b8474fe0fe2807b8ff3648548970ff854f25c280f25c5940f0a5673ed51f3757b8e710ee2553c8c1b405f46e05924bab150a506985fb0191f94d29825d0f4d603bb46d0c8d6ee4ddf938e79e914ef950fceef3814c60faaac40e76786eefd03dbe5b44a7dccb022661e3aec4c3f01551c122ef59f3e689732c8658124e261618ae46544293b69b0ba269a557c4bd08771b5704ca68fdd245dc2541d34e3176cafe4d6074d25ff62e11a36b276d3d7e20aa21bd487d25954ed36f2b02656011580416e954f7786a285243baf69476223560ce1d0a218431c26c3e6617fbdc64bd76b0ed8d3e6e2ecb01ef6e0601110ce19ce6c1ee4122b85d13f98b98dbc8c3021786303968daf381f32ba2c9402354bc9083f67a8ab491d7f386d82609dd745914cb271ba313dfac5dbecc9b73b02c22c774a2ea5cb27330605c16162c095a66c1ed52af251d0442085f16b0ba1ef76501374851dbe92e834b709694dedcf22c63ad3bf0453ca10761105a691ada40b99d4e47fd099d4ea89fde1d04a38ae8942441f92d8a7a5996c70d4aa1222ca550132ca5909442520ac917d12a19ef5bd229599fdf9298eb8d32055f5a2503e3f974d9e2ae656852b0c5cd6e09e966aa035958685858ee8bebe2e658ec62e52cb774ba3a10bb58f9751cfa885d5c9c1559aa2f41892149fea297585e2d2efa96648f1b8aa647529a7ad895cbed0813c6815f796df9d35a1cc6d6020f3308c078cb6bb418e662b32bc7565a6c51c488bf36bad97e31dd58797c7d2d31c6e6430a172554dceccc31158142fd2d9142c7d3feb4d2ca8bcb1627b19c65e5a8bb38dd453cfd16e34fee8a39c8a011377978c52b46aac40ece39183ee7607a8a22ebe1122d780e411fb14789c22e037630858f2bc7564e2f0a141ae600eca240a1d19b33c4cab18b737acc3165ea3ac5728aa7975cb828b538a9c5492d488f142dd2650bd24bb74897b0449287f205e9be78e9c5e5f6e2e687e5cae3cdb5547a2c3d5e4546a962a5d0503a303dbca73ff86c65e5266a5ab9e95b5e39cd564c38075f10c63b48bad9c5afd7c3687194bbd8728b4f4a4d37ddbc72fa48af43136df194c7538a4fcf267c8a1767e5a72db7b83926bc724db71a969bc32653cec1b6e4b41c5336a230084f9b6d198e2d26239199c0b407a6d46e3232d2fc45baee74cb6cb0c6acec4666373153e272297a9add441df02626219f840ec5979b41af73c2d4ea5cd94d5bb92d91ddf40acb24487714fb3597682c219617a61f901b753094fea2d673891fd94c06c6adbd0d9a38f2f598cc6ee4b19ba3bcbb54f0e9a0f1f62045156348829d14b0ab620c2bc02418479da62b8ceab2185f1cc626c57b75d9751931d3aa774aa3533331ee22f11e93738576bdf8db6668f4cb7b22bebbdc42b07bc492a0651ae3301ee3f6f881a826c661fc6677739a676e2ca736e1405d5350a73abf18ee46d192c4d5fcb45cd72f316872c4f467fa25e668ee678a213885dd8f12aaf0338511ec7ea6f00183dd8f126e80635c0d2713e3c323042ba03842169ad4f45fa6c3c2ef4228a5fc51c20c228c2c41389977cd7b118e08a75a51b5b0cf24fcf228c3cf05333f80c82ff80d3d17f8650cbf8a1f029ccaf4771aa73afdbde4aa536129282feea9532cce1480d3c9c5cd3bd8c5dfe67045bd3e7a63a7506742107e71fa7769a750375c765dd7461f371cfa1718f597ddd0bf78a51bcb91c0516379f1b85d2f2eeae61c8bafbfbb17cebd783e96b521e1cdd8dcafed1979335ebbf7ebd4e26161cb3adddeafcd7a8bbb95bbd8540e6562de87628686f2738c1b52d9e2ab89feb439fcdef567c2b96eaec75fa763fedd8479b85328682db667e4f9506182e9a36c1c7030dd06dc6ec0ee0d360e8f48bfb4079c902ec820a805cf04b88f07dc46e4f7f37ee812faf8aeeb3a0e87afef60979940e9d1aa184309b0e576f0db7420b66414304ed32618f705c1c7052349d0f2cb69624410b41c1343c3a1dac5144db6e0e451ca392dcbb22c1999e8d106e86374d072ec88fb39a1b9ec7dd552a8fd5ebbf7b0be20a5969b535a91c2abb15709a0c1eab08bd6d17dae2d3790625057eb032d37911d68b989146995abe1b0c3451c0943226fe7b6dc18557230fd871ba350a620c23a8d21bc8d3126684f8b47345313d90210dcef20b411dcaff047cb9a0e120ea6e39b48d6b47821910da2e57824531a832218926091297ef6da18ad5d3951f3fa89dc5c9c314aad0852562a27bd35e53bb3a19f52c64844299d5b84cfd1cf8dd28dd25a8f9dbed6691fcd6c281318fb9c731ec3b026f07c43d59a2f0bec69f418a5f4439da2563db6557b2dfcdca276db2ad2ca9c73cebb3b7a1d6d94081ae93b37b4165f798f56616f7115534a8ba3b478e9d9ebaa97d2538cbdd60da71e3be561e1ec1846696e6c27cd6cddc12ebbb94e8af8b15553357a18c7a46498ee6874b3a68d1e5b461bfd61d8ed19717d96d6c475635a112dc7e06c3f376a95d8c1f3b7b6566c27769abe2c0bdb1c9e578c449d9af559978785a30d5c792a4ba0963cd94b3ea3f8ba59a328ecd875facb3abd46e4ecc0d93de7047b4e279dc63702d8c20fb8703a1778786610a455cd038409b8459e4e358d36ae53f7520cd539e79c737de7ded3b2bbbb7b06e71c1867f4fade6e60337874177cb586db647adf7c9d9e4219d375f025a3b647b37f0f6206ca980e4f93438856a1a858d82f6926525ba72d672667e6c152c7251506aca5a8c40cb6e5452d999a11090080021314002020100c078462b168402c5574457c14000ea0a854704e1787598e29648831840800080000000044100409009663365d0df3fc26e86c2f4b93298a4c0b525111287476f7f0e09710190119e6dada482cdd5ca35d2432560d9fddc3a4e0906a835192122fc34a814dcade8aa881f181b68a4bc45080914c3c0be38b42ffd91429172ca9d6918d005ea107fe08c5bd1bb6dda02d67a2e31c5b5586105e6ef7d4b080b2556b8c6f3a7bee0a110fe7705bf1804736b14ca32905bb2fc4d3877af8aa227dcb8bdbc709e0dfbc70bcf0fe82c96a10994d1c682e370a7a8098096d468806508a8f984bb623fd84f90876c98814b602411f1573ec8d6281607768696d19ef53b0db6c0013f324686a0d3812b34f99d9c12610e24bb766742055d487eb521a21341eeafb1fe60d84ee60d6d8c61c2019b4b24cf117a89df91a635020faadcfddf57fcc959a82ede000eebdd5a1c58b7899a601fdd13f18368efbd32fca36c9f388c10973c3cd414f8dc8a4216daca190929f99eeaa38e3afa15ac98868df0d4878201d4d4350f0042878215cb9cfba9ba69b46c099cafed60c9daf4c6db8ab4282f58a864b17e49cd67ff5071234491dd49881aea896fd34676d0211a2912b39761ef7403f8dd06f9e687bc4258c6ebcaf6ab1d32c73d78de6f5e5565d76b4a6d9d2154cca1e3632dc5d32e2a0bc664324e9ed1ede5b0817a76caf14087c428c70a0857ac8026482f472a7a35ac777e51db6493718fa79138243addf9a75f10aa13e794c1c5584d2dd426a781bbd6807c541a62a203246b3dd486c39b78c9a0a412d76e818c2772268778db9604467fce6628cb308a524d2f640ca652987b51e90bd37d761dc2f3ca760ca81955f93b6ff3c1fa69d79f7d14054ec0869998687ef6ecbebdf709e258c1e5939a0694055f92a3e2bc698496b624097c1af6ad4929b233ddfa1c55c2f7ab7d2435c56814b0b04c26b00a4bd36cd09c59511aba5a0c8f8115134f1fc0630411fe69d60d0a40e3580f7aaa4e00f5fce1c4d3207aa9346dc522e09de1d58007200d2e840009aea1c477580c3667a16d51563a87102a7ff0bac4779d696488f87089cc9c959fd39d0c71b3253fd164505f98908620cb37a820f1d67b1830d3c6697ec33fa008a1b0301e8f26aa7ebd5795f6d09476de74fae2c6ec262fd1a6394b67180b5c694264a5f68bbb0548d3cd8dd7cb8f53ca01da149939eed14262b167aada46352a035724829de0307656985e958ab1f81b80d3d2881b7d7d497e872b442b98d10039a44320b9433c337000cf11b5e7e51d8917b48082227b6e59d241d40d1d24ee21c9e948f738eb0730bae7c5d28d3c2b1bd6bb452ff267a6f65a193f081f77cdae30e1b34ceeedc6cd9135613e2b70469700bf50286d8e538667ba4120c8589f8b24388072ea54871d8d41b2cc553c53161970e5ea462c5084d025dc4509b7a342ce86791d9df233caabba84d0be7d019274c1be9382cbf47b477cbb48112f26bd669131d5111e3845e7f8b098b7eb8c3df0897c63bcc19fa6cf84770714706267e946f319b53090a14c21c7db0a52f7b2dfdccee138b3bd66509d449464cd5096487b4a5d6c92d6d15a7f8bc1a2980e89cbe8fe8d0c450f60829fa6af7fbf914c6b2e648863f73dfe56acb2b62e1714a3e8f93c23786afb8f2a32bccb58d16d94a4a2664cd0ef2cfbfcb8b8599bd223b5c21381c1a3b34505c325a6036b9c88d2ba96c45de77a81097c21c9b16399189efbfa1b270b296a4300d977e758cd18d0b6c275ad40510dfb282f446f0cc71e4af9c5d015fecdffe40a82441f4b6470c4ab3230103719220fe7702fcd82c136b8f81da6cc00dac78da241ec49bedaa086a37dcb29b351da91970483316dd54bdf9dfe326f1b4797d2d43838e2b68b470c1c0c2005ed6b3897c91811f106baccb68a96b0579d1020e69d6dccb84c1a8198650a99ce62986e8c4abc4f2d1492aef204a0680615ed243ecbba4671178ad80a4d406803008485af70049dbd6678e31528504a1cc83ab4bf0c15f10fef0195cdc65b0796eda95f4467e613631f6dee8242f39c42936311c83e94456bd00c1cb3a40d0863517ca1c503b27af2ceb5817027e0643bd0eb3ce9ccde0026a2abcfa8883acad64ac2490e035305132899e74e8e0fd356cb973258ec6a9d20969beb71cb12bbf9a8b45e46336aa1dff05349f519cbb004abf6041071543dacdfb52a2bff221f791d2f7fac1ab4f6dc9e800eccb79e6ee870944e4770281d654f1d916bf8045bc022931c3fbea188388df68030a7a03600979dd122217479da23680a365955d2a5dcc319d5a6155e94d3a7081943e60f5657449c8858d9522d6563713ae5b53608ffafefe15cd800480d005d1729901651ba316fc648ae187353f3b1674c8d0a16cf9b3a9da150b19d3d4e132572f0248609b026657825a4723eff097dbdc25061315a6db496a69ee832d716361fb55d267e7d1388367599ca4c6094c941acf3259072ed99267bd4c453236a9e68923796cd8b500b96788e1ffb8022bdda9d4d9377b200d6e192ae20f2688fae41c3af9e60173d1e25ac918a4a9af39c00395fb5363514bc4cdee2b03346a50fde35addc046b924e447fb03a97bc685561a2ed2ceaae49a84a97bffe8b5fda8369406b85d0e1c571d144cfb9027622c0cf31180e3400686210ec013300e22514c1deff2d6164b1655d2dab6141bf45a918f8d70017c3b8b23ec00bb545b050212d509f191aaf38175a2c28cc0116f51329c1a9e088c05f38f13d6f116368dfdf0807b62df3feed77c724244c1e6c999db4b4ea321172fbbd1408a2243b6e2d8c1a858475cda2b3c406cddd460a2e54a357c2130ea821e30a5a3022623342fc831ec75928ce929c3751b06ce70f83790bde707eb7c563754afd56ed373276d68e6663dba01147e5e10cf683b28cd5569bddb4a3daa31ab65d713d927acef74a482d9ae19a59ce143c9a930b153e0c4e7dbc01444037cbedfa538e7dca60f5d8c87df4b32fbe45ef3a68daaa932fb3094740181dba5e0e4cd6ef4adb10d93914df91d1bf3e9b0e8b49e572483703300518454c8cd5dad9f806e801e08c7fcc10e8878844d245fd49c0e2550cb5b8378faf6961c7488e12bad123b82062f5d813690eb736af4b84507c2c80d5b67f9a77fe0664d2f954c1aba3e4072cf7c3ef13a6212c429763c627ff69aaae934218d970b3e6117f3495483eae37749a683299e2b4e5188c753d4bdcbd9963d6049e066e5e93ded8578fcbc7f2eaaf0f22ddc9a91bf156f28d5cc6f4eeac00475f6027399975289adc0b98aab67dcfb30ca701ea6a0ceb5058f350760cffec6bde40130993e76dd4913e78c291c8c30fc2ec58cd989623aba26bc9f244f3435e210ffbf27e1b92f8c115d9f20d3fb402d42118646663ddac2d2b9b3c0021ca81992110b849931cd21b465e3d70089cb6365be296c971f4f1164031155c83d7c2109d27905af911b8da4293e8e69bd423488c1c7596544ac083834f7e916a4006497bde534634b30d8bbd07e51f43e951f5e0519fcc2bf37aaa94b10afa1f720d954493847b4fa172e5a3f5f1865ec9eb6cdb06e97b05db15bae3598fcc32d0423e110f65addab6a4d0f395b08a99993fa190d17910c74ff9134d58c014c19132a9692d8531442f75014c9eb89a6a7a0ebd31442c45753cc07f6b6fe4a93d8f374e51a98200da6d872d90b8488f06aef8a830eb2095662ab72650309d3680bd5861772ba2d9e725b6b7fab249a0fa84eb266b06a359522560ac2ebb0628ee354222fa19f12caa6693c99b40d1a1fc05d8e2d0c77ca8c05e23f99f857dd188fbdc240dd781b2c8cd3d04507d2d7d248e33b9ac6d9babfc01d3f70b90ee80ee10242d4e630d5716fc6f07a8d50765d2f85856162b4bfcd1fc83a8a4b5a77b1e3586b6f24130707cc5ce3a7a87fdb4ba8acbeebf5dbe8deee4b923e61cf585f893ebc301270487b905ad10ea7bbbf5fd87d5b16ea15431e87a4e93c3a3b20af2037f95ce45f08e33df681def54b788c6bc15260d260785c77d5b69b3ad2807f9b99375542291605e89d474c1357e0be673f39d5570b9717f8d8a3b35306af6164831b477180066a12b0d2cb71670cd286ad90d36b9bfdfe158a15c06bd161bd6e0000e35116821c370b8b2ebb637a834a755fa17afd114d9a49cc03461a7bb7cb002505e73279fea9009762a3b8ae204e634da38c5d5dee4081503a6e0d3b49b6fddf4132d31d0c20ce5e3c1179504c978e63799a07a8906aa7a9a12c6fd6ee2a75978c3e72382ed4fecdd0819b90ae33553d57529e697185b64a6caef50c5683647750bf503b10ca03596739f70d907839ee08b9a15ad600e7262e0c504b6acbc39c88f58aa688f9f6769fc5e195c5dc5cdb87bb5448250dc60b2c62e85b455616e17c1843bf0906422cafecb07a619a94f9b5141d675302356c71eb269f4704df3a6331eb8f3f70c84800650b2fad6b4d5e60bc090894ec953ed8a218b332c5c7cb48322fb84c9a0139f58082cc20f6a48bfe131c9b00210631a6dba13920042bf9ec059729500107b57e870dd69b0d433489400225ff989809c8fd59cd9b7c86b5bf52f3b32eb09db75bb6033dad56dc4a23021163e93c7ccd9a35248297e30a1e18328b0dffe207504292bede9be61402620b19b0594b020c6b957e27d2c09955c088e680c0d098279183130f3ffdd73877875e11537af950ea9a26057ab68c5902b085fbe866a4e138fd324104657493684db582fdc1aba0ba3880c74ad068b298775b1744dccc6181fa8b79a655fa805b1816f6da7998d45cd90d9013115b379442b5f3735a226ac0aad869c2f939bcbd3170095299dfca48c0001ba7701c1ac425a2864c1e117bbaf875c67f7f219f73f11349cf13b378ae61709b0d692953df4c96662095d756377f40fcebf253d3fcae27cb1bbd6e1747b55edd1a6df258b0fc8ec253167c1e253b1405ade813c9ed1e00899636686f392fc846ec7099e06ac263ff85b470bbb6567342f97433e1cde75364f7c015d5615a784bf2a1ed07488f04142b175d0fa385b6598aa234222b937f9c34d3ebe3c4ee0161b68753cae160b826cd8c2f197bf351cce0cc4d4a6636a319c268b401faae9fcc4072616dedc5781a384e13ef0be9194f0e87547b03f442086019cac1ea0b0a8d5a54cb33190f89c2e55b52d888b63c9fdebbcb0e432a588283e5e9e608acec7d7f4163d0690a68e59984b4b754a34ff874d83538a58783bc207ca1a1604f485340becb6d74687ea42e257d527058639ca8aee0ba65f6615622bbbcfaf972e2eff43a8fa39903d0ea67b9154bf335efe794eca4018c4e3dee7bb5db42933ae25d09ac49452035f9bd54c4d353fd649be8fff9a93400f51804c575f5f25252741698ab395b188b6752af642c7a9f7e681671fc000d60f0069ebc4afc60ba29d4f3a24efd3470f57bfa86cbdc8b49e981711e2d22ecb43c8aa99fe193ea4755f942448ad454b5fa399694a37cf4f348aa7eae18db56a32f54af7fbc8874a6fb7f9e9154b1ad8ea7ca6b270c4af953096d2933d170a178897101ada94b1a756591285948bb3b8487024bdde633d6c187fd1dc406193a552d492d13f23f278d4b05aadf11221bd64209e893ab28b8e3af402ff269dd9512b4161be558332c184748e83a52268a95992f879ab022e6d333e461424166284c1a8b92fdabf481b58b4663366cdb65102f326d4beb7dd4bf38019682053a8a0f918d201b7b9289266346efc5e016a2a619004573a8d0ff5cb6064a950d07343c2d2736b54fa763f2f3ce6354f676a6dd17454a3035b38eacec878a707cbb0b14908670011018e187a53bb4d4bc026bf4890b98615759b7759224d292678af8535095815ffc12c283dd042871156d7b64577f92f02368c23d8a43d42f4b8b090bfe0cdc293342d2e115fb289c7ee2a0c30b4b8fbe2457000899acdc378a4b79da3576be4577621ea52df34bfaa204de2b665436078e9e2f915fd7cb256978aa92db6c2224c87b07422bf48828875569615e441efe04bc4de11eb896061109ce785a8ffccf68399ba75759defb9a9808f3cc8d40409076191ca2c651a5e65fa8cb79fa8a8279c26693c06107f879fc8af4b86bfc6345f6af51cc39cb1aa70b0d5b8fd49adbbc51cf7c910c46ca4cfe124e45edac74b2b8b9e63e89f2afdc1e0c191c0b7d5302a730dc980b83929088043e0b1e788ffd1d03ff5223085472f35349b4145b3c1bf17c6e804c93291d01ae264658b60f97f34eb9523927ef04dea89f84f7715575cbe9a1f265a06cfc3dad70b896f58e94abec7afda5650d300985d1c016455b5693ff14b712558062b809e0df8e138acb36a18b07617cc4038894a759a0ce8e6f5cbcb71335001c70f43153711587970a80aac9e49eab077d849851a83b183e972ac0c364813b9e7b24f638efbe3d23dd977194f765cdf42736bbfb08be74ca7ae4f1e34a9b38f48ecc4272146bc1fc6aa81c7120265ff833110005ba7ebebfe134cedcc60d2451b7e7d0271bbd2088a1d9b690c6026d1632413c850949d7281acfee900cf6e998fd8e39490706c9592cc0979af72077e3a1f4f600fde528cd1fd5ffbebe0ea140e412fd87a2677c814d470a6a1341730cc729f50738797c45dd3f8d767a303f6a3866da195254e06e4dd232ffad3b4f358e1cfdd55be45d25a58deeb07a6cce503a5cdc7dbb53370efdd2b2573a92f1965a675b695618636156f26531588c6e64e27c2acd069d4fcb93592634c2ddb191cf60c41dea703cd9b6402b2a53b91325f27e56f8ee20ea17d05982cd9056de50eea4a22d05b57ccbeb85cb4fbf4110db4753fff240165a5f358b4be85e745ecad1dbb427b4e8b844d3c418d13b44ddca06b1f199970aad9fdbf7ffdfa2d79b8c3a338ae406b1ef8e99320f7b1030a37339bb767bfb56b01e948f99261ceb42befacad6ec15513afeab79595442f3fe8ed56e7e30ccf288fa2dd9e653a3bc7617164a35f214870756b8eb2d3434a9fcb6abc8d0b85c5afd982169d1127a18d0df713ad2d99d955ec6cfee76757699b398bc25260431d8f3cb300c136e3ea43dfa594b8c6bf43641631e04399cb69a0a41bc01d18338ac4d1cdd73179b2b022c7d4d2471b558a6098490ab8653d96faa4a1e875e1f4f42beb4da2b6fe0e8c5da7f22a1c86095583642e3948641a6aa86cb24b846cad4ddbdd8135e309168b87b21e73509263265d972ed340d5886523ff8f39937a4d2ae31950f5bb4fe9667ab245195f482c01a8885f367edc8f78a8c6a08a4e62549ef01f3357812a0e762cf1a9b2f80104bd9e6e25c3ca6b0640db81eb2663be210b0b5e3718f490be03d92a1f19ebed7923aecf2c776dcc1685167e94089f9a228bb100fd94367a977be51c5f3d0062880005ecc90291343a6975f480c34717a1e2d1da9793904a04611b2f45c25bf2fbedbcc41d9c37c750d39c134121dbe7b77f6a4827489656d87722e98ddea20616104cf1f540bc19f410ea079e327acf66ec4a1accfde08337fd3a200e3d6bd29d275ac8a745d0a16e9aa5f3a1963c3051298684fd60295247f58a39feb8e714656ed05ed08428a95ddd3ea74af465864384fc4e3ac78e62c63a1b4162f177c9270efd73e17aa7a50cde1ea25f5372fca73e1f2c0dcf371363ece88c719f1382b1e70c6294e900580470f7ad82309ca6d811946e8b85d3b1d1c03cc8a8a40af0507be88085660731292c3f51dcc3dafbb1d51a97bb847dde2faca3e3c63da96123da99dbadb32a7620d9ee9fec3e46c4aa10c15a8806f56972d62cd2f91920fb2ace20ea27f51245436a370fb72f2635819611a7779d73dbf4baf5ed68869e55222bb47b744ac6c4d9e34002c2320a6a2b88852a8474c969b8112678b0edff073ca65828784295093e72e44ebeedfd2f082e9d2e4557c6ec938815ee960d725fc6352a45f53eec3c6d57f55a4c12eaf52af4e23d18e7ec5159e5a36a2acb6cf1a507e848ee69d55959641390ceaa80ceef697546f230ee535f9e1860ad93b45335dac3495ff3f9ca8934c8542876bbb5122486ce73d0da679d1ef3b68e72f4d71c3fd4ed0d1304507a684da1b1087a62884508066994b4c8fe2948c139d69487c26a466b984b4fe2574da7041b6ca79cd3a21b5c8c75253f5963fc70c0d9c395ad3f8096105387487788964a64b0663ba1c2c18b4e6972e4fe5edb27d0086bfe2d5e9b934cb1ff7b45112750bb54647ad53fdb3ecc7174f73f90200982e72a568c82c06ad80d881f6f249b9739e1bc8797af28200c4445d82052d01753f7767a2fe5373e410e6203ef14809c614f70861b05d65a98159052eb74153c825d2eb029c40c9bfc404dece1c34768e4f7c5c60ea22ca170fccfc3c8aacd0bff1344fd98bbeec3e65e2fb59dd4304cb446e6d448a9c9b77622e03b7de3375fe0cd519cc081e5277afb36e86884a2315b10b33cff648d9ec85b1fba6f4a23b0cea80ff3322ddb08d71c0b50676f80ec6eb0c923e3b30db30e439ea261ddcf824f54c5101c09af326d638154d701d00a60527efffdeb2756ad0529866141707c0833b1c791f24ee93f9c0e7242a88f920bd18a29ff1cf582c10d7639d37fd2de4563e40bbb39bfd1a4483eca52ddaea71cd45be33b6319d362c6d99daa62807da434b84d1a2847519edb899903c4a102ba31a01a980671b8059897e6e81b01c5b46252b678d8edba0d376d1bfb0fb5b6cf3c362673a2d563a26f251da757cd04f2f04664b5b2d61866390d12bdba0a5e596dd1a8974e7729c326f17008bead2ea1a1d5b4db0c8a294c36c0e2309749ede1c53b0cd9133ff0cca8a3b73a5f280706cb648064477d51b467eb7f9f467af5914680c60aadd5dd4cbeeb1db51c5831b63518e9a848f07de18758624e0c43197fd084c1c3ddaaae38aa98a3fd7c6ce60e8300b24761d2d8216c54e2580c2f0ce4044a7d171e22127f96237f695d0086ec2382972bbb616bb42577f5c41cb643ccaef74b00dd0cee38062f0b1957c92f7b2db8193a3d7b94c028e431ee827ef98a464128ea3f7d4bbcfae00de165b70ea1ebfa9a55c6840ca2da80e7e5b352735e0e40ee580d9a8cc985ef238fc217557f059154504abe78fc82c00d811035aecacc1d0ea9f92ffb9f2aeb997a19473d3105705f7bb87f6c9885aa5c1b22c0b9745c91c9f4911f50189061c7143a8c84e355eef3515f3df1e431bac05ee15e022e6d50e29c6f4ffdf425221250f9a0f7f6f963f376c40b00e13616df96872bacf5702784193eff17444d76bc8b4cc08ee7fe7e3fabc5fcad10d5f83c21d7d6bffe707b9e68ff0441f0ede3782b5d6810ad3ae349730f514e6f2cc18f10be2f9d06d2ffec9c4680c1f4c743cecc94651b510adacbd13bea816c0fca4f4c38daead362af2a363bb5932f6d4309cf71e12b3825a793baf57057b9bb771dc4e82a3509d6efce576ef71821f5ef6bf8797f7e201621ce62f9f5b49d935c24c2fc6bf09deb6198a945069aa7198e530c7f87f08496a3be6a83205e9f1107ff44fd343e44bf0b726e9c2c70f1dabc20acbdfe5f5644f575425af2b0b79cb22049db900f326ed256e6ff093a37c0bd8f8e7f7cff9d6a4ae0748d65a341c8426ffff8d019d02e762b9c4299c352bd25a3ad578c7a809c66d076fbb045cf8fae91a775268f298eff73d1d73e911954e8ec67418314eb1156c1213a5b0ae061e369d643bb19791952d74b13dcc7e4e2f2d2a0f1e636ea02151bb88490ac16982291a0fc007f084200d5b0b36b9af75c2e1b9c86877ae0c9fd0ce9614a0f3e3de343e42f935aebf0522e84533374eddd8c61ad888ac20ec26dfc4dccdc7e91cd2471a67a8f932eb1528a71c819e1eec3c588afcc0197d887b950f75bf08d16743d99e062b505d0f88d6ed2ad1fb9aee5374d4b9944a8d2cc453c04d71ac311c1b936f3c1b8c4213b9734bb5a23b8d2ec7ee7f9f752498188c3596d8a33b74ecd6b1933fb8094c8ae6323170695eea9b8490db1c080f7600828103415b8234fddcf22a85b160b9dee4b90978a314e095f799276cb7125ceee508afeda9d81a7d38c288effde3ff9e17f86d1c68f09cb88afd37eb122165d11eaa6c61d386d3b077c2f78eba48c0e3969944082b3f52991054b2f8af9c855e9e306c46658f7ac16e723e6bfe0fd7eb6954b386fc68170106ee41c49332012bb7615fde84938dcf0af4d3e98a51e01a3631a0a5076c685bf78b760379ad918c92fdf80a27a30d9eecc8091b8de62f113680df5b3a085402e51818bc7d9146c04d3e790d0a5bed42e36581be69276ef9e3c5e6b3e0b2376b031d1b6441f8c58ceb99e4a4cdd5bd387fd04a6f112cff9daad0a5a06e067f598f1cf0c9d105f64e87b3508c65ad2d2bdfaa18b28a375cc06dbb7d2e844dacd7264f738933ffcc4f8ed13613e0b957087f02a0ef1a656be427264602302917474d4f117f355d64c6a1b74570c58475acb3e98450af420474e6dc6da38607b974187945b6576a1d96712a0586b3284d4b31b338ad4ba1b9411b97d17c489e7a8161a1cde68e0b0bcc5cb94f2a79f74fdc92dc9c8e97d04699b80dd05a507114ea26954975e3a835efcf8539ea36058451959266f165f9db4754601055dbd21972ab616248e020644be60a09c71d2dc321f3c4abdc807ea86b544c3566ee639d16173c00e055495a6b2dc415c14dc5b255d8f41583c14a4e30748443782e54ba13cbcfc150e8cded42c5c82d668d180b9256e71e79db5aba5170e9d810054cec8dba55ab35cab631387257f996964d7cc356f411bad5dd37b0e8651db9133802815a18b5d9e14f23be35d8b7ad594991d8ecbe9f76fb4294bd582f340feff94b19c1d34f90b3abf92807d86aa80b32e8293340c4c69a611a196c7562de66cacae3b759725b3fcade8cfa40a88de0cc7636aa69de7a7584bc3e85c81b022668317b9d4d5055a8698caf43b3548725effb2504dd9b454b4fe80f91c5ebf4129c9949f6ea9242bdb4c303a77b22fd38ec66885f77285b8ba60bd09eb91a2571118f476228ee2a2dd9618ac0b00f96d2dd1024a0d259a8c4430c5968f193d705a7e50af490e7484e6db187c09b61bc8d8869f33ca6e9933babe4c720db90b7dadf27fec5095061c0705b5ae4b00c4b1751e8dbcdc1f01070ab7902ae818e5046c5bc8a1736210380904401bc0fba82b46f9845635ec726cdd5068f02f5449af023d95c73c57c82737758363490e042e89431bc4e6270865454582dbdd4d78968b87cedfaedf4d5d2bfcfac1026f98a976e8b447f499b51f7cd7319b30eb326c5c355c3925da23e6490c9bd642ba9bfb4b60d965edd75398d2de1c16cc5e6b7812cb615c208b1a2c440b83e128833074b7528d063a1207edb6f77e7a9312710e1c7d73dc7e5875878d940072b8c59ccdca88f04cb14bc7048250c4991d519a7f56223fca912e6bc678e9c34d60c9681adbb5ec65d469725c3842331ce1fe32e57678271f93da8c56648184e39b068d30fa0d391c0b980b92aaf00d249364493c7998381ca6936c25c2b00320422cc01e210caf9801414601a4f338bfec0a2101e78080ca64299bf3c41e9de314d1a7811136b21a87e4f56d9f0c48ab43bb391a1696dc6fc3295912fc792bb0c09b033f0dea524e1415a99171389a10dbe67d44097bb85f7533c2e068b5abf44aaf5797647982d027f8c2c5ab8f03b07843177bd5c6854b94845ba6bd3494ec7c98df1735a5ad52d1199b278eef4daffef8f0781bc562c784f5f380898b801c2fc1b5379319adaa4eb7d72ffd6ccf77ef817a27d00821f0fc96ed008c52a06f685aee090bb0f2259a9d4982ac08a0d44d11568fc8aadc642e222388be7704490f56bdf05ee305c9f606fa1cf8fb87407f6d7b69d9e8db52ba449923eed30836406f1c31b68921f88aa957c6801de9afd4fa4167b79669a816e9d80792905d2031ab6edf949227f6152fea4eb42b0d6b1babaf8854f58215e246e592fbc6318a981662bb58b8bde1fcaf2baed7f66fbc9b0a2b1bfd86d3e01d90e1fd95f211dfe4c210b335279e6007ab61b2b7309a69a5f3f744da9ad2252cf58dec697e89014dc25e48ea128700089c1470048ce087f801877bf831e3a1418c3e7d119cadf0c9f63bbe4ab789962ce55ad91f9bd5a409a8f7f2a952a64c77b3fae6bc0cc0f77530e92a433ea12a8162d668aba0044675c4c03e792867770482f8cdeab00a4d58bbb1541b1e52ecb42dc715aa5fb148115dd77c3de9056de4d66394f5f41e69fada73d3905b72a810d79081135532e3f932e9b811a643fe54d9cafb237c46b348733c19332b6ac08d9e1951f8ad916c1cef237b3491fd9947449abaa5d15d7a1c559f6f3695b276d929d107b37584887a30b84e095c4edc9086c36a591dec03fd186377bdcc6f29575d1f4bb27c0819fa20722f43d22eac0f6ea18d7b1fb60deee0e75325fffc2e0f4412d2b06ca7f6e3cd185c500eabf479707e3e5824b19d99374feaeeede0d5925123d3c08f6f1ddb4e37940b85316c9c81557e9187eff6ad4fc2fe555b2174ce9f496ac8b16c9bae94b6fc917232132d16decf7745c4e1bf6991d590dea1d3e93aca58a9f8c95b7beca69f60970d3d11857ae510d938b6808beab49ca32f4ce40dafd562b66eb3b0c6fd72adeb524a0713b2bae42ca68a219941e30bc13819e167987e1218779f7c5eedfc747922100584ec2a1d05675e1610d29c1d4b8ebc736b5140adcee869c8c98f84baa7db2042aa046df97b6349851ecd61b5abc9210b344c650aa1ee2eb3bb8bf478cff69da77c804181463df5879d3ef1272e6a3f3a003190abce938226f822ec7de7f63b633051c7aa8c99552f4625856864bc3e846638c43762ed9f84ce4be10c167995e20943ff379b15c9ce162fffd009025370e36b6c7c68568b40ccf45122370c5388393a1f0fdfded462d3e1c8cc414140b72a901794236490fba880e0c937a1c6e77f1fb632609baacc9a87b8900981ab98eca16a10e5e2f5d6d078281bbb3f5047f5bc564490421253584567720e54efeada06df030fdff822d9fc14a3d5d81ba04a3df9bd36b37e96d7798e8b2343cde3073633c9cd431078ac26e018a7ca797c75441f247ba18d497088705e6d4696276b50bad25438a3ced0f9a7a4910ab12d5be7c1b6abb9d2c39709cd5c41c542c26da50a1505ba994b8ba01ba21f1ee64e8bc41b54d4817e015d6ae5a76ce902ecffb98211cb63b3b1011a11039fbb82b2c03bfbaa51dd8725caa818bd9d4d9cb6b4d19198785c3aedaa98d7780efa8e404424e44297f549e72d96f7839832e1a4402dc90a00d6457354781edb3aaa0517bcd0e0310c53d510ddc5816c30fa9f581a3ac9ede0a1403e2e693f4a7e08d7ca899449311d87821227598276804f2736788c03631af5bb0629ac1a070e4006109199419cf329da2a40cba7a7483051719bf1237e2ecc6afc5e20e73b55a7328897209ede00cbc79027e15abadda2269b805990276959cdf453fc230606db00ee5c9451d21ba83b2671243b8d2788a9c1238e495a9f6873634618b3c36796b86f8dd052faed41c10a2d064280b76051b7fcebd896fa072b4966eb9a1e28306f027c1627c861b906f5caec3d7dd2c93f5a44836fa164ab0d1b75d026e039af332e36635e61e35c5e8edc061e5a9a93e7d13abcf3fa5eaf330e4d31695b77c8ad2e225578d282fc5e07411d09f014d81c50f521a81842040e02665190699aedcb37d29b0a21fa8707325e6100f65b56ab72cea59b59ed1539bbb00c3696c9d48639f58228ed50f94241b19f3967396810e5f6177b4ba1d5fc8f7d2599dfea3c6a8fc265521e16dda40b1db3c1a0c827ed2e21481139ed459fa3c989096824f083cacf31b83bbfb3aeebad1e7a24334a790c79066dc62cb4e1db3cc134d4dc6f46a768b28a283de61ee7b6d162211e3a64fbab8e4b600de123525320a66a0f2fc5ff100397134d5a5a02b256156721d7ab98c33ec3b9331611ed3379c37c10fc9eec428e541746088806dc9df1b03d751d5c75756b3e7f6289a76f2f3332ac57c1a8ea36090db58845635516e1daad2ea545e30800878522303dd880294379e5d3797d49a313371aa1615794e906f6b05219e3f3a07261450355e13d3f22c64af29ce12bd86ddc25b17068cf83a7956ea538a80e0e162c2e6b0593750fec4e9d22ef02f33c10e009240a5d2820fd09dd743773208592f9c475252e036bf7d0965864b0b111553270cb8d9f13e4d9a641d8d37219a3b8f3a9ce623a8a02b80a0985e26f76426d58b022ff22c32b305d59702f91f96efebe304af43a093d26202a5912c1f42d94c6d25dc7d8e856a4eef965024de64aab6267059dfe186194c442e88bbc1383d53de3211a2eaf6450278db920b91de588090356364d96b2143eab48bbf0c78348f22d164b252222a4f113e17110ebb5c58c688cf0942a8488fc31db04aa23d28a002351beb0e2bdb56c34af427e6bbd725182ef8acd52262efe0caf0951a3185495858a3b066313e33341778dc5fddde8ff4e2c3f7b4aa34f64fb1d396d45ca34f5b8f2116e6e8528e0ccdceda7cad9c04a2194f22cf0e6ed1c310a69b45a73001c0cbced440a9c84fa524bfa18f58a24562128bf1280e5348724396662268ce896d383c854df9cee982bf7598cd3efb77dba68b4bfb9a7a1f866d8907ed07c9315d82c3719699fdee81d45c4bd1710e5a7f0fdbde2380772a910f3db141d45088d83b4797deae9eda20354e21b6100af7844efb79a857333d82a21b0a1bed48a2007438cd23685d56e06a193c4a3144cb431769002f6c2b63a31f3ba7d090ebedd6d73a55f2f2c7ff66fe71ca29b83ff4cba94641334c482b5cc894f92d9e693183c214c62177ab34517f5663744d4a240df3f103387ec837fe8f1ef261094a828333e66c7a51ca546034b4d17925b14b5da856509788cc79cd8891a600a6cba624d0ea7908a481a14063cce6f11ebc17d32d40514154d8a2a29c08bf191eb92c352bae5a529220cfc7accabdbdeb27fa06477d16acaf1b91a268c69a2c0e6c5036da692f81378c97a69115dd3c4783f3183532e17d9aacb52abd428a9bb60113b194169086f79863b08d160eeeda111c2acb9f87e3d27543c381e5157aa559aeb4913d0c4339246baf78413c8a4872c39fab1c206c34aaa5e5580b02066f53929cc0238592f606ffb41a1202ab931758b66e01f6ce1aeed113cf4ef8550f6b89a43e63b20b9ac5737dc4130ad9ce151b6bfcd64e4a609321f6b957ecabe76473cbab4e55924960df114224d43c1f473ac5e44e575f7aff175ae406ba754f3c1a7ef96716c8aeabde4fd22ef6b96997c25ff55ee3761c2bd2591eceab373fc2528f45a9de811b3074eef3ed3f51846e7c31bd81e47e509565b997a4371f9531703ca7d322a48190be678553c234ca8360bccc595ad7199d1456e1ecc0bad4f2b9b490debb5ce55575d9c0632ed608f272ef4cef72f26de3b2ea19abfbdc9463df2afe939bdb8450de0b4a36bb8e8c88cd33acdca0dd1f3514298c48d5185b222623f13d04a2232194817efffa3ba0d9ea346c2b373cf8a6d8f78f2ce25f47daeec655bf783d7bece00b57d25bad6381a7112296e66bbcea485a18d1b0fd80efb4b3772796c449fb020e433cdea93d61aef3e97e85bf2134338380e6280ee8ed2a17eef154d8f8815d9265722f23064121143466a06784d18041a55836cbcb3ff2945d12c85098f557de54f751edb7c156bd882605c4f1d696920f684593071370c925492d95eb9e12be8c04aef57d33804b73f3295a12bb94e6494d4a9c6ac471db0b0d2361eebe1b00c52c73799a3cc094b731ec7c1604bf891a378f64fbba521b00f392005adec691765ea78f44cb2db10ac6ec67fa056ddb8d9c75960f06b3ea69405f61fe4a59ff0f9974621922b048d0f89a4d3b0f8a33bbbe82a9445968be6513680edf4a3eaf6c2e005fd5ed6f558c22abbfe038e5fb50edd2d0b601af926c925ba564b730c7c184319c0e30aa190e832dd74f720491d33cf6c3395fd4335208b56e4113db4e5e0575c14c9ea749ad7e19931da27f558f0ef108eed232e12fbdbe292ab0d83bde5e12c269c00acaacbade83afbf7a84704bfa9ad8d4ca51ea29e78f660de22a15d061100cdc8abaef8fe41e9c6d1c74b8b748d72200c724d3693c9d4e1d88394adc9d0a14e42c9c37d859f5efcab5c5ce8f0f5bdd733c470dce15f7bb226045c04a3d3bc9584ba3262595cdc603ba4ac3d952fade45bdd8c43c97238514cca28849da073373620d74f8a6fdbe8a22e944e08555c5421b6c789d094d41811eb84447ebd170d6b5388afefc18cdc6ba0bc54d28e17473e5a5fd583a04cb1b265f8984d50288e064ad69c0e1f05e8b7bc95949696b942e861d80b3ddc8e2b28b825a4d6fedf1d2cbdef27656a0189447ffb657fb82cc606290645aabb7d19764195cfc99b611e616c72f8b0172390e0772957c50f3939ddb9a523f630c501a435a2409e52616dd6b8fdae06d41bd433f3e3359b3240154f3a970d46040599ba8c5ba1ed4fef2347855a3eb570c8b8ee1acc047ebc179586213e04ea4c1895acee6c6402464d0d90b1dc9138608f4ce37d7bad73ad7ae94c49267e2c06bb05f596d683cb1ce91fbf976bcd1355270262ea5ca7debbbf20be235fd371035d49a2007889aede2b47e62af2ce44bb814e33f1992ea82325ff317aee521fa9c38ca3315c76f7bad4f46d5676f670dc25ee109151747191af6b4ed62a25daaae7c089d425066e4a917d73bdb03751367c57675b2df46cb3da11fbf68f311d16cbf87790c0859b1b946d8036c18b40806f5d3264f6e510b57b362bea0c12051c881dc5ce32eed73410c4afb8281d470c0be33de351cddaf3b9d8a85064c11d40fa60fdc63741b4cdec1c8cc11590e341a9e571efd4f889900baed207deceb9f50415c7bb6927dca46f450adbca0bfb5dd532288059dec1ba606efee9341b5ed0009d9a4c69fd68d33ce35f42a10a97c28485290b34444341dfbcb6c784643ebefb2748118245eeb45d6707cb467528008e83c9aa5e4db5beb8e3dcdc4ef780c3ff62b7e93dc2fd7b5dd74e98b527ceca58a99e4a45b41ea7f04346b23367d846d870b6495a4b0e92900497a598a83425d42c1c33e0e14be3aaf88312ef44e04040c59e8319464a357cab4f4c4cffd134c993bd50cbebac1de7f79325380ed86314002806afe8779ffd33f4d079a956e3c2ef5a1b2eaf6c9254b1732712d7c8f706738b3c3e8974f85d1f32f6295d7c137d5bef853f8866b8d91e457b4005e2340ef25b0b5ddd91f7745c1a5099f105746b69e44edebab5edf17151a59720be6c693e1a2e11def734798e8993602ba79c8ba221187bc27737bbf7e691ddd28192a4536a56e50f8c231e780f712f48917e3cc11b1b8e55ea8bc34292637a860b8d7810463212f58eeb6cfe443f3f3a508905403bd606898db3c3b070ab9bd040ce5a47421a5692646068c7880ed561a294c3f7b258e79054d9d49b3350b7995d90a9188853c4dfe04e81dafe66d40f5c8bc9f52dabb604ed7cbe0855d9ba0d539d9acd0bffbe195623e2bd416e2892ee875eab1f7853d133037f28661d77c883ba2611ded25fcf7e712e4bd8db651a37c87b8cd45059b12a4339c8d355dec71ef2bf742f7e4a13e658aeff3da3e1548063e13eba6b2294c44188bd6e7bcf79556f31b2b81b94083e271cf587965b8f51d3a48b6609e1da4a892d2826e4987c3c0300305f5223dcc1b5b1723669e9c82711060a8c5405ce6f7e3a89e78feeb1bc4ed18b79aaf2ca77fb9c203e9185ddd7e4c1fe127789e28704778f4c004e6e5ce514d6953ec041ce200acc7f1f17d489458566942d6b89102ee86a90365c82a1d811f84384425be5e0582da39a4ced0769ae373a4ac2f78d84b06f9f4e074eaa8bb94ad62f24008e78a0ca9962588c514082c448bcbd3d0cfd5b72c00fc47e31d1032636c41144b74d59b03db504c1aec308b66034b20d05512a4873e0478486ab32638327969b8c3d833ba50105f4ab11fb5dd2df626d6a6db40cb0b13f0aef5496918471e9ae689243a64162c394afb8832321b80ee87dee275cae845f2839b48c127d7acc0ba4787c528d0956b9bf51bf0cc445ee20fdb589b47c596380f9bfe46b1d9b09bd2f17c3f25ebb0fda6a1801a74d7dc2c70057d4bb2af7afe1e19521e9a296fd95cfd663d0cd06f5bd314d4a77d66259387ef1daac75f0aa7fcffc5c629638bb6ac1f814d113be66e88e4c18fd4005eee6f99c43a861b492799a7ff7ba6e0c800f334042e5db97b40afd1f2176291c7560a1368321961e32b01bd623a06d1af6ec2ddc45845abd6750d62e9fdb3d56390930760352eeb78d7b337781339026fe732aad8c40c9c6150b1e923e1c2afc3184cd76fe88fd1fdaa3b83d49dbe491948f8bfd99c8cbe0ff5103ed4cfc71feaa978adee5f688b9881b930da5ae55fd122efe49d6c675247ba97b9ded5ea3363cd5fd582203969decfafb5582def59bd5bbdb26bb759cbaa8d597fd95434881aedf42668c0e3d5303bfa5ad4c86c37763a40c804a85b6c06537376c2b89b0139593829fe0de6333d73829e44d136456c3e46101a4a7892deeb94e9d12fa4a11371081ae8639aa0ce73256e309c7ddc8510591bbe5c67e46b674d45c91bcefc7d6f487785a7bd1e9e5f91d34c189bd913b16c5c3367e8997082b2dc34079f09db7dca7f2e1a1e7c026eecfe1290afa5f76a6d724f7ec0dbcdddee021960ec92f0f1c28ec386a22bd40cb9f0f794f2fce3b2904bcf82a5085c0fd2dce8af68c4517dfff85e183f0a836282ac6d2226fd5d9f65e9a401c478b182d307e07361072463bf030e207e9d66ea40ab3791e94a028daa33a64c412c8a30b395d39dafd475816a36f6d678c181a2fa677682d4e2c4b7e108c119d914408b1b8182d25adecc5f58a3a95377bee2661fad490941694d97a1db37635ad5db4de2ff8cf146a541672b895ea9a55c3db24e05035300f0f1141844d095e0330e3a6daaa312d86efc6b43a73b2d79c4a082195052325f075081e4194e069fbf90c02423fb3f872256dcacc68e85293e1e2929f934d815afa56be060961c485c7805c737140e6a3b0ce7d5b7befa3d91674a237dc08879f508ef175b4691d0531a8e4b41bdaea9f7cc4233b333759ac2bf8f7b5d555e4091214e21cc92a0e48edff4271eb42683d656773f4c55eda7a4a15851865493a1ff0ec1c8b41c08615661ca31410ebcf7fdd006ac270bcbcf8540b6a0dc0755976efe0539dee42cf3618411c1925dcfa4e1bef459d41d312b20ae6acc1ca4241a2e87045e0e3f086aed35c1314267d6c1e917b1e63700f41e3257861a5e42ae479cff385b55fe777a99b12e38ad870976a5b3160e9ca908b15d0a27f4ae20517d3860b5d6693ac25bca5131375c005fdefac5fed26684181a36e26f582a56021b52bb78b02619ddc48bb337af8840496380b25655de64b20d811d04ef510128ebecb4aa6d83f6cfecb9d7fa7cc720cc41f516ea2388ecd79000e00bdccd015964b5df11c5cf7c23a07953a3e8f8673e62190c392342b0c86ee780e2ebb7fcc28d3876d9976207bf91de0f86c05315899597b98adb9fd49eb25d1df212bce5b9e6ae6ec82e948979eb039982bfa19231fa10b67ffa352eee4449eebc30da6f42aa11c883dd37768978d3956ee0d8d07e8c9df8977446d626fffc03de8a338e8af76a75807dd769c5d51328a3df6a853ef6419315cc8871534143332b84de8a660890a30323c65f1ff6d2503804d3eb7c01ee42d23e3cc13f50c0bc4f4d40e6220913c57075ab0389b743f60131a5682c592407cd915bd51bc2371352bf466b2518df26a783da2dbe3c27437b1d564482b3306b4464b868de0a3608b0bf401ac35536160743a185e08f4445317f817529696d95e131c2055cdaa7b6141b6bc77ebd2fcfdf74c2981e4c1cb22762ff7a5b6ff82472d9664e38cb011dc5ab9fe64b6da096396796f201a357311fec13f3831f8903ca21abefa8519910ab7d95bd7402811faf12f93650e849eb331e73d24098bbd26f6523f46a7a1dde41d72bf8d70dbd717ce6af0dd22b0b5d6d32b2e9c314684db62504a315cc02e2901693b44ffc62352939ae804e1721d84efa0ea4781713c82f7b17e7c095e79e8d40541ed8698d4af4271bbeb9e976294b74930d93603e1644ac158709a31fd9c57577d500c8931f8702e90016f55f03cb658a1e6165e4c6fd56f7701e487eb6db89aeabe32369119cd417d20f595f11eb519bb8787df5e17f9ac35d87f1971c7e5ba7978a3c44ab7fe6fc30a5d76c3f848906b7cebcb81628b60d61b0cd8daa7dd62e6e9d359a6d8110ff2c3f302a802c7b93448390192f948f7fb708c15e7f0d2afa6190438b2a40250e51169f9bd64c27faf2097325384192039e39977eb6d52f321273b4a0c04a2d5c0de20b295c5266c75767a76f9e16901319b9aca99fec6ebf7635e06f53f4d46d4eed6e9a2d783deb29c7bec98f8a7d9e77a08bc4fcd54c9f21fde92f3830d69a1c59a0159dc4b6a874341067f2f46b8423f26d23ca18fa209031435ffe5d64352c03f4b1acbc059da22eafd525ee5a1628f56da99a9c83a81aa22f84a865592961c9c38691fb9f112dbd9b354fbd478863010ee74c035db5de0b55800503a99f4d8ac8648f80c03196475537779c7314cf0cc806f40c61e877c6497f9f493b46ca7379ec55c99e89636a3e7265260b0b77395ee04ccdef5d8768b995d84bf492d84bf492d84bf492d848f492d848acb779a210f64dac53c83b956795eb2627c5f7b926e81b750a0aa59f687f424cf5317cd09c1c15dfc7bf6302acb7e7f9400736c78d75d0dc875c2d28b65deff7e1cf7e2e60b8716e719073e7ee6307bad9fe0235c9a58e4f45eff935a07463f98bfa15929ffa97c50c90867f9841307927e911e3941a5ef4af36b34ce06b3a429932202652cfadfef370832ca7b5d62255b25051032dc8c07523664c000a3a43cad7906a61e4dab3047018146a3fddd07f3ade063e94b1654518eb7fdf8f758435a5610cb29a4df61973077d3eb2801a19fc35042d1666565239f18c4e195d73680ff018974ba8ace4fa395d134c7f9cb747b7544773f60213b3f7e3d152c470986840a5eb6dec37e60a244d4014729984c2491abdaf4e509c0a590112d3a69bca963464f1a9efb5eb393fc237396c71f996b569f36b4e5a485a8047809d95a8d93355dcd9b43f7b4ccf6f342d1759d448c7a839566aafc9849f5c8b80abb99271511686e068713fdc213b424d4c5680bdc0089ebd3d298e4933b2d6b92ed7bf6663e3078291c2accf64700a7f3f1e3d2bdc4ab023601694158ef7eb5b63f785388b7b3f542edf819e8a50f9e0ef555c02fc0cab3d01ab4ed0e70eab052935a9b60be4040b0435595a6a9697b99b0057c2e5e0c11da07a856fbe234b2b6c11485f2395440b28a3690caa76309938c9c2eb033915d7bfeca99018705617a87986fd526d4da02157d6f54b140ed9332822206fc6832e5cfa4d0bb2548d311da595a2fb124b1e80ff13a037679d910cb3270a038205d4634f9ec8878601dd081c248a74aa2df7a33145ab280480040b653fa4f48814541487848985eed16aca7b1c61c2f708e0490b51db5f09604f444660d999fa6bf2c0845eeb017cde83eb3663bec8d40debfab26b52f5fb6ca73876b22b8dc5bdbe33bf6507c6d24d1c232150b65da69766666d5b74d38778e6db8ecf34e7b7a492512c0d4fb27af182a31d30ef65419c7108da66bd42b7ee0b000cb4e2b234e4ba9e66d1be861c72e80cd9719153652b6f5614c7186603b3d61a65b6929d94ac62b036d003824d0fed1274e4e474d8719a01859d574f4887c8f7600270564ca427d348ac5bfdbf1b937fa1a7216e6f16e1dd841db708c9c58f2d98ef77955ca41bf2eb09af6a7ae0a718730b9dfd9bc2d03937820d7b1f6ad20fe21f008e5ed9956246aca42d78383ee067cbdd8a6070284a7d59fb9b22add40f231b61fa9152dc194d6c2ba679034744b7abcce04da180a1caf806c049fa0c91dea5290e9b2ac0254bd321835f9b9c1db77302359df4d07274b9528f49527c56ec42e9d8474a7d20be53d0cae02c7b3287a159043e4554287201bdf5439ba0252ed0cdc7fe3ecc965a6d88825b452887529315cd1cbc9e246d752b655e8877d4b90e2a1f4450f8859d03852e10d8a5a34ac55bf4b365b155af279610488665df3f8486c9d79a74cefcd7dd45105cb045dcb7f5b06e3caeb06761610a548849da953c6ff47a52001234d6c04c170a1b5ff962b06713aeb68c26d0cf0c275e3d803d35e495d2b48c34d5b802cebc3859686480e824ef4398f490998843312f96ccd858078cae8e3e859acc013e3941172fe615761c4fb26b32a34dc78da5f13d2146f5a4e073e588f7732e21f0820aa05358816a82b100fb489182b3bb450e0b2390eb8c2fe9cccf8992b1fd89a5098ad5961f11f16b5298f81bac6e0bf2719dedac559f0ff9668b225b521a58f40a934c853912bff562671b9cb70d6d4fd0bf1077616662cea21760ede8168b2019ab77b1b6d123cdcb4217db9a0193b7a08e84a190c572423217976515e015eecc9fa2bc358d9e3954480aab0a750c9749c51374cd042d66a6c6360b9dd698204781a820293b80bd95306441669429c4c976333b581b7c8a00d836cf54d1299fe225c6cc4a5c349ef2d4196aac44f2a319f2cd0494b6b025596c53e28ba0de365dd7448a5efd290b7045d8007e4f865ace65edd791235f68372fe5f203988cba25671109b9d5fbcfcab853d420b80a692e176856d63ed69b596c2aa0ded7f60c60cc6fb5e2ed2f8a5ee8962f8014004640555a2710f7d2318c35f392590bea7972065e0674371ac81822a95e7d224caf90cf80712ccffcceb0b5b7206833cb452b2c118d533e5ee797cd8e1d1440c62b133e55df344c1e24daadce881d3735934f35c0a99a050aa5199c05b991cf2c5ac37511848eb804d085f4623ac256dbe3c977d5391404d61d99ad07f41c31e3fbbac1452e4da2e5904971e5bc590754af7e8797d2263749d4245c803660a9725bf201a657e184e0325e24d787af6d933ba16e917174bc1312b07df86d5acf037d3f4e918169696f958f6ca0abb49dad5c2b95a628e3fbcf99235b4b06470ed890fe8b9be5051214775580b0cee6a5757ed9169a9aa72ab51c6885355a267df8d682bec822a9cad1eca6405e313590469d4fb0efdcd710c2e2bd6e13906c7b4c60b019f9191175d6e0c41be7e509b45db0c325bcfa02b38405b8c6659df26e7ceeef533a6c20f56a0dd3035545c4a754b371072ba1124f3030dbd5e169748dd6e5e6afb846edd73c315108a3575d3b79235ccc0dc32fd4527a39278d240b653eca55e6e4a281a7bddec8fbb0a591b595b5300c097d8b73b9b8781ba602f79dd182eacded257f46f3c526c85c071ff82908d6b886b7fa33d5f5b558c8d46971753df8bd67204cf6069adee61045cd2dcb37a0518b2abfbc26e0c2e6d1c7fa7bd27d1a21c4e1bc0aa9d887d7cf5b0ad93b07da229baab0892b879b5ceacbec0f1509480a68193edf79e67410d675bd5321b0e2d50896ad342246ccf37946dabb08398dfa6ff3e0343295e02d11a57252fde13bd7de745a4bd042cbd8ecc1b9195b7ae69a1533b077ff168353289b8aafca1232d0a336791b6b5912aeff631f882bf12ada3d35847e710ca88282748a74b9bde6048381fdef95d6f44a2444c6bfd1f04bf3d8790b4982df8d21bd0d0c30939741d4ec9d65f30ef0d9260a3018b33e1076819ecadac4baed20cedc687337d1ad3538dd7bb23f9fa15d31f24f633c7ddd52787e200bb8a995395ff0f2cfa18e698b9573c79d15a0204fdc458098191827d9511f8f25cc3d708e6d0647dd56ea6b4925316999eb14f0d1e6eb8a3d6c4d307dd0b9a34b5fd80a76dad83f9ac1d9808554a7524da645f516653f6e1c366e281a3945db89e839ad3e70a2f21f6928674c54eae507dc3560296cd1782da0135f615320d5d3a411295df7f7bf95e2fe2d8f55ead3c30e0fd4b9b9e4c60e08c41eac92d11a9a08457d79bd9863b8bbdfd034139615da54908198d3e10f98260af71137aaaef18bfdefd516d92142d82310bc66c4214fd6adbc571814ab266e1b699d50f0d141d59554833b3f872e82bb43aabb62c628f38f8fdb114809eaa617b1b786952e11f172590539d65e0124000d7e7290d3c6188003c3bce0567f98038181d77cc0121639fc4a3fada0a526f0913f47880454819d288a803297120118148a6334981340291cc12d9400a5a0e2dec3ff3dd25cc2c58ec23394c665396a0bbbc215f36903618b2102cd8abd8a61d5cd4b9ee6c96dee3570602afe76d4540536d41394cb76f32e311c82cebdf2f6e95d5defacc1c48af11d3e8e8367733ca3c3c944ed5816194955422a5d039e17cb9a2e621f008041d19e977a96a2e945e98d34c06d96674b084a95a3b1697e19891145cc707a503ebb07b0604b8e53716e273e8526741b5312b62fd1f712d180b0b21328b281ec4a2b08b299ad6244f076a9d1e7a108d02b2b1948fdc0442a859f726c24da3dd460bb6c8d3277223391a0f86a6dfa4afa10630a557ebbb19cf6174c3e45c9a213d2167d067d22fd7b76f6218293235cdde9c3c1255cc1396dad77c70900e0f6dbaa90a327a8eef3503a32a2302b527b37d68fe614ed12f0d0ee681889bdcc1217169f45d8e5266371a701fc9e9f0fb0f84d0f93e5b8deb29122f265945ce3e4e801bfd51a4b1e3aa8cf19d24608f8d09ba87aab74fd59b85a275ee75ca9424c174a9ecd8b9fa3062edbca534a3c7dfb2508a7c39c0ab10cc5f2614fa5ca3805dff6b4c766db20a404a8e9405185d02011f9a501459745fec4c96151c6231f5652c352a0a337439506d0f0bace90610125484a2fcc1290997b9c147195696d75f97f23043cb6059f3b36c7b3e752ff1f1dea1a4d19e3e3d5da0f23679080d521e28fc1639958f5bec45570c1baaeedd680719eea38569428d650022c85e57c6c4b4786790e43bbbe97548d7b6c599c3a71f126b453cded3aa82134294f135a00c4a99de61fb64421df643af9051b2c334b5f28c0346473910edad57cd616c20009cb503402641757d6c3ff986d543627d28aebac39765be80f7e362112666d1467950fe39c9e63625625f89e1a041e64d14bc3e1200b301bb63195b824586479246eee5b59771cef697b6647eb7be67530d9c8aea2843d631e393c6e613b0b385fa47a2f2beaf42cc706d94c4a626ad2608727c502c613ab9605f78bb4dbdd89964d709d737c45283049ee05a32b16d3214fa4825796c10a337d82525195ee339e0d1de2441d0e487406825b1b68172eaf181a6ed5afb013dac04c94721151d6757c4b3e2695fdea8e0edd6d17111963659702429c8212bc9400195e4ab0a68184b3ee24702992dd23a8961f61330a76e74e507636386bcd7d1c64966d7eb14d87d696c0a51ed0b5171942acabea58a44c0ea3b77080c7149a935e85ded3603d3797c41c7337d924a0136525ee74e52806effdced88487d08ab833699e57f2e93d732f004bd2795837cb0aced3f83b7323f04a7911e597e3df9cfb8b5976782cd08a929d0aa5da7e904680d7a603a7ac81131082ab618346fc36899485edf1b2c56f220b1974f0f951d030c42dc7e4667f53c09adb87998b716ec11097a564c29c19213bce4dd6b52dbd13ade55c92e90cce1b82167b0cd457aa10e42b24e8c1d2f27eb8239853aab35f7dfc1afaf15eed69b8e6ecc8afacd2c269833448409e31c9675d8e853f1212a777049c0f604dfe123ea262e99f307093e2440359bd7bdb01eca668ef59a15936db434837dbfff486dc8898c64d42e9e433f5b4714d71dc3f12fd1bee4a383af7ebec36b1e1a9795dbcbacf4da51055a3a0cc56429f0702431757b53a3c7dd9f743221d3b40cc10db15b09c56bd091c0bf1dbc7892074577e677ca306e807d643311287a37add243dc55e72579f19a89068466c9cea14f4450b61a5de8c9cd534c43adba4c1f5ad461999269e7921b65b9245fdea9746e6e24eec7dfa61d0454850b5097bcacb607878fa29c90d137a788adf254a5ca8ce08e41e09efd339c36a96e6f2d29ddcc5f0af6003e27288370667b8946db1bca8e2875a88cfb52c32b6f132dc4499d42f90aa305b46f7f9844f3dfa9e938a904970076170e49f33c3aa53200dac6f5e33f1d0423e12a564e8b0361a70eaf1918b28286878209c1d460b16a03e12ada1a469b42bb28f3f9c8a98d3bcdbed60b0c92df40fa6689df77567368713bb59412dfd80355007a05607281c868119e132b15eb80c3420b4721d805aa7d055b17c19cee3191483f8b45492b0c9d84ed0da4bd332a6a651fefec40913bf7a027c69837943e9239c73ba60f2f320927651d1d7adc747b5f504031492fa6fb1b048c5f09b69254039a552b4a9200780421066a07131f744d31be84d49b61536efa39e8dc50555d22d45e374ecbc079694a25416246fb7c8847c8b472b1ca664f1e81e8064a4c4be1f9083d5175eab7080c3b6c0c47068a5b5bde56c867fe1950cfecfa0051c96ae432ec54e8493b3652575f6ee583381a2d61c99e923909ce1ab09654704d2857f6fba897082c04c73715b5cb963b111077da2426882c4382e1b938a34048ceb34ede7f186218405fa51614cbd51aca50766ca9c77a70b4c684b10bb4122a94c3dbbb2b8c2c07da889796185adcbed25d3d7aa1a562b78abaa1638396560cbdf0494a096954208a0d82d2f6ce7c0b3c8fbe03258e424ab8c7f7af882b4401118902654323b77696549feab12d5a7521d02318056c4005733f4d6fb654e80283e7f7cd5deaccd8170492cde9ae1591d24bc1df940bc84efedb1c9efe43343ed45ac7e82c7781fb1d4dd4d32fb744c2aa2400d240f77eb7c90adf48f53a8f0921ed129192dcffbc82300b6b89ecf281975b4ef728a1fe147eb2d61b88bddbe2b2dbcab5f2436282535507c512153ae7732c9819cdbde61932d6f4bdb2d999cb351beeb83ec99e3bef2c1670a478960a604011e9c805ef8890777741755a3c119e9427b1233ab1b3148caecb9e93bf4ac807739b46e6677f98ca82e986b2a8b742268cc8e3dac739c1f0e5dcc8c13b6d6c4ad1c53b9a9926a9069d9e5d4182c5206c96ca07fad86781daf7dad8a3eee02232d149dc32b6f179895748288f049917816571df3dade30295abd77fb57514859ccb051a4486853b0b498967875d9c02de950043b441f960a6c4d3b7a294afb19df2ba21745f04f68605fe63fa69705f753af0f4caf0b4c294420052887a3070b41a50df70b789ab8b1cbff182796deccbf1cd02fa758207a0ee9c8eb87f003821eff9321a34c7b69dec7412580194a2c3bcf32249bf4d6b20542934465707af7726bb12684a9ac8d703b1166aaa8c1e807be8eff872e423e164fbb5934eeb8d788fd831e1cd89bb512dcc7e983bbcac49e4353b0490f6417d4aa60f9037cfb4ec8e0c05100486fc6f5eb9ecc38e0cd9befb77761ede17a1866274fc9991823faf89895694b25387a186a5819dfffcd6932faa847d6639d215303cced0c70c0177024360492ade5cd76ddaa36a2e26be95e632a96f4d02b5d49b181b3282907c9b81b438e4d6f19d10e113943e93674741946fce781fb3ae8388ad8065e7d0f424479c10ba31c5f306e18c74187094006e9bf9bd78d33dc95a145aaa38717cfe1dc8fa70ff56316100f12fbae89a25569fbc58d5afa79a4f1b7360d27480fcee2af6a36814f4b3fc2d46fa562034993b48046776120ccff62727b7cfda92db4cc6d938131638d14ee6ba0cdd478df1bdae619aaf0c1522bc3aa8aebd9ec11000fbe4199a609eeb54de8a1f47f8cde409c67d0fb50f5bb1144bee7650835f1325614696fa9a5cb474407b50cac8376fa8303a75ea4d7be4e79b44b84a41cbb648ae013d8328ebe0365a5e8f139f2854e50b6cbccf6bbdabd85d12edc6fcf27a5cfb4082918fcfbca418a2e3b4026c52b59443b280491493f13dbab8976b90a946a4168f6a80a845f4a22f4f2f6d5c1dc678438aa9b713d086e873d9ddba1dd4c5c4624d9b32d9ae94a85c0e315b5c02667a6494267c23b6ca30ad4e45d5738fa3776f0ea20f3a6b73646ecb7f0e7fe99e6d413971dd452fac1f7201a1a0fb0663ed23bf9bdd522e50cdbb2b2a403a9d53a74f48edb74d9e5e15d6a90c6f23c423ae31181089dc50bb8aaa23c1023b6ca288457d7bad385419b8aef10788b082de539e6ee19824c0718afd402f3264d882581412daf8d8d97f202463b57ce740331955895c200b3d48c08aa4d218e7911487fb9d59a15d4582c0982fe2239e0ebd3bc847f1cdb9b0a1e79b48395d65073ed7721a23688fd95047799fc9f25cd35092a279f75a7f0aab6ab7a2ec14a080081f502ad769f09e39fd132da16c859d826b9a5a63593467ee11a8c31bc74de0c42241dd0b4920192b8ae77b4ef40e966764010d2e5b1a5517218275111b68a25bcb8ca41aa56f7556c6b1335e20b733532280af281f6b40d1ae108241622ebb1ab5c737ecc1c106651cc932497a63277419917a214b991428a7610e0d504238a925404808688218e7c573a9797084063575cb050135947c4411a24d80510ae25b50db530d611c903d95db03511d47fe5fdf08629cfc0cabb8ffe23e58f6a1d3e5e81523752dda6d72b7c5c7a512c316e7f2e9b107c12e3e8dc29f71ad296013a2bc727c6619f80b0fe56aea2f093cf402882baa329eb31f083fa58596191c51cbde4eb715258b4f6087bf31c24b1e4a1c8733ef6d18d85812fe8a994e819a0f2654eb896ec511c107a86ec45bfc27be13ed61485b3040d4eeeeeb7ce647e7366f40cf3a0f5648e3c9aa9a72cf66162dd736a5a3981686b340bfdb5b5587a3fa3b7eaa892bc6070c106eb6de638a88c92cedad0a73da0c5e2452699791c168b2eba0dd872c5625ae780c43f01a2ff856467d0be8d80cc2018c3be1d9501c6eaf0b6f48c584287c75d96cebcd0e1268c41c5eede4db9190f97ee9566258e79127bee5f31d00367fe37c17d86f701e46a79289cd9f4033b47b9a66599a523761421f38b537b9bcbe48d4fb3d789fa4cff092927b2b1aa3ac0257d10992c2622c2047362e9da7ce9068192cb3adb474e0ba33cf488ef4cdcee317aa5dfe35450c49fbad999b4a84ed93968240b1025040a2a6d0917889d48b52adb06efa01ad69fd05093b97812c7084abb40d4b8afa2c65d94c3d4bc5cc58bedd9d9e4a5e66643c918289ef40b5200fa37da9c860e39a9b97643e4c34486b620783707a165e6355b76c5a4fe047cbaa99ae6ef93d055cddc6072857bea723efc5f1de787f20286397976288eaae37eb5860ec76b34f573de1c2c956a9cc8a420c76abb149fc78e5c39aac83227b4214ebcbb12c2dfe022e3f0a526a5a4224a41e5a47b8288af6104aa0a4f58dd9de8607c34e651d544f7acfaf24f5054e63fc1b902424b2884960b2b9bd81ab5782b2324f658e306a807ad4a06f930cefe235ba5c9e1b8d1d7731c6194d1c608e0fa3e5cb87bd311421a2492498178fc52a55e644f1746c184a905f46c38d816522731a9f138a0128ae928ba76e5c2417826d7979903635cc03c0e82b6e9f58c782d6e08c4b95ac2ffe88f4eb60623cc18f44ceceb38ac4baae78a608c8ef3bfa9937f37dd51e53d3a8317c4415699316162b3c212fccf3839925d53189dfe989e4439981a3dba8fd632aa69f2f85212a8dfe68dc7394bda1a8f38104a6d344df6e686d6bc9014c32e28556f1b55ddace83f2710bc1354b39dcd39973197d662a64dce8f9b36967083b648b6a6a11dd68f253f1f59202cca7d825d68c6364699653628500966a961ef2ac372d3c350b6600d20c517e86aa6674be6f621a0f60d91daa6d056e29649a35bf7127fb159fe4919dbe7c0177f5d40672188e49840bd2e9c4921ea2c0b021c74d3faf8224708060c99a0402f871e47ab52650630abf92dd5f47d0bf0a958b29ba96d31383d5527d92a976ceaec04183ae100fde0e4ee41fcd612ba7120387f3f1a1d869d5f020fed8590eb7fd8b53dff1996539b212405062d703db6cebf85ebc1930125cd8be87bd56fce4651ba44fd474e4090d6e788bf77eeb9754486c91ee18883b1a9f16c29a1e421351ea36f3059d1793084babece78d9bf60e44c27081a9339b506eb5c7d7bb9a8c761aea4d6f2608fd41ba923f3ac53415afbcdce8cd3d7f6d740428c6ed19e914873de8df8ac6ae75849eb985f7e80ae140647685362a71360d28aa54b517ecb225df77cd0b1f4cb6b9e954dda67fbc8c3d5803caef9c176ed8208aa5570f0d7b8c008ac7a8575984ac744e1046773c382f4cfbf0cf5aa1ae94af33ee40c1ef8b8330edcc105207fda6dfa89bd596a9c03dc89da2c9307014c51dd8bcab1ded92ff32c7bd8c3b4abb76c325fb115c1eab050359925281e26ade9b4a3bf6b21a272ca70eeac4f364bf28154ffc78f7b94fe067557ba66c48c2cedb648982abb536cef536776c865e3da0056ce324503307b21fce119a76580b5f3bc8c88266b927d949f62252dfb1298994c20af0800f52ce390b6495cdd2f37ee15bc3adb0f8a3ac0813f6ef0576ce369a87c371123f98b44688524a8edfd4a9bc9c3a1c49430a0254457d8743f26d81ebd0180094911fe0993532d01e0a234cfdc1512be8441e8089f2b0dd16abcc0dc7831167b3441207d476974bdd65cfa19942e21211c7ca29abd8b2dc4ab286ae667b00d4f56aca78550938171e48365cef3a119b5f8ec9a287cd47c836da0eb6b35af910a0ffbfd2d63aff9d5bfaa86a53318586e2f7fe309ac6247d1e33e7ad435046acc59fceecbfe9cf2866937be0076d998015048b32c5d7419fe101a77f82579d26cc1fac2aaa1fb8c3d2ae3a89fdadf4a88b38f1715b2f0c62f32a16dc1bb1419e86b69939dff1fb57213e9e52befe1ff06d7d35df40b6bf78d147869079acf08997ea026aaec4105872df9463eacb03d9ab6f7b18fcca2546b4dcf9a0fc53d06e49628fb02eed5a4b8c89854b96e193c74b7f51609bfceba6e5ba9b831803fb09be9e0403dc63e2f13d1424e0b90a06db76bc721049bb448a285a7024c91a0de095184143b07f7519f64be3dd243e5a636a05332df286e60b9b69a205194e26f8ca2822d6969446592bda41afef14e8af04ef22476efb8409c57e74e75d56e8fcead8f6832b876025577742d8dfbb740f87213692c7cd6a99b40eaf4cdfabca2cf3653325a4bb55af714b35b3d68bf3d448e0ea2d19743145e308b0a45e981805c39034990c7fa26ee467dbc951dc4981f46d02d91984126d891841124b5dd0962490d5847d1c4200a6181e1b434af14b2f51d98fdab1439622dd676027c7cbeecb49249929d64a6c1c50577630cff319a847dcc35d55c610fa82831a7da06e09386f5c2c7161164672cd4a929ed6b1eb1a34f97d8a75804b46a1d517e135f569051c9f0a8ce3082c529be5dd041ec842b9a82d0c8ede48d0cd65b85500548545e08dda3f9a16343fa72bbc7bcb04d78bfc2edfce4ed5d204d0d2215f12f73db64ad3fb4cb26768d116804fdc2f7e17bdba472d028fb29b58606e6bf4c94df88d122c524129bb9668b9920377286ed9d70ee6fc08d997f6f4685bf436761b941e51cc35889541df3f78072a3e0602b8fc41368db531825da0573d143618ff25c3402dd4858c688043c91b019b63d3f8a086e64c45f68bad39d835814523eb2af888e8a685aad0149a05b7f751915bc59d55027850032418fc7cd31c2d0ad56d855c688dd4bb96a6f7591424881d5744b719b06f941d4be911d10141b58fe083846f19177acf22605075ee7eb1ca9fbb3615a1d09dc33a7dc7337da30e174a90b63e8f4cc9ba4ae5c80e2833b3d3c067f360ecefcf7d49e0fc20b9dec9925c362b5cc79e62249dbc440e3e78d1ed7aa3e8ad005ef106bb149566d925e048cba59575519cac986d6d06d363bd76ced55132859ad8d9fe9495ddca23e82d5413f661672c31ed6796193b064603b9b62dd0cec2fcd97cfecd2114e75466e39ea76923b4243fa0d2af52b4acbc2116e60c5e9a85ea751eb5c31e700249473bb518b0146a23f28915fd88818010b5efbe3971b48838da57f1f664a1173b0b0a16c564aeb34ba3f39a4d3a261206c0a947d31c595ddaf7f350cd7fec2823e2646261a0b87ba9157e1a42d5c6be6fe450a31eaab2cbeadeae8a06bb687624ab20e2f4a24950e4fc3877123154abcf02a6c6261467d6d950e67e1282c097ce163814911760702f3b80fd0432f376732eec649c7d2304f4460f6fd386201c95829e55b209e946dc39d5c38021f489c2022fbe4fa50f3cef6a0125eb7faaa5691134745afdfed270b9c071731e870c614b14b890f4ce99c2de5d7e8e6240cfb45f01720156236806f6118f03a4b7807dfd6b4e694f049f21e1d84b1029dd44090f81a5db49d05d75dd3a2b596fa51b20b4d0ca7b30c88e4871898dd36889f9510c7e2640cba248263d738f684bcab6d21b1964a9612d8170aa32f822b6685a8bda67f3d0809ef7413830b530c420820522ecc5288548c454043af28c106a600ab44e5a91cab842c8928f74b9e076a5df20ad35c965e740b5e8fd870d790556b5344db71087ceff63e12ba0c0496c699b4101af65a54abeacb9ba6c0eaae4be50137f5dec61599d460adc9b910fa9e882e7e3ffc9df74ad0e1edc353c4233dae0f421f170f0c586d93e073590a3d3c20c05609632fc120ba0d98ca1ba2b9165ef7e47af3cdec4cf29750ec145200284cbfbfc5985d69ff5ab18ace2fe65fe91a13fceba554a4fdfc645156f4fad1f180e5f80c1b365d960d0dba31dc07ad8d7d6e401a21e9fa5c9912918e375c9f30d08c0c251675b2d0d77408d422c9e302603e40f1370243fef2fc1f6959d1cc6a99100349d8fe5b0c799a3203f851ef6b2c90fb769ed3008e15ee59faa9d964e4629698c5b80a0106393641c145b1ea0f218544e243d6abd2cfbd82f0407107b48e4206452f034ae58ae2f73157016b4142202144235f1756155e848df3057080803672d945f3e05ec15f8aab9f73d647e9120dac1b0352109de7c18190a91d66c560c479f9928a2a9cffdc9712c4ac84b895b1a2797193e9179d28ea89d5fbb4fba5275c186e1a9d4185c6eaea3385548c6fa7242b4be4aa4b5421f36cf90ddd5762e7e7940626ecaa4aa008b83731a3820e959731e1bddf49a63e4b9e4148754e1b8f2a757eff4441c7dc51af76ac4a13a03d2abf1ce27bed48272c593d20128a512ca7322357f98b1cec9d963e6d2151f20048408845298c88b55c8663a6e4d376f46156d452a2827b13f677988d5cf593f00e330e65e141ed66f2516d5cd7ab63ad35a0c419b6d48debcf3451c2f90ab1202e39ce2f8f9b8ba0d49858f7c72b0bae38837cce58b99c327d99c62842421d66bd6cd942c3690e73dbae58525ca0c37819223e4739e187e03c00290fb9315da0463df0cecb50a61ef4b293910e861d814d3d889513a562a9c0e3662dfbedfcf6cf3021128fbe9e657ebd43b6e2048cd52baf761b514e58f5c9c3e6627b42709eac17901962a0596aa3f7ef8e63ef3bbc204afba00283d26fd7fd88e4e635186b11c1365dee91bfcb3ebe722e6dc8883a8064d31a423fa04348a30fac4ee4c87dd690ce88a7c744a7bdc253708fea111c1b21bd2c6e0a370136d4a69a07570d04592a33a526cd03c5d01915398479d84e47a8fb6b5ce8ac346996b3b9c8674e831e24ff61d2e13971638ce62423f716779fffc68669547d0ca8c3b12e72941cdfc4000f48d56027a11dc782fbd7e9e309a2cfcbadfb5333110f884546c61df9c26c48c48eb582778857e0730898861d76aab7247093e7a7e2f118417800b7b6e179e155e5fc462527c3790b09e1aa058b70151fd05c539e70670947746b0c286154ce127b20f82e904839b9d1e6397b67be85b414593cdf9b6ad79769e7bd453d1799c53903214fc0024293126925b3c77bb35e2199a949a842f783dbd24903d974d8c00bf02e1d1d1521cee6f0d65db2502ca7713d7af5c493d50d1c7e15fdff6c1f25c0eb0c97b3dedc8882044c13e192b306a1c0e89ae4b0075b8eaa302aa518bb7bc63ed40023403504989f7598900f601d0520d22b476faa4979f437ed42feb7da7dfb3132eb8a7080c80258fe6e2ee5ce4416225be911694f50e65da425f19be0bec7ac2bdb07239eed8d6c5f847a4687bf29c4e67ad39a9e1a30dce81b7dd8a52b3c304fd4b69a23e0950493d2ec56a28f368dec68c396e001401e8afc4851a8f9c43770e288fc76893ff2bf137f728fcc19e8f57103985f1d765482d6afdaa97c63bf89bff9a420474a416340785fca3c295b37cfe8b0dbba8476ea57a8bbd13950e36cf26449eef708d5f4cea2c5434a3c95841c80c06fa2eb155116993402e89063b461cb38ad33b034769d07330566e96f479f07313dfc45649a43775808fe4589555b7c1113fb3a52ee3ab183a3853f527fbfb25c5fe7241c0a5718ffd57b7faae23f34b68fbaa83a563c7b390bcf75526a5990d5f256740d1f240f67d869bbf4c49d3d32f6185bd78884fae8abf111d71ed58af80931e20024b631ff4c2d1bc3693e64ecd5a61040a4ce0e08b17861485de11ec9397f8987136c1b1c9a2053c770e0e89fb80e19a2805f755cea478a8f615e6d87b8bfafe4253ef1d97a98484675d7f91c486d552b043d9d18366bf4f6c1997e62fc5f16adc81442c4761b02e4b30c6b1a666bac95e9a7dbe5949ed1f7940db7c0106aa84aaf4d9aa468a66283c21d5ef27b8d7b820ff31fa9ecd8e695ef190d157ef300f33570d5d1e28b40daa01f0849000247e17740798eaf31919152bd3f803000ada6dd0143568922be58f80dbeee172849feab3df037cf5b488fff4fdbd933e8abe8da699f095994b181cb241fc7f7def26464e30fb108f1527b28724416d77c4f4810462d20d8ea0c29932587213fe11dfdb7cdaaf19da88c01d09ea0126596717a5efe957ef12a8845996e6b8a898d8307f0b8bce1f291288ece59cdf52008f3069e09165739ad038b1c3ed507eee9b6c928773e25c6a360b88afd920b8b5ea04c551affd028725f32e6a604850aa90329f2d822c5c099cdf123d5732187b418d6ce0601eaf52ebfcc684e892885ed62bd34b88011894276cd1cb207c33df81b3b8c63ae13447d76a4b74ccbbecabf12f6da73e2757f3e2600765f93c9b562c893a53030c6aae8e9d3bec1e62574827a57d60d04d44867db3852bc66ffc93f4078787c6bc0af3408240b80211018776677daae53a5f7192b4e792e82c6926b9e8b723496f0182e31d7ccf1e56045a137ea5aadfbee77f1c5e2d7b974411c6f1fc060c6d17cc39d8d296d5886cb977b9717ffdc993fe7c24f7e1c1e04b7651a545c1b4afc78b1123669154941ae24da6bc2dbd12a353244f70e9e2fa45af36d4ef97a10efad73843ecfcf40c1c892e945715d377fe7f5c1783100f8ef012cbef55f9168677efef8433130598e41c00c0c1f472091bd71a7570a7767e3b245c7e9bd1f52006642a7f058fbcf99e3c523db1be688ec3c2ebfa16e999b849d398cfd6ae250548a0fee954aae48101a04a1b823add2e5c5421f0e07c0baa97e4682621332ad1395ed72ddb8e5ba17bde88ba353678d784585811a1886a20629d49281ba72cf90a0d382db743ad24d6a06d283c169b0459b8269427c0b4366af36864640073bf2d59764e36dfd39f9b3ddb5f08561340e7a0fff5840c02b918f548204a98de6318d1f75e8fe8aec5de407e8f76b3e9282dce77e219c560e0582cba190471db4e0461f9afc5b583d5f5c44f5d31a374f344e9a506f3a3f5b2684758759860ec39643c21f9ad1e695909cba72b9b932cc0a3bf4f3c195b62745fc7fa3fb582121acf82476818306fb10e7605847c08239d9ab6d24d757c9f1c47cc42d66f57da04db576e44ba3aea60c2247511497c7a62af952109efd6d8d1b0a9d4be5b40bb1af4c05732fd57c8a59d5511a7b0f9f39a744f42f6c661d64412c71ef19935d551c3459d4d88443baa8cbc65c69afe304c8196e39dac86225bc3ce416ca9e4e6a83fd7c10e15d57ab23436cdb6de419e657805b926afcc1a576ffe2bcb1fa58b796151967a4122f5cd0e475f734b477638171f3a0fa4296f67bac4532ed78107f254a596091a9c1448d2a3e3073e20c8277aba458b0825f744008f012a72aa27fa04d2d1e42d7462399ea57804dfe9301da2e8a78199f258e8d2e4660421427b692258e722fac55f3b27f5699a9ec411d32a9b29555c5d842bcd72536fd6cbbadb08163559d0433a70cd18e14139340eb5423c7ba016ff226d02c1a4a320bbf6d034206cf77b7fb7b71240d1afe322ceaf914e083f188d86e0e2682c0b2200223a4c5a26a5f713b8d79f56eddd0574538020952011779441c4e1598e1e574444a4b29ed7dca550ce71e9944718c48f3186101f121374e0e06967bdc7717b2a86928dbff6b77bff241529ac16b544a43314669b15289097a1c7a0a30770a54b5c25adddc45450dc087d27a239646df5c0b29f71b02d4978c216d11f8001f09680e0281de9da50969bb0b3afa25e879308fc45533a33f9913b9d39ce0916214da0f3747c67ae153b2f415361559364f67fb9f0a36ab447205b251434327c46ac4fc5e53dd82094196b6546655206fc8f9581593a8dc8f355a5a5484d95c8201a84ad5d00380a76a6a8d02fefacce3516bffbeffb7ffad2f17f864ca3778ea9ecaa9986f2662438bd1f3b0c78bb1f19090404c21f3201a4d8710244bb3939c9231afc2f13c5a70fe82bc9fcf60a2c5a27df8348ab568e36c7075eca641d04712009ae9a86a5b93e6a13980ae409a21db5d5cfa9168b6d49daea6c2b00550bca8f98e9e5efca561d878ce327313928abdf94d0848b428c997f29c0f1ff20d4248e94adfdc20b7f10ba99e86f52e33230b1fae5d9152b66a98da780d28d4fed4cf6f9c070e71450d363da69f83a31323b80c1a0fa2b3b2d24f0b0f7ed85697b3d848c10d1506e7e373a378d2960543a13a0bf5bd0b90dc6b17dc819f7b8447322ee15b0a20dcef26f458b0d81f2a49f601679faac50e81f0c69a6967393a586be60c130fd8c89e1c286902e9202f438bbb71e6213efae3fc32a4395e69f0009f3744c8fe1b8786467a857a1b5f1727473a66f0ea07fdcf205b746bde724e449d1890ec4cea6fc2650cf80e043f971d72991755d717950c86340fe781a25dbce3ca0510f457abac7661fc74a21f94662c7c6809f67fbee9977ad5e97e86e41917c52cecc3e7b35e79b1d9654f45383ee5f655cb38379f8508cc14b2e9ba41b600d8960b83ec83f46890195fb2704e3a7c19b0ef1a52ad6922c7610a5cc5f54c5370291bdf30443759bcac0336a251abb99d33e391b6ed395e50f64031055e5665ff72d79dc4dbcf603718ca5e49e475f48289aa9963355d5242a00ca7abefc07baa7878f36c7a45c26ff7133e9d5b0aaa803d1452a759713883bcee064390395c5413930e316b43a3bf8b348a85e460b6d968d8120f64e48626a0ce657b828d66f7744050617b2d8f6d169ce8f64b0b54704516a0e9c830a8a301b89188a677674d4bf7d41d8ca9b8ea31fc9b8ace6822de09bb11932b5e09a90a211f2d774d32cfe586535eaf0015804219041c5977cc6ced024591015cb82fdc518999e453997d82508e94d585c3381b1ea9c768853faacaafc2f2ac44a158f60db7a7f9098f4da04736d25e98898524959dcdaf7b0a06a63134edf8d9eb3d9172c9c4d8038764c75abf3f54204291e6bf20fd6c2ef32f2265695853c02a509b100de3be7eb6effa11d17bc0efa0ece5378af5dacfe9b35bfccce6e2d66657143566be7f509ca354d5092840cffbaa5a4ca43fd50dcb8040fbfe4e9bf9ce9ef390c20e7c83f9adcd3874c364b800adfab2f29cb9c3e11c1bb8f377e6431d9f16c8f3db770e1315ccfb7a59fb9ced74dc1592e70135b6d62d43803abe4996aa6631cc49b189c53d48453c39166fad44e1aa71d12b13cddec2b28842580ca0440b3c687754c8295dbf4c805df9ac01663a1a258bad3bd2466faa15e97c6c844fd908882b4154912113613126b6947a884683178782256d162aa0547efe9d7c62ff2deca93bff13f3e640b49332e15297067c084a30056d24952c7b7c0f8b109b839acb4f9248a04685c25e0d880027eea4aab422788dcc1c1e252f4d6f91646c04913721a5b0204654a84b08cdda38cc1b575c7c5f083ce37f5803255f1af7824fda7e670fbd3673006120e99b8f0a7768c843676f78b80b7d9adb02a55ca0615f246931a8db5bc6be28cc5be9d81fb2341864157537fbd07114e3d4e775a0848867ad1050b80511173ff0e94b05171bb295d469b48f6aecb328d690e09eb58fc8128e1b8a242f76604ae1fe219ac08e84c4c1f8f8f5a37ad318834ba765b4f7d9b2c7b2d0e87a78b8233873a0a29e1056c11a930acf38e8842687009a38ca7d87b780fd62aa0d2f566591133e70f8e4742bb57ce56ad4baca9b62d990c25a766d671c7096fb18e9ce72885838aecef99ef82403e9a8cda29d5b78dd227e2a1dfaa53eb9757734dba0832b7bcb07acbd7d2836fb62ea0ef7c66510e0a92c879d8b3d1c4b70c5a167e3097298a79d62892ad816e71e952264e4168c7d02f290b12f45d556cb83d50364489e17a31749bcf974ceba0f4920080594d705d9784e1c0426dad359a660a0743f8ee9e9501c1bf0c2391c8981147bd7b19063293f51d66504624179b13c2ebd2180ec375178ce74814c005221fcb47737dc2f56b21b10107822a3c4cba5bb8993a2ee98dfb5b3b1c1b9a636bc65794532552747c9dfa498323c0e719662118d54997758c6bf1fb5a35ce20cd5c3db8d137942b89892545b8ea99c4a0538f09ad433452bc8c83ac781ff4cd943e956f26a7f51f15e88381e1b1563040047f512950559e251af7c11c689d37c8bbb2ce1008136853307543b68a1502905a08114072a0bbaf83c6eb0a6e7cbcb416a9f52d2bbbfe3936c3dc8538744858cd617c043b2b48a215bc0c13195e8207f6b197497ae6d54ee1482c648a452e0ef193917fd5d41ab6386fb946db75b0dad65a87a8edd1359d0097f3cf05116fe748bf7b1ec3079c43b0349d719a8fae5adf75dd98a1c19df3a58cad53a9f275c34e4fea902877ffcf0511e7e6389f6f92de36ffc452147344d677d2daf478ffb5776c6bb81fb4115604d64820654eb0f4810f84f03681e1ecbbfbbde1ecf0adfa89ba4a145ffe910c0632bdd681659d675c7758953fab3aa10c2c06dc70d85aa7c181726d6da199d6c70aadfa110c26ea52d43b6d109d25ebc85aa90444d5b56a0389e5f05031f2213d6b5803ecf8b8f42ac3cf4852e8e88112c86442f69253bbeb4a16f0fcbb97ceabca5c5bee7c0da9c7b07c14db46375bc19e8e9d5d1e9a51d211c341d39c600e7a55b9d5a39b7e92d604a95d31fcb3c58f6ada41fdcec7421b7eb46b9f1890592082402cf232cb8a28b47eeb28d84e0d24d3aa5ffc1f337accda14156c92923348db93cb39984a99bcbd99d048a2bb03f2736c30bdb26e00baa42c25c786341be3e6448b99ad802e2f3abba964645c2a83e9953cfb76e2e41513fc5d187c0f91ad12a02d9b812d2ae95372a166b5725eb0530c1c4920a3e0898c735c936d3f204c6f8d4ad1c0081ac4c2c06b5e4d3a774cfb46eee4f96325d55b1c085671b189f143f5bdf4a975c0783544b46729da88421efdf5219aec0a2a223c8e7666db09a2b048099b7e76660a74e325522e9b0addabb1bfac68b0bbe4e2ba4b30a32b4ef699ee46917090b8e1e6ae784ffbdd0cef9704ba018844026ac782f654b9464a1d5375b3f3f5b29d91beeedf670655169e586ab8247058b28c009ef960bee6428226af9962d0118050842d34b52bab42440f9e7a851afe3b97adf15ca65444549cdfb3e05f3cc4a44b50bfb2774e4cd6a472e87b26785e0211b3346f688a34d62a506318e34bdb6876cabf8fdc898bf514523e52c9c457d2e5408d5fad8f9e09c00515bfebeafc456fd531e9519a2cd75c2236bae5549eae6f424f25a832524b0b0d7d90ce54a59c29166f4bd2416456b95aa7274421e98cccd2a5a7c64857254e083643d2fae85be2853b4c8c0686c636bd62aa507ceb88200dd612d4b992f2bc754633343181787b6af6a5dad45f08d774bae2ee8a34406bc1d00cfa5d0d5ac20beb744b9e02953c4fd6a776135bb1e36cd4cf63080414f2837b7a11a3600f4f671f1b71142148dd93af3740e05836f98ae762ecbfcdc540f6c179e2bf36014f8eb97593153eb49e24c8b4252c9e119776ebd54a550eda59478b0fb32b0a8a564ee44d7a9018413054e0884af0c59305c554d07998456b5b2a53f4db32336d2498a52e23cabd94a1f42231d5b1136f5463286d086263f6e9e031a470b0bce01008080e60e63280ba027b2dfb3e771170c4e07f957b908ee56ead8ac35d94deedd6d6f29a54c5206ce0cee0cab0c6fc3d6c0094a50f23182d139ad35a9697123853a7eba1c9211c05328d62ae9d567d19c44524a29392939c96dcec96c13b9289390b2d6cce5573d931ee328d346354f2046e1a18ecfb6975b8c2a4b051c64810b1f6edb6fce6d91e3388ee338aec618738c31d618b31c0db667517828bfd65aeb68e4218d85fdb17a6cb04a23678855e6d0bf08f931d6208e08fbcc251566a86788dbb66d2e6daa6f6fb3ed8871025128f3b0e63089af54f86ce4f1cb7cf4d17cf45ac3247ee49c966d70c428723ecb138851e4e8f8510eebc728723efaf65aabb65a6badb2d61aa56613fd86b7d1682cca1801f34064c07c48bd50e33b92d0217df02823cbe41b0ebcccc8388ec98c0fde301f9e8426340e7023e33ec884305d38bab1d530e630898f41442a7ccd4062bc2e11764ff5d517e3f553008c29a048c3cccae317eaf86883230afdc8b398640a423e87fc519e4014fa2c03d14012318a9c9998ccf2550ee3c973b38bf9561e2f0362143933abac23c6f14cf9e0325f9cf1e163d11a8400064abc200833371c789959398ec94ccc77c3819ef1c1710230b372990f2700333ee878a266e53a0eb0721f5cc7133532ee834cd6f1448d0fbeca3a7cc8ad9587d115c5d0812200d9c21368200525f80e132409020b5800c52b0760988933312b9fd19a8cf9565ff685437c8efa59cd68acc12ea2444b70f590c66a448f59a642521fdad36053244e107d488b5ad523a4553426e91255340d8102155387af2ea38c08ebf99aa58d0c132fce1086d169831ec85d24f3a1f449348b649e938919fb36c34b9fb1564996aa0273155b52081232c9cc9143a4916f568fdc89a9688c12554124ed37e7acdd95d3e58d4e44b62122661c73d67b636608e0a877084b4494998856c58ca387ae09815685918f5109961f62e48731be5b7e88336b31627ef820e09045dee9b14f4e273df3e9e4e4145d2e7f8a9c7c4a3cd3b79fcd88dc7e5a258dc4f9f5d70f0a9e774211256c46e8d95af1250d3bd87c10656c43248cde8600e17bebc1b76f42a28c74b9b54831afd5a397dc866c445c51467a74af3c5e795a157a49f8f688a48a1e5be59195a75946af2823a768f1b42a4f6cebe9d984782e6d8845c4ba894210f6e8258ff5762412be1bc7759987d171d43cda444d7ad641a523c132b93479c9a5c9a5c96da4cbac83158b2a0b092026a5528c31462959d175b062d12759a52f077d12a954922cec79885d478392853d8c45384b96b7156948b44a4699b57f93cb277d29e3e89ed33cd307e4c4312e954aa592e765dc60770dc6942c91450de2f44d832cae5973c51a034662080e6311123a8080c358f43fc09c0f70287df08d626625565895d40aa3340f7d15da80b6d7f6da801a6ce9aa43b617a59b2bca2093ca5c63bfb93697cb18e070736d2ffa02da5c2bfc9abeb95a25299597004d9d56afb204f8ce5c2387af3e0d2ae91516d2a996aeaa43738571a690b012611c1f9cea83717c706c836d411bd0e6a25f11c544db121cc3b13967f6b3fa6c2ebab9e60ab7706b73e1d6e6c2adcd855b9b0bb762980817551f0d2b196ad0f549b011dc1ac53e80c3ca93439491d4b95a2ba59356dcc22d2ce4db298f1d333b6682308e2a524c558069f6629e9206a337e4015cab8f37e4b3da805cb4095c3ddc5cde50adde900c6a159542ac4294e10d094599e965e10ad1438f0adfeec182e013dfd13a9fd15094893e5166ba7c5c3ebee3de909491ca210b3c4136233c3eeee34a43cb05e413859d2146e8077c7c867c585aad922e1f23bdc246702b89cb1565c6ad10e3e056cc1e0bc785d3aaf0c4e3c45262a9986072f2879c22721e5b42adc70c58d6573889be7d0e49a951e9f372c6a69206690fed5c4cabfc1fa6c11953757e56e0e31372d0e93208963e5de659f404fc82c1404c07c3e4a904063bb223020e61785e720e23a5c718302b8c8039c3517b0bb0632e31c23e8ef8ea5ce2a8391f1d08425af1a59d9b3d9367a63829eff5397b5e1e60f2704da36c4a02c2f98c9e0ad899af6f9f44bd6a1fdd3e967c77a8fed9999256c5e6cedc993b3020cc4e0b35c8fa25334eaf433778fad011b2ce75b78d347bba04b6b2e6340f4f8c27c6137307a5bbc78ca3e6bc447285256c1ac82571076910c967aff8527dbcd449019e3e3b29023c637c99b1fb9849d9c71cdaa04be09939161af046061cb68fefb07ba8532dd4301f42ad0abb8aafa27db42ae6266a50253310ae53ddb1ac5f7c8ad8e0033ca4c085a228869f2ea0c00920546801113a1665e22482c3b9338db4ca05711593b0337df0edad89039839034a2ba595d21823de34eb588b3f58ba14bc30c4c40e1a52a0822fd81c206108ae9c1e2030d13e79224c4cb5b30861a2cd2ba4873073c7e7e74eecdbe78f1964f2cc9d28d3819d6f2fc4547b93616e6036f1edd4a779e28bd6b14eb56b1ac6279e993bc6024cb59e51bb8e6c0971ceb7731e508cae18bd979c010e6b4ff5a9426a8f8f2a4a23ec4b0702cf077dd7f05c7395e56dfbb1fd7547d6078a32f2df735d1bf3b79f32f4f45721bdf26233d5b121db0b557dfa96c48bcd1587d3a9edd5aa9f2843b99cd8f704f5aa1ae95411e2aae22086f5871f62c478c1527b6864ae2dccc22c5b63f45e1e902b0938f45cacb9f2843ae56a958c32d1853c264415662282edc2372c565d6d493ad5db8f75cc9ad613f2b010651ca16d21cac02c4f4994a94a74e1dbd3c2b77b4351467afd31fab44e27bed49e116ceb8932b5f6d800c3e14927be34c89232be08a35b7c885934d79d9f1f2db721df3b7faa3d211681cf4f92564d29867c7b155287c8b0f6c88f30597b4e7c86f4ca03f25c46e4f6d3dbcf94337b2ff05cdf4596091c62d6cfb76356ab428c6f54d2714e690a2dadff117f8694d11bf8aed5658dd1ba57263eca34101d5d49143dad383599d7ace30036eb00c2fb2620cedce010e61b87c4f93803d32a9be851e85be863061d8802c43408e3021c3d8471010e636bd5d2a9dec1714aff561dab00a2050a374ac0a1cb3ce30538848189315bc399760040740fed5d0b03d13940b44afee8655c59f7192d6746452f7dff8c963eb0cdda634b4983e0168b2f40cca09e8e8782efa15685168a701b523223c9b70f20fe300388b9caa2cb9a850d58289cf02c9ec6b6010e6b508749c84b638587829a85cd0b506c439b922d165f58bccc9e0e117018333d46e6981ad4aa30e6db6aa1d2d9d17a6bccac81df524ab192f3374f44b72e7d6016273c38e03327f307f804677aac8f433bd9f9ee04e7434fe844eb4e82787cfb098f1e2fd42bebf264e74d50956c23a826d886947c3be781601b812734f4ed271f7820f8f1e1490f219c131e9d6aa7279ff8d2de42385aa801cb82c5db8f762aba8c94aee454245cad32e6053904fb094318660f38cd634b38773a9fb69e45193963c74c288bb40f06157de43b666234af5ea3d5ea58cb9a26b364c90d641989996ab7b90273a6b562ba1112d6ad3642050f22aaf830c6242c066bd5945902b56a116e3a7766571fb07c288030460ccbebdb1dd4648422ac2d880d60e8093b4a88be59d3a5e3a79ff64da73e630b5593fbd5432c9c9c1e9ff99ab039142bc29181841cb466688c16c599d8433a3b3dd4ddcdf3438340c00e82c398981f762a07348fd1a90eff8161eaa797e22a5c19fd7419575a9c91a8034828be3493902da5a0ad2a68eb8df00c0511f2031c6acf93c30327beb8a4a03fad19f167c6785a3b1a8f8bc286e0b5c455fbd4f05f1aef6dda299ea9d849d9de7de7f125ca1c963e6c9fdd93d2fc2da718b4d810f1657ae6337a663eedcf9c39c39929daaa3fea8ec599990ab12f6d47c84bc6e6095cd4e5841ea02056e783db676bfa7c0be9c96a74227cc74c787afbbd503094cc280458a75e63a96759474c6c7b00984d0a1c332d22437c698f3371a6661a9b29a734e6518616519d99eaa1996a9f2cc061bbbe7f1a8491453dc021289b4e41a93b81f5ed2d2f624beea00ee0c2a3c7a7efe2f3e8d25de44d8b3718dd65ba745749baade0501dcbad816483c7027b45297da66cd2c2a69cb464279591b6e23287ee2de07450e23498e33a3e5ae55a4cc53cf10d95b2a545fe9c850d35b0a056ac9c545250624c08549c98984ad823dd8edb465a66aba6f94e4b6f3e0238bd92488efb4e3ad52ebf9b27aa731467ce7d15a8cec51a7df33cf2930fe353d421c32e077d2a5180437cbac1f8e4d4d2de18336ae61a46d4cf41258ece5929f860054172724c833d9dc095ce0ad3343c591a16124fea36d2e5749a3b5309b0f40264dacaacc04d6badb59964bd3dfa8c7685b5b656cf1aa454a35a379f10622290bc7adc3c4167cc102022c2321143b47c3b045a159d46745a6db59536a055d2e54d94a72ccb43c454fb89c608cb9d0e60777e409ea81e7dda44a7339600cf8cee8d9869ee42ed63660d7dd194628a1339135bc2cec9b43929a59466d99454764f99d1cceb6985c44c49392497c8a2cc6551ab32ef5b83872c482f6df06ae85eb27028d30a130f472db428a3b9cc5eaabcb42f535ed697282f9dae4815798394a537bdf4186538978ea30c8bcb1c665e59011bac537d53f2520ee97bbec312e961bf1a2239446e88b06cb83544584158d260fc1a22339a45e61a229785436be4db6dac55611723b245524664e1e4e85c9e797b4838ad1af21365e28c79423c1f8f9443f2f124d89384bebd86884eab66163e3e00be1a223aad21a85bd31a62a455130b1f1fe6ab2962a4c78a5b03458f9f56cda18f0fe3ab29f2a3b3726ba0d0e969d5bcc2c77ff96a8af4b04eb7060a964faba6153e3ef8d514f1810247e5d64081f3a355b30a1ffffb6a8afc204ab9352d229e564da18f9fbf9a223cad22945bd32a6ab56a52e1e3dbf0d51469419173726ba0c8d969d59cc2c767f1d514d969c54c6e4d2b36a455530a1fff7e354586f08832120a1e425a35611fbffb6a8a0881e2a6746ba0b809d2aa59c5c7277d3545822cc1b7a6b584c89256cd287c7ceeab21b22489776b5a498ae4b46a42e1e36f5f4d911c5894912d988f56cda08fbfe2ab29e243c9bd352d2545705a35a9f8f82b5f4d119c57776b5a2f22b156cd277cfcd3574324d68a32b245a4a855d3091fdff4d51029fad96e4deba7488f56cdd7c757f96a8af4101add9a961011a256cd293efee8ab21426444bb352d23ac56cd267cfc94afa608cb1565644dcb75d3aac9848f8ff2d514b9018a32b205a4d3aa09f4f14dbe9a223a43f5d6b486e24bfc9a223c5a3597f0f1f157d30a7a7a6b5a413f280f0dd2aa7ad3ab0d2624455cf510f5417b7c531e2ae154e2841f034f9256f5121c4419f346c2e8e93387b40a4f23f3a755536565afe2cc64e960fad860a5955c2a99b2bbfb28bba7b4aabf7ad688fdb1494e9e6b2dff848565e2f492792365682f09a3bd8796bc77132b651209331153ed20780df7c0ac06fbc61bf29478b10689bc226fc94bcaf0aa1061f415bedd7b79304f28beb4df25a20ccfdb2f13df6e7d48195eb63b124667eb8385dbb0c1adc7ebb5f019ddc2397a6958230d36c9e49c2927ca5cb9ad5831bd6e4b9b724c2388aa7a8ae121d6c1436d07efbc8bcdd50ed9ba74c89f8bc793f5802af54aeae4175974169f4eee5e50c9e5021d6cb0416b53b49f92abe422bdf80b8c87305e3e17305e6e70172f0ee305e6e505c6512f30300e83060c8c1ca25e5e5cdce02e5e5e5e5ebc4be3859e5050a8d55c29594b5272719bf6b3dd20aa531fdd1b9466140da55e9017e4bd00e0a10704002f08009f07646df0ef85a6506bad4d714b5368a4509a425f68cadb3f17171717eb35cb9c83e3e09cec52da4f4afb399d5c6a3f36db60c75e4d3a8955dc445697dc67aa356c702e5e6972958fbe07d4abd8f282bccfbabc3458b8e963f1e233b5e9b3c1f4598fa6de72a8bd0eaf95fcd2e18b3115e38b3135bd1639e09053a70f05fc427ac31782a174f1859a351265a2bc40c8a71388e87da1bf378447bdf4edf82c5327248cd8b7c7c243bbd35d014b29a5f47cbe97c32c0d6948c392dc1135d8a552a9e4793b6a83a5ac35586af1c92f612fa9a8788a8a4a8aef78cd53547cc79f5cc597b07c3cf27afd11272831a5e1db4d3e1e791139e1c7875dec88137c1c0972fdcbc723413bcfc5ce0a38ec62271e4e9c0d14550126a413c261e7f14709a26fef888aba25d3ebf4dae315f003078e518040e8f3c72840d09241eba5d7d8fa964e62cf5b2e0d132a67b44dd2aa149c5b38a77d36498bcffecc2d1831d2c55270ce5c549ebabc37543e73ce452a3629acc1cf733103382209f2f3d8bb5883f244760dae78dd92cb6a150a0acae95158769c34882d912dea62d8cb26d9c6222d798e695734713495943caf0d7fec9ced4c3e8cddc43dc7b7003b1e63f7eecd77711acc3c97d2fbba33c4975e125fba185157e479f9b2a880ab875d8c86f69a02965efaa1c192cf989a16f4ed285d4c0a58e523128650f45b0c4cc94ba592cbd87adc1575aa63b21cc2c08033873e71268e97c399f3ed5f0c8659adead88f251f7237df6e8df40a27c121b45e572557e965837c97604270b825e724567111bd94a184d1f030ec46ea4b382417373f529206793458fae29b721a0c32f403e6830b93e0f1d4bd8f935865cae99429a7af1032938048afae0a6cd3e590df7555601be9a715c7ce9dbecef10a0a9bf8e94361ece09f7c059f48278fb8fd4472d4c964c54d482b39449d4a3cbaa225d8c4a57318c3b0501470886128893f9474f0b19b84289969c0c7b908192bf168d5e728ec217613af1f86d130c9210a671af04b384f3fc4b09e95544d5e9fe2dd069ba9ce5e1f2691f38261c759f39a040f22640ea9ab38373b2f085b9e6c7f34987383905efdde1bd5a5f45abde6120f286059bd14571b2ca6da4ba52d6eb092774198046bb03d18e7e17b238a10dcfcf43a338a269770841aec39738947834d0aaa61835bb7af156049f46117537959202a70761bb97b02f67c36115f7ae23801875d2c7fd62589decb4a9ffcd80e7e8d7d36b0f45726be3aed559c81a19d755250af62f789f4ead0c6621d51ab5692b4ca45023a399e725697d5290ef02912df2dd0158b767e5ae71bc5e957445604122f2b8ac415e5a3cf45293bc64a32017acd431bdb841aec9a2386f92ef679b3e62aced01c6a6029cbfc2679838513c7b4f98a9d6f67a6dabf9fb98beda478d33c05aed9063b76e96117e3e464718ffed6c5c2930f274e48bf0d96d2c580a87f21ccb3f0602ccee1d0a97cf44b38bd8a33abd88ade50afe2ccc9871ecc2beaf660edc1fa8910c61bd590c3933f71ee9a76f0897b79f26810480cdf809831328ef47892c3d6d1c050027a06e6b9fc4e7cc63c29b9f7dd6b274e17b379f298399afb482531d2c4e1d14981a7cec499ab0db6c1700a107f3658cc33bd4c2f6c6bf5bc52a9542a799e2db18c264e47d429540cf76278d704fb436a59e6792409eb10bcc4308961d2a518e40ac364eac73557315ceee0d265ac2551945171e9320c5186871e64c9655c82a35725d74cb526fd01518607e4106822868832262ef3cbf39173de6779ec0fcfdb603606fbf6926bae4c39da8fa9e45a715c729d5cf3804a3e2f0dbbe379a5d2677f347882a2834b930d6672148d741b136cc8463af6f81b2cca68b1f8c284d85c21a1fd4819d665ca89391f6a1d129db75bd7f5be16c527ede64571937e15158d87af07964b737215af41cba6a16c829d8052be18304a8089e13bece0726687183bf0e03ac4d8c17b26eac0c26f8d1d5d43c5b51d7c872f461dc2143fb95f1acd55fccb2d2ecd0e2242f81428a48b8bee622a2a5d4ca58ba97431952ea6d2c554ba984a0e55fce4a1f6e0a5d13cc5539766479157f1942f8c507c4aeb6d544a6e1a2a9db229e8868a6b0e5e1a2a39949f924d43d90443f964eca4be49964b904a08a0c3d733a6214dba29a8c106db5d2e0df692bbb8344ad93484b309160353d219213fdc60dfda4852a939d55037063081d6791bd390a953020e4daf6ec9375157d44981435317fb76d3d002e6aa25a6648c972f5e6f7abdde8405a3650fb4c065e9341ee10c3cffe2db497f842543be5d237dd851ea95da506fa7a5d860acad267ac4196310b3ef0d1a414cbbca987d6f507b6f744b19190fa61fb2c874263bd7a04cd428437a8c5e8f80861d245248c3b7bbf8880426f4f4a564d58ab91c3b62d486d8b63366e95381c7524a1d92e6a0ad1ae02a5b2e8734d88a3936b88d281dc5a29e2e87b4629cb2256dabc12132b3590e69499f2c5b1f0e9f21adb2de9a755b334af2a8611c8ab9020dd2dcb970488756440031532bfaf351fe6850f27c2fd8504c892402626ad3e7c7e747126124a853ed43423e34d20161cb479bead2a59090e746a5c900b589596619a4c1a121223ad59e041c5f355b1583703f9e7e464821f38bad267ab04a435828b660831ad534cd7ad8752feaf42c832f4e5e34885b7e3ef8edf1c14f020a7e47c8f2f2e3cd8cec1e167492b7a516995d37270b4fd6cf689312f832e1c3866d47f8b05b5cebc3de21e5e0c3666d3c1f4a58f7310a960ffb310a96131fca9d12d08f51b044f06184651fa360e17c187db278b4c7956cc594f49822981838747a6157c4f8740241983066b6e6c728584c7c285b4b740efd4d4ae0fa310a16eb43c96a9610b89b27a273c06756ab0f0920e090c6e20542091f21324883fdc5d827842908a98e04c98f8f429042cfc3f8280429b0664b099c398d3548748325abde13c82e09ee8e802d121e66c021058a459ac515ddc1c389a3439c3a3c783877627838797a5045ef565cd11c3cec9f8ffdfaf8c2c3867d1cfa08c3c36e565ccd160f1ba785ebb878d83b61f384ddf36074d98aabd913ca9f8f31463924631f65515c754a94391fa5cec71857449f1157ed797ce118874c11747a04898a1ee9811e89418ff0408fec408fe8408fc0507ae4080c7a0400f4c80b3df2821ec9811ec1811e49e558922337606cc90d31221731252e31a09658508b98508ed920028f988a46be1cc60cf11cc20861914330880d39f41e3115bf864c041f2cb387083f50b327968408372b624b566244a798129518504a2c0825261453271b4678c454346292c39821a61cc20829e5100c8273e83dbc1c6a3e622a3e29f7fc88a9f837f7d8184292988a466eba1cc62cd97208431453a0122e870ea4e5500b8aa9f8231b43108aa9f8590e4f3f5b01dcec6eeb43968f5fd87d5b7fedd2800dcedc5ded0809f313e607206a86c081c44d8e0f1e21ad24adea9ffddd3cb1b5bf68d5bc34d63b87f9a977b27378b2cd8bb5af3fc208a9378d069efa8b9c20fa690677bee5bd813f2b42becdfc74693cab76de20c006a76b39a42d3feb11f2cfd0ac7a6fe017a2dd16c18dba1c168bc5c44ccc3cfb6c96cd2c17c1e2e9edb44f7ef6bd68703a4b8373fa6d010e5ba884bd8db4b369adf59e320bb56ba346e6ed01782dab355ba05fb48032767978dd9c27a7bdd23a6739812d1a3db1f89cf364021d869941e0624be8f54c02122acda6d55c30168e6b52d84c75758ec2e6aaeaccaa033b9d30a653088755671b8d70e501c2c3e298b27093dce29f7b0b778e73f9751ce72d92005d277d61059ee45c46d39132aae437872821b073a852be81736c90bbd24921c9577432cbeb9c775d57ca37bc4ceab88f73d31792fca4c5bd16e772e727e732790d209ff33c5e03c86ff1197dbff0ba974f1eaf102dce659b16f7b24d0be77284b7257bd7826b91b91c7eee2b2cdc84859bb03061b172bd23b9c6b15871ed067173b8e239e69b849233c936f80af7d9d0755cd7755cc771d2243dc5a52cc92facc04b1792bbb8b8ecdcc5e4f2964aa552e9725388c9b5fc48de75a41c3b4779a9647269f2bcb0e35cdeabe3805c9e2bc5eea4a0a07c3bdeeef8d0579dc82e9f9c64784e9e5645795900e4db2b9028136b0e00a45382cb68e5e29ad6591e9706c805f436ea91e6ad396acb10c847d0b7c15fef10b0b9aa3e42d8960087db4fd7795f1260adbb3792c5ea9678ee76f5f31cda1b91033fbd521a2109115c424cb57d820acf240f89a3499d99ca91399e1265b8acd2e96235179506322a0fc77da3a71f102ba5ed1d9ed6d19e7e4072e374566b465dcb624543435aa5be01719171a5ce659d51ae52ef461df7cd17cce5cabaee52bf55ebec8ee7c2d573f1b8f864eb061f9f8e1b695b966136f494f0ed97746950275d1bfdd5394eb31f1017d567662e6e00c2d29591aae7459918a377388607ac46b03bb57e75f474563a29ceaa7dfa6d47884988a97617db14f547cad87e240c2354237cbbf5eae2cbbed6e4886eb6d2998da6cf951598baa6ac3f93b690ee99b4ca496bb356a0ba18654af529dd7e286d1927ada49c178e2b1601217a4c9c5babb2914fe4509366db5c41d54029a59e69239fd15209b873ae45c77d406eb837b870cee59333377cf3a7d59a2f57832e972fce38311ad59f6c87c577df14925c9eb01f12197ea4e0b415d0c8ae8f7b82ead6fa4acda4a2e9f2912ab5928665b9346abe27c0d2b91a58b87c976767c7a7a747e594e2f2753336541b587c1c0e6ba658532727274565d2a0d6b92c7d602bb36dd37c9be15cf8e6e2e6b8c4ba91e6db966999a58e32698bcee51bb5742e9f36a3514d9ab4882e9aa4697973f9b29916f736b16d2e9f9db19f8f9e9835e8aae0f51d7568df4e5c3c97cb5767b80f888bdfdcad9a81b878bc40daa77dda3553391325052928d8546e8dab83c3ca13654e27939a14d991733236e7120acc39f7a273f9e6ccfde83d41e6a391cdae094c9786cda173f97a469b7e9dc0d26734ed99a44c2a32418a44aace52830d539ee6ca2573e1d2e59bb1a199725164a56c4aa994b2e7ccdb8f0d2cba132e6d3f714b425cb9f07617b9feb878b855e1c2c36d0a179ab6d3a367adf196a0ced924c0218dfd4819d50872456332e5e2540c5f902b97ecb9cae0b9ee083c97cc267ee342ab76eb6ec6792e9354c2a6d257dfe4037283cb37b7a0ce6eb3d24927901b5c73f12d3183c5ba21d39c99a2383385b33353ad737374bebdba8284a2bbde2469296f3f9292e81cd1cc670bdff91528a8c1ed3573457079fc6cae1b02af665a2d7dfd5a9cb93c925c104ccfba16978bc35e662a69d46e9d0b0ca6a3537966e591d2e5dbbaae3cb058fd5aeba85ab7d746ff8ef6966e9e5d1ad6330e1f214e11c1bc252182eda56d0911dcaa88a9768faf0dc8d2d8cc54b36fca82645e7978a810da13b34d08165f92b8a4004485206de2d1a5b123397dbb369af4cddfa673d7467fe69cd6799f11397014227b2eca23d4ed0a5fc87266fa8ed03ef3d277842665d09884d155e7772e0f2cb97c3f802dcd341a2b7df15b4619bb33398a65f5ecc83b0f4f2fb8945af18762adedf07792d90e7f269ad6e1cf34da46f82b6d1f7e8ee3bccfb9eb3adae18ff4f58b3f495c0d02096d41b75efcdd6c449a244ac25ff7dbcbdb5e9be7d10e7fdc3b9678a4cd0c638c3bfc6d2fbfede7db8a106124e1dba4882fed4788607b37b2a59a956669f4391b99b669a2a65192cdb50135d8dabb6604198504b7cd6582bfec4ff067bf0271e1f403e2e2c2858b8b8b8c0333e392b79f930e7ff5b30f888bd30f880bdfba91f6cd0fc807e4860378c6c57160665c78caf6a5e0cdf5f8a39fb71fdae16fbef71d8125a644ad7d4070f00cf701c1819971718f12a168915679b2ada84122a058e5992bcfa5e5cfbeea436dae3ef1454619d7c8696f1f569fea5329a772696c39629ab77ab27269a811daa22dbcfd1c819f3af71d817f7af71d813f1b423d80a587d4a76ea86689b24975c5a5799b47d9a4db8a7b9bd836140aeac40402a4ead257741277bee2d2b8be7235bf9fdde13427f9f685dd6f9ac66d5609ac692ef16d028ffc665309f048dedb84bc5c8e78e4df2dc08e1fdd1b23b7c1a3ef88bed152ca887d85a02c96bdb7896de33e790465b11a9cefb91c79cf51de7ee24b4bf9db77f2de0fb7a2e3be38b23f5ad56901e83a002edecd684e4375d5a76543c341c4e623dfbe1c7e2b10db7e1a3ccd2edf6a77deb79fd1f6b3fd64f9e707888b6f3fad8a2edb8f8eed0709ce458aca295252759a1a3b5adc20eb5481ce14a20c0a93305a7e1c580020b6d4e8b3cfde20d620824022682b1274106152867dc114be1d07b73d3f73a003c8e1cee05ebcb8346af6f4b33bd6ee642fea0f2f6e8c7d69c24efdfa0171f1e96277ecce4f0d02e3d6a83c3dadaa1b759d6a4f0d52bdf234d834d634a6830e97463dbd943548831df33aa4b6aa0f48352de74b83031368c0042dc6044e6f0300182c30ee0906a6777edcdb04d63eb4f687e5d9c1eeecc0c336d278a834d3c5ad0176da48dbb84ebb1ac9c35ac9a4999ca0682d7920658e5b956de6c15ab44482135c6c09fd09e9b1690f198833f2ab915c8e317581d15397f786c67d5f8d64b1728cf106414301fa85251c7fc76fdde87ae70ab81de7521b7911320d7fc339bee9b6ead6d61ad54fcfc367ce7159d765d56d8f7aeb6eee92b47c447f56efbd313bbb3548b7467539c36559963113428dd306e52937b75187124f71a5796bb9a5532d73b768f2b452ba3493c601dfd3599ff3ba1865b6516c0660c7735146ef782eebb2cf52afb37d529fd333ea3728cb08eb46c0909ee394475df6dd909ba5d1f174bb37dabd6f103bb85b837a7644bfb533baf30e2be7149e17001a80e99b4669adb5564dd3b46d661e663ffbe7fc49a38ccce71c8d628b37cb69d6794284f87224be20b9b7094c3f9446de48e5b28b23379fd1f4b7ea7466ac51cde58f5a29a5aed92051343d9bd9bde1bf002dbeb40cdb2f46adb39580258d039e52af0741404b4c80c110f42d3e4201861e3c51ce7f1fa1008392f788823edc60a109e8559228c9d4c287dde3b082723e1e096221e1e391270cf9ed0815b18f47a88031e1e3112a9af8cd84f5ed4848960e0ee0d01a610287b4f5f1dee09ad6901e3a2c1c2200388cc3f01707fdf39c188f2549e267b7c159f8f5ce49cef9e62bfcd58a1f3f7efcf8f157fce42657f1af2982e2268e3d7eb803474a5923cce3959a47a7d43394cc35a69f78749818a157534e4bab4aae064b409d8aeecd2154600c9b2bed65336b03ef797983713fc0a69cb9d25e34d20740c0f20738dc5caf0e8739ea7b2eb3bda1acd0f40ac273fb6ab06e30d3abe64d681bc226a056791988124f488fe12dafa099825a85ba51653ef97083d99d5645fbc3bb343c19e2781b53ced4348c4d39259729a7e432e584a61c3cd42a1bebd54b25cf732f873870bc75d9fa7083411106a1696da8799e87a0e7218c97a94b2c64e379b59e57dad1d01a2cb9626260604030740f4f21ce365e0a513c64ad6baeb034e5bc0087db06d37ee66af433727d3b7e6db04d681b6ad0c63c180ca664488912424a3ca0180cf67abd866041b4a3b693b7864850bfb25b4324e855298e4b560e4e7c42908f440082f32c3e1e79028f17ea546f4abedd731e44507243042d2c79194f60070a01167b180c79eea5a3f501e0e5175e90912577091c7631160b0727a7470f2238af1a222f95c45ef572b843d2d891790087da6b532265c86fc7c101a6051c6a2fe91b6c7a360bf8c43d0f37180d2f9fc05a15e3091a700d11184cc99012258494c4622f1d84c11a8cff72f17a0dc18284940c05bd5c6e0d911711a0a016af06a30aa747abbe86484e8335447adc0d608f87976b88f4904782602f3dc75b4304a7c11a22f139165e4384d5aaf8354472707a155996699a9689c069ad5ce98431d53a5a042eca86b1b8d3349693fc5e7449beeba6e1a753874fea0728a52794525a6bad95525b6b96d96a29a5543a95814798b731465a6a40839afbc989bb34b216f085f518a39432d2c8b22c8bf5a45356cbb2381a699966a5e6d6718c2ddd5e41d33c6a3ea3b32e9356669bccdbb3ea599fe00635f969da48d3368edb469ba669da6834d2685b2148ac3558718355caace4d5b6d464cbaedd1577aa66d6eed0b4cc66147feda6b66d7b2dbad9791c88889610fa171f7f96f0f9ecd239b598c40295512a7929259d734e4aa5943136edd951c66c83a91431cb222d519a65d45a5b279d4b7897886870f3303b39b18c46a35137f56e4ae99c5a6b54b3b48a61646d585b2b8ba914db5a3bd2346d34b2d666a34ccb6cad2fe8087bd9735a3beb06ec6c6b6de6d6adb5f2ebd795762d62a7f5f65a67694eebb5d66aed9cb39b6619adb467d357466d4ba9699c66b32fbea6f98c6e3541f49de63de3a4d4da2ca394ce69eba4d3470d11d88b72b2144e2d736ebb34b293134bab6474cdb99194a34ff32cd3b45a2bcd2cad96f5b38124dff2f187a5f3db277f8549051875837ab6d93cf33003e97c7acc759463abb66d8bb49479db2cf3e7a2bc231ffab9e5cc6b88bca24639ad868892211a366636dbe08a048e8b57c0d1b96e1c296d12eac677b6800e6b89922141413f4a2cf9364312ebd3fa8cf446196de4d169bdd1590568c0af289b5135c3c468f046ea61ae8d7e99bd79bc0087dda3757ab409aa0f4a2bfd018898d829230dca9e545252b8e363a6c7bc68100818559da26a9ebe93f9808b2d615f2d37a76524d2d7cf75d1a3fc4269839e8f48f88824c86b74bffefb04cfe31ff9c8efc82fc92fc94723d770467e351f917cf48262e96ab081857fd9094cefcd2cb7468d2c728b161717e1ecaac0a1fc111d5dcf9c741d7ccd4734a3aee68d50d43327e52e4451964b435e1a37c7e4906fe3d2b5b8742e6e005338e490830d35b0b060bc6265e5dea00d6aa90ebc37bc1f3997ea70b834a41f3997838c4dc23e9f6b19d32a9c471e35cfdc575c1a38a334cdb59287d8350d6b1f4ad31c7c9363cd94833ea769ae951c3ba77dd57109676e3af1d2493629693ea363763c76e9a5929b429486ba34505aa601df7aed469a5fa7f17ef45c0e1d0e39742f5eba0b001817a6bb3ae04f9bcfbd7497527fd14247a3114eb9346ed8467ece0e092323ad1a65f9d3516e10d747a38c22b59156a1463f9d24cadc1bd7491935ca34e08f7c341ad2aae9dd6ad5bc3e25c9479a8fae36d24179ea9dcb53e79c83e960c0743aecc0438c1e54258fe4239fdedde939491b8d48238d7449238f7477b837bcf79ce3a18b716948ef39d703a76ad9246fd5dd7e2ea61b7d24ce07ed9b4fba3450a3d157fd7ede17a3803347913ccb480efe752fa3483246835267767d8b907f5f80337cd22a1bec9a4fef6e94198dbce478e4a64f3ee90aa1f948f391738d6934133e423ece369a8fbcf4859a7bde5d1a928f9cbb37b4ebd7b95803472436d8f968858f489ef0794ea68bad4275f1457afcf0bebc315f6b5066d77cbe7cda7595d4daa84938871a782d8f3e3a6da5dcc4ed5d27ddac759fcecfa1ffc24e6b708749b64cc1c596d0c2be3a976530eb5633612fd99c39768e56dbc928b312008bc3a534d17aea2a2b1d6d950294043dfd4092a76e35f73cbf2a7e5554fca4b9947e72039c8f483e22b141eb5b74ceb9689173b8c29d050b9696951515475d1a2a27575139a154541c7c163fa9b0e4a0bf12836241b17cf257748c7f5a5959f9627c09577c3aea0a417d251f8143276594e15edcc88242a5789edb954b23adf57bc353f1ee95cd03e5d6e857c9f1a4e2d6636254aedfeb9d9ffce4d23865d4cd34e05fbf39367857f8c9b9fb8dfcf4ade8a2803b3f79e7b1c1eee4e0a37c458e0dde8c3a651af051a32f077d9598f8225532ea08fa2a5e84f72a7eafab7c31be842a425057c947c847f11b6e0d1437712ed5d99667adcf48999b64995f19e090027d963914e4676e02445fd4ada7a4e430c557384dd2aaf0baab5536d73dcf9cfe48cd3dcd3db7d73df77866c6c1903187f2643c45c8f7fcbe0077e1c957dce6e42a5106e599f70ab87b7dc54fa84f3ebe42a8f855f1eb1c0f9dccbeeb5c0fdd5f8f716ba0780ed9c9286303c54d36d489c9fde29fbeebf4d2a8f875d417bfde22e49ffc7e47c83fa1e47045ccae0d9b93e790afe22bf29d010e3be7333fe5951c9e7ab44a25db9c5cc5333731dd1b27955cc306c5ef0cd27079b2ccfb47169e38e799cd894bcfdc746934975eba374eb8d99d8cb392ac6b366b9786b5de4519cfad4b0d6f51defa8d2b099ba99b71c05c2098f3b822d3b4eb1a4a4899f8cc4dbe1cf229f58c47abe267948998ca1c049fb929879a093e5bc1678e33bd892f5926e550fbdee172fba8eee3c1486115f635e665e69b8fb24c037e8cc79f6599a665ad661b7b92df92d85bd7fc79615bdf1eb64cefe2caf26833c69cf4e79b4aa7326679e64a0e75aa5d8b7da861d68907f6a1b6810fb597b9795c19e0688410b89ac7b7d321ad8ad47f08d5b12e2a042e46992264ec51ed1ef7a255917a485134d3800f3e0501d15fea33a33ad3804f3d26f1d2b3cc26731a90fab4092d80a2b1b9086923bdc11b84f4ce2f766872f2e0628d204c28f9d2c72898882612e0f6e8ed474820dc4193a5d4e16200a89374dedf7a104ba7a6cb302287e61d921ee07c9639750d3c89e45c7f47685fc44403ebab2fc0882f870cedd7a1f5cc7a9865994d36bd00f50af1bdcd20d64ed85f3b9d30b69aa6f99491bafc0afee55a469ea1dab3160fb03c122037dff211890c602f035754c010ea36739606a71023b7c9324b08fec86791bd35eaa5716380437a7fe050037fd53ee72469ded2aacc2baa7d737faf2dd3bbec9685c442ca2d30c0a81a35e46b19d5aeb9fc7efaf2ed15d2a6e39328d3514a354a297549a097be51243c189a21cb4f9f487800fb39b5b9c2f4b3a99e434e7f417d3a4c8c28a3f9f41ea28cf522bccc479f2604754bdd526a394c7fb695b192ba2f9c78759de73cea9ca6c5e85c6cefb25a7ba6baa91336b0eca1d3aaea6d3bce4b9fc9b38f73932f73c99aab92ed3ec075d9cee1c9de6be98f36699a33eb35c936d32f7542ca38f13afdba451142432965ce67572ddbcc0ee266ea84e5bc65ab55248f73b4ddc8da6dbe2615602ebbd7c689777742f2534a46c95c78e2d99abe6ba3ba8dcda1dcaa372b9bb4e6d0023ba813d909297b393ce5511efdf4ecdacdd68184b53f3eac3773da83faf83087bd33d7ccb930470ea9ce739ea3bf9d5ab7d6723976669bf6ebd6c356f296d5426f6d0e73681ac63937aeb7cfe81c35bbdf53e372f51e1246667293cfc4339f2bd9a30d3153d4a3a95943cc956cdd7c0a4f35778eb06dc324efcd71b6a9ee797b0e658ba6bab76d19457213cf64cee98431ac3d0b4b26d9a4029c79e7d6c35eaf10dd6f2e733ca71e75d3171bf4727b49c2e886bbd32987392a75da39bd34ba4a227df16b7542cac8720efa9e87393a732fdbd43047fded07c7d58c99a231aaee9643f959ada496da502bf976bcc19c9032e437a53aa3cfbf9606a9df0ffc5eac60e8740d94a798921e5d9b5daba82631fd5a1bf63587994faf3b38e7260a88f8d66dc4b7d9b483ebf785feed5937fb8b7fbb2cebb2cce76acbf4f4daacf189b631bff6eadcec1c14e9e2075512fdf4ee495170820fbc8c2d1f18f2d6671420e879998f51f0207a8e76b4c6582bad95de17e02893797ba55e84f742589f517c20c85b9fd38b90f3c50b23a6c7f8123d44459b5131d3801f3d66b0c118da2e0a78baf4f8a1a4c7e933d3805f33f8f473bc882f323a97c9d89d5592a582177b7cfca1ee5e45197248c268cfdd924352868c93cea638ed02fc336d9e722872dd5130934eb54e19a104f2e333a7746a8e73a8699a111e5b97c3e6b2ae03fd271df99c99383f9dd3bac965d98a2fd3af0bb0754ed6d8a4708711ad2984a3ab5fe83dcd7248c6e9498d0a4d73a9b9746e4e23adca7e2e8d0ce286ac5a57fbad75695d663c56baa887584ab7d2a7de1b7293526e595e9aa965d67ab55e7570644ccf7e0290b9e65c9431a3e800cf57b72553a9e42d2c26b7866c7d96c3eeb3cc7d374f84d5db065f1a69addf1b75e4d56dad39cc5cf6cf98dda905e607206a86c081c44d8e0f9e1394be1d4e37d252bad82a1babb2d13c0b2f8dec808fbc414ccfb42b47dd0d9b299d327625dd2c70b1259448ce80e40c3d01ad7ea8f46c35b64d2d1d9a11240020005314000028140c878482e1783ca2c98a750f14000e8baa5478521ac84110c3103208214304040000000000329a2409ea4945a1d20af66fb753dd87c868d9582d74e995b8201d98e32089015d2822a1704985e0cad3819098fb251646c7521db3aaea959c252564232bd6fff703d9f9a9d87eae7db4c781af93e8bb04f272c570e08feb1c0b386726f611fceaa641a6f12dc245fe566910d5a508cb04b6a3410a209f208ee4cc4a894fc8aaee752a2e25054d56e6deca9029e8abc7608a993b74f69e9f4d3b832bbecc802d6abbcf4344b3eb19b06d24f48ce47a790a62fb7c1ab7123539687163538d57c68157abad89bf1b450af57574192adb4f16eaceb09a20081a6ec2719e101b35ffffb49fcab985dda1ef17a6ae996fea730cdcfea9e54e84654d04c9c4c191642a5d6b145df14dd50655358cc37531d467d9d28a064f5b110c662bddcb4c0d961e6088ec420a2a98b65afe932db1b4be73abc9d73f5e35743b536fa4d9280edbc7fac0d71dbf6950d6a2d114ebed720a066d1153673d68b240476fa3854276095f521df21f5271d47ddf1e5c04d9c0f1ff7ac582772edd67b3305eedaffac492868b2ca424524069cd6a63b88d4a6521dfead1040b67d946d0bb32f87b8b9cd9708be896c228af0c2bdc97e8da326cf7361f185a868f2c2ad362df743d77dd966c36f336b7804c43d4f183960c6b8f605bb33b842bf183e821549b0734bb888dcb4474fb6e7d99681d015468a26f01b9e73a20040256c430819f32064a6389c844a4008f4b93daa833c5ba22042152934f03557001b4eb610e7f0709a8dbbad4f43431679de29d8881334604908847bb814a93ea32f6fdbb3ad458d19d16b8de8ccfaf8f4b6d7cf02e04bd437276bd99dc61d3dbff534dce00a724b850554f4bca0f5525cd00718e807bb493d57dd6d28e19def6ed4dcfd3d639bc2131d4767bf1e9a69602aa07209d976e38e7cd09bdd06218c7a82295d2530eb56481de063e7f39c39273de5d40895ee5b4b25560e12afd1b3390a09d55217d8b4f4e2bcc55babdfdcc66666e58543fc6270e6ec32f59e7629b2978690e4331014d512486ca7bcbcd6e252b202e76bb7a74094419e58ce83b13af6204330840a485228bd20305f192110924f67c5d2d9132b4a6c484e1eb94dce20b4b270af854b9c83e32dd21e867a0dbfc89031070d96f0112de0574990a7c24c92da89960e12a5560ef45e02f1b063e532a686b41fd802dedca43dee607abb2818382d31d13e37a9a9c8346101cc86aff76dfa33b89cf6fdf4bec049de5c8932c554369188cdc42ac4decc4b506ca8134003483673096b84c40b11864a88416b016e169d66da4babc915c7f5a5dc6f60c00367729381a119c317c45df837b9a720226ab82a7c4f837a0878dceb3ed94ad4f562598bd0c67d084f0c9b0469638d647650472e8f6e9e0561bb24bbd5723ce2d714b17842fa52f839c2220c07a3cca2789867fb45cb3113bf3e27aecf0623e1ad17245a765e4dbc2cb2be98d76fd8b6a4aa9c9cd40d640529f1013133b62abd8f02a36d09a74bc03e1701f886d1f423798fcb982bda2aaffc03309720cacbf142d368b2f083d7afcca0777b91d577a1a18ce61d5175c180a1591a68535a55b844f0e1344ea5a1e9c5cdefec891bc60ddbd48a4d40820c1f98501fe3daa5122d74c42e0321a01dd214d35238b8560a9470278b52d041ec32172491e43ab01a777e814f9062d5e2f17ca7748b05622e39fbd06887bef50efaf2eb2f1a117e56f6d22931e7091bc875b4a18f5a2ba0fa381dd733e06dc19f0add09fb2f33c392527d04df4d19868305a7c260d1949a654759ccfeffb98418ea8b731c93779f0633764a0a9ccc568e6665cb923e07a25fe2429f1cdf29c66506a3223913b32e4f9ce20fda88e2dd5acb5d0b5f35117298404c31e2802a2e8a40c1ab7caca18fc825c2f9994e2baf848b12aa5b23278e3594c31a1f1991e860c132bd1c31e56d3027f9561e7d4dbac443b4f5a52ffe7e98525c20291c3e3857d0bb973240b8f530421b0149b144a847c9068a1849796be62f74483b731192d8df7f8ede7407f68c53b8398f1178528201207b3604c871d77f562269d7569356ddf5bd20866b2237f972611b0a3d00f21469684fdfa39b34986f26ecf58d2379a9dadb4d5cbd02ee6cdd129bc87148dda4c89c6d500694a95178e99d1b7f63cc1ff0858b63b5d75a765c3c185488e289a67a9c79d511d85071c82ab7fd2ba3fb968655f9fedb386d34f7e58fe250ad3e5d5b22149f1a94458b653ead46afd70b307a099817fa4e68bb15a8b7d0606b8a66d1c579d3f6514dac4eef0b5ebbdf11de9532b978e922d0bfd987b3648a08bb6e84d2c2a9af9d9a2eb8c634c1ad41f791d2da9e0500f77cb775a77ae7afe49fdb739876fcb40ad232019d73677511533c236756550d209ecc10888e215e7f8c3a88ef26d856d574fffcb515803b1b4118b354c07a066f8e208a65cf226cac827d623efabc497689424fc45a2cb771856bc22c151d944b7e5958ca157fde0c788fdab32a94b1c579e57a89928eef9e46de536511edf95415a1cda122503b39c3d42614cde5a1ce47fc85ad365c4ee4bfa47291bfcf9dd99e6ed81935faea1376b45da63b0cce998dc25aa74590616ee9496f3778f9dc96f6a3e73d24c5e690faa0031f1fe267036e2b3524c6427fc4e8f25392bde43bf4c384e03c43354fa890dd53f0c68cb782dcbb30b55bbcb8f374d3cb17da22df486d97db554725cac12a5b99aca312784484ce0d2f0210808459e874c5667ff454e45f078ffb4acd4333097c958ffa6874becbea38807184652c6af360fd0c8833c502c6c3238b776dc874897d4cd1a04321d3c489ce2199037a0f5b62606b412f457b2be33299ae47e3bc76674360c55d092e304360746468f072d4ef30d5565339d7eee4109a70fec57877099223c1559a3ddaf657445aba420fe2c79d58911ac871cc404b0a7d1d23b0d7cd5df824baf903daf4828bd9c8fae4458c2f90f405ed9ece015e673d1c999d8a8195cd40793f781510e664185f4c1a8672b816e4dcf87542d462a6b5ceb3576082a894eeac064f6124d943da603adf94976a5a173c5d3375da6df8cf4744d7d74c47e7454610b0a8c5b45e7bf6f4c2f3fa40e3fe84a95390cfaffc1092d0ec1000a50e16f9012fbac3bd979138e604cab826aa5c23abcf3cd3b887900018228d3440759b465e606e8c5872651c31e87ec611c0e477a62f9510d7aad3f88f427f2678be76ef2d08451e03b8a7cd4a99a10135d789d4bb1f1574c5e01d6f1ad10058e7ce45f877fda81fdce01971ad08350d240b7d715d4e4dd46733e2eb3f7616559c09c907a2aee9796281ee0ca81ec2992a533e1e8a7b496266682ccb8710637a0990f647a7df49864d9ab288ed63c024409f34264f9e6c4b7ac1e174cd7d85588ce16597c474edd000b13e20722fe0d339643b6134cd46f6903eac63fa8b8d672ee21786b240d2b5b8cea0d79400d0aaaec934a147fbd039c75ed766e34c2259d4cca91c96ad2becf9c0f463fa95e2fb5a7b51b719fc63cae799e86cd70cbf6016ecc271f2ead29eef6e69813c61acccceae70188ae74db2ce23a19e4138090959fe5bae9d4be671eed5d8df76effa285d1c7f642cbc2774d815c8b3ab7123605e42397aa754e846ef2444606c0c9d34161b0a8366dde5cc48a6e75327cd2336d21e00151b38b9211f7cee57302ac310b23bcac2b35c5b6040d90124211f2cea78d40608cff48c9b32726507817661a6079421cfd89efa699906395135d33f85d19659a3197211c220d2bbf87ba80f6ca683dd47b0124efa7dc0f86fd76968a1d4498c9121f5e7ebe053bfc94ee0dfd050df60ac1315372e38234bee398b4097a1b57438e9e65a67000456930762b0c539a72ecfb43454255724048e17de46b1461dbb46537e0b361c9a7d34777051c730634bc054d06508af1a054d52c3d45e560062a4c300df0022257a82b9c61c266a631740b458b2959c3c4571798e338a46cf03f5d5185fe7e1f66b20f1ab12caeed231b02c83911410ee659043e78b1804c5f7c176b0a72abb81377d443201717f7090f12131bbe568e49b663f2bb9c962cacba00990c66dad60858a4335bb663b1c54dfae66eeede0ffdbbdb9f9327e9242beef1dee9b8f7b99cb476afa6223847b502db45cb5416b377675ea907badaedc032d0790f049c05bbabd116e4e434545e119ceb212389a0794a5be6a46c3d2eefcf9e2d5148b9f98dab77f329fa645afb232c9214450838c96dce24402bbd64f5bfe267e74db38cc88e26bc0b13c97b87f79b270930001a7fe3af4854654110dd4aa3dbe00bf1315a865c5e3fab85ce94a038bd125000e7de461509c8aa6f4c4f3a3f7a5f132b4a1641dc70f936d4675ee08510399d071931008954592fad892aca1651bd6ad8db16a64e54dbc24bc882a5bb5d4bf1261cc70d3165af4164cb6421c329c0b5cbd2633b8d33a6a88efc7d4fe1e69ecbd4974387c490bcd389a17a6d302b882f07af99c95cf78972465abe3d91957c5fe9e394b8a80b4e84a23bb27adbc20eb0737ef7bb2ae05618d75260d7c130f0b5b0d4a5e8811dcc5c61b2320f75b91f802bd2ef5c66f726f0f9d97c3e902bcff993ba23f663e07c2371d7f16986b65ddc7a72f5c09e04e04fb571406265f106fc59f59ecb26459bdbdec918e57809b137b10a3920280c94b0c4da433b738410812c444e432226e93de543526ef2911e800ed7e487bfc26fe5235ac707c2f9515ce082ad5622fc118ceb8e2227d920ec4666122f10a6e89beed4c2fc88cf6d68a1437e1799ee454a5d6922bb5045a803b7d2abf6ec0142c9579e783913504f05d1936daa05e2862b9eb03e56667f3cff24185d42ee43acf614b7c18aa896a442f86428a88b1940436c87f4867746dc782d1b958ea214b4925bf35810ec561a6db061a4e8630332187d88643220310621da70c6906838e2dd94a20904c352afc42b86f9f3b0b7e2e2fb5a6f52830f0547f81bc1d2b5bc03e66d670320805ed769de4e10eca311551137b773c5508822c711f237b5a30eeed66ed70e2005568d9ee1338743af30dbce5a281142434f983d7e49e9f8832dccc85bc0feab0dfd2b255a2a340819af25a2b34e192e6be87d2dbdd616b575c5beb12244c7851a5b24178b60c7d2926bc875ae6ae081fd9f134582771db5c67d1a81542b8ac4dc9225ced4f5e7c1025dfe0b7c27e3e5e59129ad4b80ea4e9e2139b75f1d8d1fb74ef7454b1eec1f3b41e7a630ac14866838a5b3745136a8f4904661967edf017114ef0f20c4d7d7c201ee6bed9452e480447ee89ed65a09e9d771ffb432f45d0dfb6e214e14f05ce4215cf4d83bb2de71d014a9a9bd4e2a8001fd026edcb7b3eea3c118f25e0f80d83a1308a21d5f044531f1e2f383179ba4221c36eab049f71351e882d65adbcee1032d835b3cc2d23137d209bfe0e537e7e11f387dfc3d6b21511e17d1812195c85504b1547cce4c6be8393f96bf8594c3c9dcdf1ba800d25d9dccf32101a2cacc0525f54cc9e7d8bf708b49dff25dd028d64defa782ace35e82822f88b769c43d34f172d1e8e0167c5917628de25ccc46f8984e5d64aaa54539ad2dd2b9f3f225dcb2b2f5296ebaeab286e3ccf882d8d7493cb2d59991beb145ed0290cec33aefd09154b863d4b4a9fc23060da21bf63423c8890bc2658f33dcc57fa6c13fd10d7b7d8701cbf8d333664af01c8a082bd6261e7163f10bc2a46c28a04eaaaa0a89b7a126522133b65b43947a20f2c0656ac19d05c22aecdd4820a5ce44c2bcab6af77258bd952b23b0bef805d9720aa3aff14ae5b4430579a14c922417e72c6f4481327b67adeafd550abc22554963bb8f1a5758cc091c4a410cc019faa075b9640656c5e4be81e01a60bbaed50bcd4ca37cc6f964201fc997b236520fa14c6500e8ccbbdd88906f6357060ee3d7a41e57c9e1dcee77c9a4139cddebc4ff4b704d8c616147c0f37518d8ee6689451853b18c9ad46013136bdd22a7d956acf74531cc0389f60888b5649d20a8f91acdfb42ef6d11f1e03bcb9b83a0d401161834418b0d899b012f7b71a4f9585926517d6780a68a30ecc8d1026e3c5a1e1801be61cabe17b0756c140306b848788e4be6a63a5dabdab2be78b8ed8289ccdf33559c599ceff57a5d6b3d30bccf48ef92406c2a31bdc0482f6c7987e48c18f95334ceec954fe84a33961b1749dc9eb668aa34866ef5581f51a6965632e71f56766c5ee6ad87a997f9a2ce71808ac0379de373d4c2cd731ec7bc89a173d3e23cef662a5c16c062543bf7f7c24c3829f272672d758ccfedec42bb86e8de55b81de55a8e14fcd40c6c34b80dbd7eff27291a7aa468446d811e37d9bcb3326b6dcc8c1e3fd3f6f87d92f2452e1631c77f989734c3fb24649cae464d6fcd5c392538097dbb681ae1657f181c577462a57b1caf5f2261fafc83ab685dc0a622d0f589e062df819b68e2aa74d0139b15ba0b53ca5d9bfb3156dc0808169dcc759714a00e25b5412320f30070570f4c1a343185f319360b938e084964e46cd38017291ec91d40e98cde77bf9352a2670bbb228ae04841d56a4ba9cadaa61207882ce86d1c3d539b5914fe1b69f81270e22b8c5b3317dc9627b459c540019ba0cc122a287d185eb8d923c1ba31f0af33e5e2fe1fa6020cf4ff51d54b14d831bc773ae71545588bc66e923a321a2fbd674cf8cfe6737a8256ea5859e3f348e74465f2e703c678a49b0a42d6ecf11e6bf28f6835d799f6a58745ba848dcf3c266818b56019eab62490395205890d85c02917a7e06ad4a7654517d3431bc01549a48e35c2b9795a1cbdb68e5450d47a5bff086c521bcc3150b0afb09eaf617e7ebb6a73608ab6a34b0df54ff24fbf00f2ed6b4723750740ab95278d0c8fdef82302a830efe03e631111e8bf7ad244d4a46c2f1b3d5888ebd0c44ecaa4789b8ad39977719831614d3d0f9bc8fd0e9ba774e92d6cc5aeccf561eb03022dcdb88508f531a760887ea12ee63b5dddafcf1cf19e220060cdf5e964c07507ddc580dc40664177c904fb8a1544421a7c67353a27c32ac4a6430b0e81dee521432f4682a4ba653fd1c2de5bd0473e4e0a0b9dd64eea84960d5058c301d99af6a10b87590c1d38b28b1bf40665798049823596af8303b1e0f45835c6d286313b55fff2c5cafba21ff7def4984e9be45ba397f8440de61a03453346712107afcf74b9902b354e7cf68f88e2201600dce42d949d0bfae92ca205853fbd6da93bda2b3716a9fcca7c4021e8ddf0e468d8d564e3154ef42760030819ec993547e9f425cb1431e969f818808a2182d239e50a06274900c14b10ed77754102645959d0f27c062a204a3527930a6d6630664c7469208031caa10ffce54b4f8c2f76c00ae79405a0943a1666b64c5e127443a314995e92177a58e3de540014d26459ae9f6136c0569f931ce1499652d11e1600a9c78ecf17eeba05dd9cba90d73640970fd24017275d35fc13bea25db976ce859c4289bf0150c207734e34b0a3df01f1697cd05bd21a12b774cf3ce725a07344a18b4943027092242a45dfb4f353252b980e343341173bc291f154dd48f3115a00e6b7bf8210f6fa6413d9182d8b42a3fcc10a392231e011caba282bcad1e1fb03eaa2404f500721c5f453872823b721358673b9d724209efe5d12603f394d01da4b9a1090498531cbe8640a8bc4bd16f79e14e9ca4cb33e83b07d6bba9caf2d34dd9b095a551c637a0a0ad4d8295b228bf3a60e084caa8267b23710af41d143a8e538d9b1882cc793cf73794316d8075ebab7be860009a63c272cc80e2d86d0c50574741a1953cd92db504e3b284b9e0171df000aab84c96185e46c8353d05b285e249753a8fa05c7f7584e0874de1bc93785e12ecca4aee381aa9c0e213959f4422f63a486d89bc60044a2e3528b31352354f7c1166413e459363655d7a2f3dc9338b458946dd62617a4be58c44bc24c70a871cf72b3d2a364ac9ad191c7f54af56fcd9d2c7cba7669a451b016e25228862aab5207d5e32818d0fd3e1bcc06d2bda05375373939420cff52a396138a00725cc2d2658b444250ccd8942147e10ba8c09f945c4239adcd00a19b2bebc9690690b76b0786d6235e5f9b01170810e6af00e020a85564d5c5ef27218d9f96cf81363e98965e003f9b82f5f3b857667d810b563df8098a875f8059a07a4bdb527c5061395eecf5855a49820210401f8e6ed938709b461322905691e79a4850e66a05770e385a6c417d03e7be81d2b0fc2cfbd2016a73229498cb299a0efb8294111bd948ec7f31407b4a769a0fbf521e3941a0354c488664078f1e696522e1adb621ceb11b01f87cc0272795e577cd9f103659fe57ee731c493b1fec47c4adc0caf51ff85eabce5c6c59ab592c5e0d1ac4dad69585f1872c1b2795c5ca0bdbd63da03814ac12d04ea5f5b78ca0e6a623f9f73e35c8fba7413a810dbabb66c53c973e24792b0663aa78122f3e43a8cbfb1f618afff84ab49c391a04a24055129059111cd148e8a27ac2627e04daef9b67c7fc3fcc78fe80536352c7c81e76047a5020436414e9fdf8ea7a9e7131f2a434ffaa1dd14e6310fda87615ff15db99ffad76db149758e7e618184fe6f2123981fabc3efcb354e095bfd43a74437e983f0a44f6e4795b302edc8d92992b09ccc958a2fc3f71407bd26aef46a258f6582c22bc0506e9f2318374a56783746132991ae379d7121b85319537da4d4603d74d9e79ba1c95a2c55726f3489a7c4f6cfc343d7c220cc00ac1feca41409e8ccc10ceb83e4e73ce696badf4965b9702410902b8be39321958d67855ece73da0b8e55afc6dda6d60d880bf13dd165d6155b13bd557f1487fc2fe9e762d2cb1e944b6f9c2f4e9382a6b5d0f1cf928c5fa076641092c1644a775ceae49ade4895d04a50adee4202ac3e7faa5910b7ace1c78a2f76000e68ab1b43cf50108ac8451ae12ca6cd01bae2f75bd92973cc98fe0db212e2544324470c0374d45e0f7a95598e8492d317da0427ade50f18192f6aac50378bcfe2c74bca3be39f217f3740d231f176e9dba10faab8a8300a637c4afb5edf5e3ff80e01f0b03b9cf8f97ae50266a4e419520a8057f0ccce20bce06730e3b9f396e737a263192c9bce66aea6b82412638e1ecdbff46d1f35be321116fad3c62b6be94e3bd5f4f3cd923a05e95ceb3ca9fd454f6cf867b335388636e2f608a7c74fb436dde94d2429447f43d2cbe4c9f55292e2219f2efef47901b1e3329b59397eec620dd2f07cf16a6f64a4225a13e7e39aa51e8c3a039e542d906094ca9aa536bc9123f48d8ddde6dd2450038b14baef9dba2b647451497430431874bf0300cd6c2b4a99a07e328e051bb8144ea2992b835907e45a5075bb26b5537f19a7db02addb4ae4cce175a5731f9ad676dbe5951dba6f5e7b9c59add65f0d276d2dd8750f6a86adfd1fe7e0dc8276acbc8980a9c9d3bea856e3daf6734a007f1032d1d0d123a7ad039995fa12c8232e189c42b3629ec240dd0b94b3294a196b7dae07d88bbe3896dcd28168eb9437bbfdbd154d712ca7c0b306fd1171204d488191b00b429072bdb9b35080c0668d974874f991374115a6576527ea66468a05b3d7b56fab1d86be9060eac9b982a721627461705ef141fcd743881d84820d2e9ed9526b6c69b80a5d8298c0b750ad7558908fd950274092e0c2dd4b71eddad239cafc17cc1d3c0ce7a79498758763366ec5cf0bcf2c4623422141f8511dd804773dfd53cc9cd192b56ff0d8290d1bfa95ef3aeea4a42b8a407672d66a534d8a64ff1c234f29e1ac0ad623c494abdbb1083f21f801c8a6c0a60687de2c934aee505cf5fff9ef96d10e2ea2796399aff486c843cc75bfa00dcbb864f2fe1053ce8f207e79fdfabb4461c375d1a5ac0eb2c444266600e9e8e1cc25d71ea97ab01ed7bb12c52e3b23acea384a69c29499b8841e48a5dd1874409f52a16934c641d54743fd473b42ff96f1b80a9a0cd8c45321480cb7ce8d26c186dd0ad4ea326de9a3ccda1d8d2a2caba141281c29ddd498d9374e1402d812a78cec23b83bc929208ea8e789f4f52f64136cf741670768fb9a7ab3de3d7b61d7d1a8510eab95cc2afcdc63c9a15c62bb39ad5cccb34a093efc98e58a273578a81161076f6ab1654032beaf30a8bb95ac3935366ba339b0519b00297d6c4cfd6c6b73b7b8b0688a124493d194b02e1ecd04b1f11f740b828752edf7447cb047ff981b2528bbc95ce66cc6cc478ae19f3572c49752caa6eeba79cae2f6a959a8565d09ab7ba95231a715cfa81aecaf05d5934cb786fa2b27396dcfcd138faf5efaa4c323823c9eff628c78bc059e7f02fabb64f2b2bb435f4a3962dd47cc8f3c39c93fb77429431cc47505db804be774e50e90ecc220f25f057342477c1a3ee5a51b945a7bf8e22936b741858886332ed00c55a0effbc86204441d5cd2c4a95e246ebd50811cd415b6046b225af7918b55d0cd852e6811a6c037d5352e50a6add8f353c787a2f8724d05a02a70c1337daf5cd1c1eb669745f9af356d9b992033946eac46342c287f31d0ba011a2b9be4aaa55257da91444c568bd7bca1a3a5278129c6d349843847c6cd8ad3992a0d08b336ba0b0dc1fce99cd8538729c74c3602d56d31d532b977d3c094baf1f542347e739f8ee8a11218fd6a017fa872fd1d5393341c3289ca7088e9159ae95f916e6f03b2f328b8574a049bc88b8c58264c4bf86668d7d5fd79f7e22d260953dc1893a4e02febfbe5c9f1c47921b5446add7999108adc9bc439f21b54220c9dacfb6f3802d20727d785012fb3e462e2f3cb1af9d999f35d3129783aef7a0c238d3cdc00729ebd83a5670637862efd8456c7310f6dead8270981ee08b6bc1480e641fb41e39603e0987f70426aa3f8e0c04b6ea3566ca49c454e964747e301dbb2b30ee708268b2fec938fcd1006eb0cc1cadcc66745c8e5d6bb9bd899c370840814dd8fa1b92aeefe7ed4151e2afc32ec8bca81d91a0468067a142bdf2d08f6722eb44818bc82b8a49ffd357c6343352442f1f6f9d518f65f47dc04fb6aec42462a53a33ed057a82537ad509ac7610e27ac70e527fd1f09127c2ead3bbfd28cbd0789291ccec45a9865f700ee1669a6882da973ebdba414a13d42c511c895214e776d53f327b51b892a01a09dfe6b36af8d401fd7b7781f3f1d75debc968419cc478410b0a0fd996bd62959d19a6a2f229d5ed1c7aba01dc94954bf0df612b6dad87d98ba2225fdb28a950b0be7aed89b15650b544e7bdfdad728f2a31d619946ad686477ca508a5ad09b3691e297bb1d98beaf3e76b8701a338b24a8791cb63958b12c15dc5a0635c59a504574db16916b51d0b199f293cc6918512015869774529e720298ad4771bce2f33e78467e8f5fa1fb43ca28efdcaada3543e98f02e7a289c2274c956f58f3f7b512d46684a1638512dc23c96282c414b2641e44168fc364b1a0825d16f5f0ec99151a7a13d2b461d4fe3d955f51548ccec453d647d96ce290289bc65e8a6033d8d1ab07d2371ad005c5dcad82b303b07423c06a1913b1eb948b085ad707b7f4ca768e88075ee22a1e336f21c25333b145cd579fba4e168387b51c248ec505220f86f6505ccce8a4eaba6608a5ac16628c589064b2e500721a33c4223c8711f0c58cdc1d88b895adb158c4f6b73d92c2158acc4fbb413f57cacbfa9e0bb76a2e62bcc4023cb17622f2a6a610cc8086906153b286e0783a018cf1e270c2761068ae806cd9aaf0732f2c5884f09ea6c341ad63ba454097fd74d103705f6de0d3ec567a4754e2069ea8b8a809775c32c713b3298b02d4afe81830145ff59fccc08b9f73b8aa710889ae7590542a035975a67074a6ef3b8596c32e52c8617d21e4a519970e85cd34ee2ae7d05e661b1448bb264dc6bd3697648ee2ef0523302ab3ea2b32812b615c0f3c0e960024f63eccfb7f863981ac094f3bc75dd7f0a483abc7b0ea3b577e4a49154f0fde136a44f6e2c9538cb1ff12f0af79319f8fe72067d303577b35a679dc8eed27cedf6960462fed323cbe47d353a043a511cc0bdd1d1e9c514dc3c58b8b12242896cae37c10bae3fa7607035d187ae4d0472671b70adabda42995fccc5f47e1fa04029c229de351628791b61fd00ea38d29d9ae92f8f163a51881268034397ebbc381223cc3370eca9ae39876650f3cb0ad8ba14495bae58ad1f6d55ab3c00b8fe61c355644cb3664901870824f7a04661c9e6408372c40d4ec18b474bc11009bbb8d1a07a20001015a515546e00e373ea74426d508bddda1e04962509398d2bdfca8bc069ecee865fecf10a577e99b6a2cdaa18700f2326c4c9791cbcb6f0be8f9de7f2cd7c365e09c7c546bce361333f68faa6c11f6bd66c7cadafdbe2e7fa150f59267190f66a5b53ce651c9df74bc1d36bf8d54ae9ac0b28e31a84861ada4bc3d77f82bc95605eb7d6a7d393819d6532b556215e1aa7be88f405973e015372f6daf67679f92a78d0d2052c31872f0afb8c7cd7ad556a7e75e3c176a0fe0c4961f61262e8fc46d541b30ca0c89e73b6566a4dc728ecfd3f88871f3f36212d616140035b388780200c0d593cd3773bebce072ec65b156e8ef91f5d3555805cf527de8c847241950482c9365a21d6eff14db05f655dc4adc7bad55bd792ff3a37d7aa9e64c8252588a61e3bfa03db338c6f369e39e5d92ccb3399c47d823ea131cea12d5104f27a31cc77061412f592172be4c32a7f4b00b5c52f416528a08fbdc30f89a081fc35e65edd35fa37e7fb4ab0b024eb3dc69b9f8968c771f9e61d8542812a04d11d13b7ffa05641a18fb4253fe66b2af2c2c6cba86285b9e84f6ac7354c15e5be160e346a721a0faf48b50de2e19a62aa469da50ff38d8f215cb0de45b22f82c3c89ac008765a8bfbee97f78fb30148678482c5a0d9d68158b8cc8d881ce1c7d1e4dc91753e0752caf10a6733867967f9d4220b011d7c21510c9933f5a83c1d3af60d6c0f9ac2d066f63200d5e912d6adf9353c34f28d26a0ade17c6545861fad7a4868f7e92518c158271a506d7bc195cfb206902a002e324e293055cbfc4dc0c8e6fe0bfea9664bdef759857c47112bc7b7366c064519e4eaf843f6982a4fee84ef52876c01cd4619fd8d453f73555b384059f926202742c76539f5494035fee7ff502e3b058f04f56fd5fa60a4fca6da50156fa887f081033c0d1bcd033c110f3ce198660b8372aacab2e3f6a94f4c06e630a85e1435c5f759d0c217c41aedabc5fdce6c9401f8f91f7ddd905582e945343c969915bf30b59133304f3efde3d1b64213678c53b70946f947815d76e6013fe6cd0cbc738accff8934761047e9651ab5d212128d6d49ee8ec641585f1997a5600e43fa33530cf13950f0985c11640876a5d32b4ce9b4914f117ec8c8f37824a425a7283d1f50517bc5c37b2f683b5f1ca4929d6ab86f485ec167086a1e362a087ceb0d7a2a429de34811358be21c82e790806a5fb8a9426b8342249167e86877d1f106ec60ca4200f613bced48b4f40b3d495ca77d3a06f66891e081831fe83ab0327272d6a59e4762d26143605bcd6b0d5221f248201797874279f948804a82d7991b4f8ca6e8e5fd5ba6d886a29b8966c21a1c072820daf279c87b43b82ec983ce0904f5a1faed26c3245fcf1d4beeaee8777fed75252e35d901a7bc719a87b6b2d1a9f774c3587d061673a406ede4bfa6f84d5d3bc375f698b6ebb5575cb3d3dccc65dd26beca694ca1c37b6f7ba73941c87d632886100ecdcf69dc7b785ce049b084cdeab21b8dd646336ade57bbc1ee83950b4ab903f97c22ab9a90ef606ba33ad078ed78dc75983f19c85d428931c44e7208c8ec81b46f6b5766db5fb2a498300469a403aa14606dc870d5393a018ed27ed33293c876963caa03cd9a87c41862d27b6238f96cb795f3be1f985032ee2e4f4ff46a5faff81c006c6d6ee03956e7bcb03bdb21f13e80319132d5d5ba57c90210ed8b8b6ff343d3e23079ce8af02154a5a855f694f1c9348cd80ca1d4d9742c4868d6a2d4d12a40a257a99d8af2b57cecc21c91f9dfc4d49786961649b580fea6ddb10cf150c70d4ef89db50c8f14765d0f58e608f82a40134de4d66df89a13164a042c3e63bed7daf23ff322c9e273035f4f65cf758f853c646efecdd0a0267a79cacac78fe1ff415e863c3f4d9225755340f23c7227af162af33c9dc0fa16cdc10f444cff0efc00e5b62806a58dfab3b33dab982485e6eda8da39af2f8038363372c8e3fc87c52834850839714d1a563dc03bd39b2fbef173a414f0974c73e26ae13d54201d2d440ea5530e33520620e1930d28d68d7d3b845a8b2d49649d576fb1f118eefa321547855d500302106638e709c6f446f4b411adcb6661da27c29d0ece05341ebd8d0dec048dd76fda457657ebabd1f2d056d0306a10c0827ff91b2c00bf2e79a746ed7132cd5e3290d0e38f00ac1d8ae00bf073caf50e6b8792c43f39f6bc0b54770f9566cc69dc4946e1664d5c9406241888fd803f784ba846a6b4e37f5ba86b51e082edd3be94a1f9755108c2ddc909a1b0d9c5cb84cbca0d6ef106dc56c46da8e38da21fdcbb8d3762e50acf8b623f2aecfd06972f206997e4b3bf9fbcff90a3d8fc42401f80d788dee45860d17ab0b35068a99666bbd162f83f1c84423cf000e0994cedbf5db9da1bceee529ac462ce5cd6a15ad239dbc191910b71fb6ec5e793ee8806a658f107a4fe53f83ffb176439f2f9c7f925d17edb2736652eb58e145deda05b0897a6616017ed47de34870531f5949fe465569fa4bcce097720e3c2173a04d1b8c23ece51ce8b3774b9c7dbe1e0d00ce2d2eee2c725cfa6ea54c9152ae5f07033551ac161e478b64f101d2d5a8d9b5171e181712a30f95c562c2c03c444cd471b085803f0f6abe32d5cf21651865b11a65c671a835f18a6c842e98b5b8bedae140278a6a93a224c7eb8918b9cc4ab1639b981202554972a49db3598a1a4abd48c71627cf69109e8a9ae3e823ab1824fe1830e452966b6240ec7c7895f7aeb44b16f0dfa0a9b5d84d075a1eb6ffe42874743138b2a16193fd14147f08bcf27e8ab48cca444f2706f109214a741c0b885de97ca4c6c1f09ef3e4612dd1164fa7c2344e0e00830a47bf49394302d0bda03ce337f25956ef741f080d63e10eecd0726ca109847edc45cb8b1a8c54f4172a0066a4982b0a96147a3cb145814617772bb6a40893500f41c3c12457e2468eada0dfd1674a6675273e7aa8e3998874a37cac33f5c86ada9bd61c9c6fc51aaa3e80d4f48bcd673148b09984ac850d7097c56f09897764780feb5330ef91797f0e432d1899464f08dbc7cea608009b22ecb349b3a7dfe4744c34f760789f6b439533b8ac0f4558ab2b1446495b1cb413680b333a3c223dd86c878ac93a24e04b58dd5d3e99e1f8ae3f8e4a539e4ff347b5e5db241c64f57c4c4f656e71495666f9e9b9f68f899763da9490d6cdb357ee953cc2ba1570a8d2d74a75592f2335b5227b07b52122d72d680bd68219b5ea83fd97175be8db888db89e0946357add3f91009696448ab2a5c75258eb7468388502914c0e0b480039ae13a3fdd67c1a32bbedcf9097d770e098ee83a93a94a8a27fc76ec9d27a458cbbaeaa12b31ff4f859f422f1807d7d0749bb4b61bb7fa897de2f9e94bfa20a241835a27cb71a9ef33d081ce7c08d2915511859db2427e776ab3f2c50cf20a10143d05c486513bbf9098bf09be6063aaea509d4fb70a5cb7dcb4b2b7bd597399f666dd344aa5a7ba9f380c816e6e1d0c9d954d7be962056eaaeb83a0a709d367a450210fe249432e28ac38950d46a340e2e80d600744602f22830c66748ca87953f5de819fcdbf51b5c5518fd4f1ba3abb2529f10325f07af9ec16e811d3eff9f3446c5120a1ccd646ad6099b037094e76a4fa2e3e1884efd05da60edb4b4131a531e1ccb2734070540450d86227231f3165457a3bf488c2303e7dd205ca638038ae5a0383325ae5e9835dbeeef0a37ab9d1e65377f18c95ce6207ba35df14c4d09a0110d12963d2503c2ef2f6e9037f20e869fd31a5b9111063d788b2998a5d705f79dbf57468a20883b7541de979362a5dbf1b1f582c3419b592758f80d2e6b44f0ea2c86ff1266edcbba8bdee3adfc3be109f26017e9c879bb6088339b7de0e2aa0b07e67da540a28cb0faf3cef865c24fce4015ffdfe1233b9ce80b5609c1d6e45a7e1b161787f0033df3b6558198b3c2c68d3a24c8ab21f23c17d1457fa1cb7f0b8a247f581e4c7c230ec4ea73cc5097fa91f9d190f64ad8a7a454bbb7223f0812d2e8dc8b79d3c14fb4c01d66121132ac82e386e4ae1d1b081b2d6e64b95b2085f479b76ad6384bcc7892b166caefe1a7d5323b3c1d39879a53642cb871bafe1ec5b6eb6d4a67b25c17a14bf945df68fd9d084b4f80a19c21da96e9c1e8fa717d366bacc2c20d6a238d3b554ee16c71e750dff88dcb6640f56cc60c95ffdba74f971c5d52133c7d9616a918edbc95f60db85dd8994cc9efd19683f47fecc546f1814142a33a5a858aa3f774dc2bc89d46a69d0daaed281cc6b51b12849716903a23efe04fec71d9e177253c9bb0b24a7eff95e8892690391d298f7f4e77098bfc5f56e3a6a3bd94813a25bd2152e8d8be7cd8b8be6818455d6565e007618ad13c76a3cffc636a73d786e09b15547b3d23ca72751f864b7590cbb3ff568c756802cf4a2c40512a988cc322e9e083e3afdeea31a19370685c644680cdb31c2f9e4346e825a8f895bdfec021434d78510fac3bf8b09cefd5b842252043210ecd26e77700999f254617f4f045876f8d53e3d804e86bd804050ae7ff4b95d0bbda830219086c1fe6e31ef638874e0d4470047722ec9fb7fc2608cdcad8c51d6747ce2b722a11b7574db638f8a88d08dfafb9e620f036c18436d111a4a7b2bc4e242442166b3ca2a957781ea604d7f4b2f1bb6c63a18aaeaa57422ca57c678ddf6036cebf83515095950e7736a55e2b0b87b3bd53dd3dc3979663d7b95280801ca2e838bdb170cbe46fc75e5b9ea50c8bcdc39a3d72907a4c626c5d3b4def8c07c9fafc6a9d4b6d5a877f5f284a3d510815d3b31ca1b152b5f3e44bfbc8349c44c8a7f09fe361a171d1aab14ce8199324f2b7a215aeaf8f87d44ce60a22f035d01d0528ae6c2dcc29d104856cde578d6b405415f224478757e3574eb1cf532ea62bf30f11a09dc84fc6ec12df378394acc800b715f88e2402ce70f06da19cdc381d801f729a64c4fb26df03a695273b5f7c5aab8e46fb1eae317478a2060556f6e71fbfe5bda5c2ac82f848efa82dc82c2a81124fb5e5463f659fa31e14792a15505829bbb50b4b6fdecd96b7dbdefbfb256c27a6fd4c567b87ab913f18cea289ef1ad34a78ba3c9dcbe70e0c53a3c2dc06fdab3702d1291d6c0ec7b9966475d015192cc19c27dad98fc97bedf92c51847f4092f30d99908da3bb0a7babd185704eb761de954c94764ef7500a77bdfb3998b760b7a5a34f69a4408febff50ab3587af60709f97a138e17702a10325670bd02689ce5d5496cb70b15106f0e1698d5b8e8de7d1e7467cc929b21063140ef64d2d290d8948cc9f128380d26466b44c1b177aa9d0c08b39ca3dd9e3459d9d38389109e0e65a201872f6bcd8ea21462c236b88732511df6e8e1335db19bf834b44e9d6729009383cfd9c04e66c68a248f4c1325daa2beb9245e2c8384ccdb97cd7041ac1dabc915e1ae3296d1031ad744f7b625b8d9167d2efcde44894c4254fe4b50fd73d32e483dfc457b8341169aded0d2e22cbc10df76940f9f266cc355c10f7c567b045286ab082125a4168fb7de1f430124779d66038aebae7ef14b9b3e843afaea27ecf1782cc50f739a4e77f1498b55cd156a570d9195da37560cd15aa089cdee3a17f73e4d5bc9ae078958586b38f00cad6cb3d335a0b7f83ae4374882a05cefcae1edc53b3742bd079c8c0c14c6ad3e3d98602012799abebea2f292ec450d1579cc467bae105e4b9280fcf423c1d8caf1f46b0084a4aaad4fb92ac66e5a358b761f0176a8b85a623df8fbdfe7cb00dee78ca61514173dc26e6d687b1398f5f3902a00fad8653be735ea9b8f93867f7ed811173c5b999d411e41041de550e58843c6ac3ce754112d59b99cf27bbf516085903f0f4179c48731c5f2e0a9507f2e613504fa80caa017cd3cca51eab9c4ef7801171ae46114773cabb4cccefc1f1f367280c8dc83a64934c9750d7f3f80c2ff3b6ccca8084fae643efa0d26000ad2698f5dc87e53c6a13398fbde3b6b60e64111e8b6798b4e4960a9e6facf51fad7ec52e6bcca4c0a3813aa6a3480024a48be74d9a81faf240d7788940c28b6f54e520fa5e31ca0a8407ad670b3d19db676080d40719f4b7a1175abf40451a4599da0d15b7f5371312b42691114618ed6f19dff3add042a7364462548bf864066219c738787b31246dcb217d549b0b57165e993cba242e20814e8136107727a9f230f61b50325afc366d6508fa8c677c1253a4486a29a267d8569d001a8ee687bf55fbc6995a48bd9a4500c70d3fdde2d06c142b1071e5d40b7627cc66bb68474c8a147bc26dacbdcb4d4d22cfc1682e8c18e335b1ea3e90d8799138ca842484b452ee94c7e97cf24cbcf8156244e4c79db40a08486cfaeecedb4c676c804ccf99bb528e9f96985807f53b30758441e1bec012de54e85f4e1321e0074d4e61b3d4d973f8a84f04615fab04d3bf75705724a217e8dec056794131b239c2aaf9572480dac15fa8b2790abf53faebeb9ee22f73c5b77b12c12c29a9a8526fd4fea034c327ad67fc488f3164dc0fe7d649be9ec15e38facf07e5a2fadab135abca706dcd2878d11bf8434b0bfdd97b62aa9f81b91a5370d530160ff38b4e47d9ff626e6efe952f966f617aa0debfec1cb97216cffd631f2707511dbbf6b2e6f0f07bbfcdba3d8d14dac260eb7375db1a401068025a9680d42ee6f94539672e7fe8fe520218e81fa7d3a631ea829548886e7cc2f8500059c898c49de5597dfff4e4d4ebbf1f7208b8c4549ecbd840d7331d6df3526f9fb110efe55951fe6f75804f24f1245575f34b92bc7ec3fd3b8d56f7b3aac75f9067dfea44bc3cc2333a8f0586afcd251a2eb2ac5290ba8c1cd5f4fa4f21e4e46c2079759448d7412c1979b11530fa1048edba592764777adfb6ac7d5c5b33d18f888c8dabcc43983928b413cdf984c41f59f0527d31ae6593c0bbeddded3c5956386573967ecaa1c71182298e6a1f91ec208e656baf4bfc5ab53aaa582845eae7ec375c85b4ce435ff07504406087765fb7427599fe59c2a56b066e67e9aa7c6aa757e61bf316c70a4df85bd0f90950c937cd4112fa4187b44b701427de06caaa0ed6bcb03bef235d88ac4cd2e35bff73328428493f4c8a605f230a4f25d8b5ea56b3e634003ee832a2d7aa185f6d16acfbf4c77185a04a6331ddfb532de410c3fb8d769d72daca5d3de42e890d540ce0f5fce1707ace1e8cf78fa8c8291895f3764e28bb2807c39166d364ef6cc2189260a6ae99d3170df30c636113f12ba02e4eeed109fbd9ba5299f2355d16615d3fc6b6a52a4b5700a3b12a16dd8bdcd0a00177d6d69d4402e272e0f293c371ca5f303d669c3ecd240ecc8266d330137ffb99b1cbf8578e493feb187abc3f87e983c77d01c4b934e4f6b583512710510958b30c95e3a05fe927e99826d83237812be3060f3a0e19470ba49b51813259f9d8871cd09cc197640e249de07d945294c3a7eadaf85e426848ad172631fcb99e275916e0a67dbcce187f33c8057b6b61987fa9b259243f2b56d81ef3a74b6d185e75e4402d2b039d0865e86af4de52ec3a05cc44a80d308ccb5c95b99d3e39dc98eed523810a0321acf6a4e6d8592bd913a8dca85c18c7d9d09fbb3180a548dd4c7eafb7505cc5212a4a50603fa9d25c193e2cb160c5446cbdaf720f7e539ded5c02a8aae5d9e18e60402e63cdf2d11798522ecc6984d4d7a48752dd5f751d463ef2531da29d77dbce6fabe43281c604fcd253fa7e8680fbcd875a86a0240d651684bca8da9ee858ec55ca6b2ac6adfede56ee57e8149c0b7e98fabe2755ca306c0b792c56deda3f262995e07ff63238b8aba7b1629eb8608b2ffaeb6739574a8320da8883fe998d33d2260a2513bb218eda8af5353c983712dcc825ece72c3e0256a39c1ca6a8386b550cc11ca55cce7cae3bb6e0cc08c21a71d830213d23232e0838fbe455b84b331d6956f9ff652c4a13c6d6ff341100f18571a54362e1cd6301d55224753adf4a8e7d385bac2eda1fb5381eab19fa610412ccf1472f912057501008d0281c57207778f3313566897f4bb5138484425fb078113a790d1258546b2612b0f8cf1b61a31bbf2a79202def7031e98b8b94a4f2118038592c711812328cd135839a1d1240fed6ddf861343028fc2ce5383950a4ec8010052f3bcdcd617ffbd753137aa5a5b427bd1c905c490add82e9f1669803755e51c45c7ffde6c369c8f5ea0e7eeb5ada7d32ba338334c54b19e7a9c3c160de23956fe9f1e2614c7b8d0699091d467374b5defaaf493212c1f885c766af2b0ddb97f3e32f4bd8ca93edc01d2dd2f8a6039aeb7a4527412bdf22245f078d72b6fb5f09633cb5b9407c50289ef389c11ea77db2dcd3eb24d2331e263f9ca105b90d0c0279c214b995d1fcedaa7200493ff019076d8be6c10d48b955a8d47c6b7c78d351c7fd0bc495752c530f80c29f0253b0390cac08c55438af82ae5978b845f7acad83247bc493656052d2a0eb3b59bef18cef53b3bc8871aeb0d8c311c5f7d8f4e0951c16e71a42570af892362473431e9786205abe8593584f4b5c7c4a9ab381f9dfa385426a76e8652dbbc1636fc6af75aaae90b913cdd37228d88aa4eabb76c9f9a9fe9f388a8a6d95dc305daaffb4ab28926d101ce8941f590916c5a73086fdbbfb503c61b582b9bfe74798878f364c860e8d1ac4051ec5762d86e02e897469839916323d2323e144ec81e8f24c49a898e7b382a30dba2f6f922c592e90b3c7a8cc79100505943888dfe06fd6d07490e59c0c5573e166d4751259040bc85a6a1aa19a88326712c42221dda5859e509029d9cefe1ceee50b0f2cdc1fc9afaad1c6bb3445e59f5199549abcbf3103d29229fbe34a6dc52e2bcf2023441e746b38631dd70f9a4a0c0a0368f87b398e146b6e40bf6618b239be0cf07c516f309d1acd3bacd484285d8c14a80baad0f992e8d629d296c086cd8e641c82cd1158123d23e1f18f2f994b0b214838ecebafe7edbc974ff1cc48ed218df3e4df2dc7bc3c18bbc7fb86ba0860a7e27842ce12ea152b62dc31ee4dd214f4ef37addf23c96ebaefc686472b9bb795e28ca75f45870ca630a670cd768e85a5eb52ce1a4bd407ccec636c13ecd07e03e68983a601b1e69cdcb39adf879a5ad3637695d57028deef39836b492d368fa1269948ed41a9b6a148c0e80ae12582b158d42e59716a777acd277b9419e804e2f85c729adf42f5835e9971f5aacb39327f1ca2a569fb2d2ee03564235f2c272b26c1ea21b75344bd612ae879ca2edcd8d0fbbfa3c3017f7586e5f492e8226e567d8ecdfda43df5fb5b89c874bd3f7c8bdc5e6e279e1f04443f0e4a3506f12b50732bc8e0d09d561930469bf301a598957d58a8f9d80e898c260b9a0ca8c8d2a63504ba6100bbef18140cca81f769998a4ae977c939a89c89716a5a3d513d3de5d8f6214271946238192ca4db175302d3a322b7bd6caf0b20064f54a8c07a5f8d16c35ec755410c5acf8c470a7485125aeabbc7126734b47b05a9046d1fa098730e1072b52f77d4ba5b2c8a0af5439303666b0425071bf2acfd8bf5a1bb3fe500bf003d3bdceb72623c3a6775a9dc326e206495c21ee8aaca5a9e92e9850003405a46bf52b3bd6939e5f50fa0acc0940f785b81fca053461156c116e81bada3d84bb5419e77af2f2dfa92efa471c140d24a8281002b1765dd161db760486a44922e50e3ead991541d8f7724283a7692fad609457ea3afe53fad26b9327cc9d4c11bfc084af55ca072cb66e870a1be0753e946297cb95226bcf1d5e6a969e2f1a8d292ea29c92693d17d84cf440090a0b885ae169435f911faad98b12f9a4ab34cbb21d2e7a364624b16803096f7d409b7cdc42fff4c83bdedc8a3cc3afba17da26615ff1f351dfbb19b6de3994638cc3cb5fb28d59e972ac0aae61df6c9a95f79300ec16ddb306f475c32d80f8ed514ab7e074105614b6ae48c7b03deba98ad4ea529f5a2004c62a28d233f03493907264e34305125228891047ce3c8a65989e2b888412029f1e98010306fc58a91c5cb6ff4eba3efab4032ebc6854ef6909665e988bb23cebebe2cd5d7a11bebea5cf387c26f2bba48a91a0b1048441fc7ec7069d7fd2968ea9c8a1fbfc85399b8224c775c2972793e72fa4f221e002c9d77df3d8567f73bb1741e68229397dc438bb1947fb66a022bb4bb6339b12678614d4e4524cece1b5c8bf5fd1f4e926350d381a2f27ab609217be18a2773be51da95b5a9ebabca356151262afc93379c7f3fdc176d6da97c93f6ac0c2702b1793f9ce9fb3455df050b62e2f4ab2d9be75cde28c2b5da072dbfe8070674e4a5f703c36f2821f847dad03e2a4fe9d6da1b54180a628464bdf601d452c6417034540e12e27a1ea25ac5298844ed116f7d96abda4f0794dc9e19e5165e1ff00e70b8fa8ef6bc33068eff2a1577d824d52d44ce3b2ce0c23adf86ab0bd00dc35b3431702321c87d5406599fb346787db759162a059b93a2cec2831ae6d6fca14a5f13e8827772833ee7e0609580722617863dc4e98e862992de6f3a6b793fbec229cf3995357f229398774c4a5f5546e59c5bce06f9e9fbadf2c5c80ad8b12a66826c39ad79b8b651569f87fa983b61a51c542a22a5cf7dff650656f37c85132ec8b25dc68fb4c05645cd438f44dae70437b6b4d6df12f361470f177fc955d9a6f8ce932b3709e69634dec95a58053b8b94f44e412ea12aceac04cb0f498b7b1f9de341361c3c4afacc294037428ced3e970f26f0a14060e3e2961f4aaeebbaa1e2a9a07f10f7fbcb31660c9caf73b98377b6af841b37dbfcb9de37b48dd21b8406504fa333f6bcc7c88553054ff7c1308ea1e11c07d54914f399852385f2d0ade64e998e0edd6321d4165ca09c2c059d60193c97a2f26586500cbfde14735288759b8194189a8553635e1385c688963918ff3cc91a377858ada8ddee4ee55d0bd386e35675e0154dc814ce50534be7e00ca58b242a27172bdc202dfc6e90b7a5400393ad30225690b07390e50d4288feb024bf993e1642ebc5bb218a6528f3591adbeae0a764d820e0bd5d6fa6f17d06a028da53abe53e9f2d91678ec667341891bfbacb22cc785c2de1c421ae41c29682a7fd58cd7af3a8abbf1f13769197703a429f7167783ced9e880c69e4ea818d29a9d5d177453101b20431e7501ec0da6309c21b25a08bf771c17ae059d3b3d448110e0b52e81e50a9d22d097e4e698601164b89b0f744ec37dd06f95aef2dfad3cfd48a88b861ebf0d57ebc301086aaa39afa1927352e00429d0088228a5565d24a3165bbb884feae25b64d28fac2435b3ca2dcc13cf1076ffd842585e751cc24b9c3a58c82fa1e96083694d940466b1f6b52e4d6c78c2fa3f89e04d3a497fd26374097404b592ef5fbd982be7be5f92f964befb7b358d019069425fba0d48e229b99eaa0b19d4269a5f12623965c7c3ba3a38707e9979dcba3148a925866053a5ae2cba6995b88bf5dc86e1c6b7db1d9cac3792f5a92bdb9fe25e49cb97a97fdba11879c9571f004eaa131ea63c24ca9bdd9040ab38660476691a9827ba93c684ab1c79fa872fa0b1429313602e18d8432c4195283cc481d8fd26728df11bd649aa68fe0d7249cc958ff243ac85c513712ced89e4e964b606cbdc37809811811c6c6b347e7a43a37cd9f11f903072d001d8238d264216d7bb4faf661744ea5f60319bebbd0f75aa10d930e270f97f9a6344278ce2b7448a07246cc9438a39b8e8c34baa011667eced7ac83a0c4d85c99969630ab31a79170825faedb4c41a9407710ff3bb4d9c26f7d050d445bbf17935088b91339f8792a7040873357c5668f7680ff4868672f41bc9f2c8da25a918f612958df3dda06022f8a42a01b10da15e331ccc640c71319cf275828909ae8a0055c2344296fc38ddfb1487dd6b1a06db7bd80b6c00498800e353eb94efb92fec1fac384213b43b4b078f0d11956755882bb0cc1da1ee6a24300d6ca355e9eaf9a93efedebaaa506e00e9063c640c0115b5ebf0af36650c9d4e6ce1256f40129821139e099de5893efa3b6c390bb3d4c7e2008604508ef40e40c5d612b680d1e7cd979032adad9fb425651c3483d531f80cdc7f98df54bdd053581fbeb3455b7129446c0ff20bfdc9e6d588101bdf4c2a785e872b992ca1f369857e2517e531d8792b9ea77fe194301c6577028bdfa2d2c60623e7ae78a02447a1c89b03d6a783d86d09aa27f48d47e4a7be8cd9a1fc48e4c872e93c083e915e6856dfe85c88aa1966484f4333db96a8f3d02e9c8b2b7bc296bfe113cbd96b241fc901995628443be22af34dc4974579345e4e6c5670da30f8bf04b58a2a6ba3af57da2ee8383752939ad3eb8a34f773c98c931f20886384774005209baf685a05ff66a7efb63c3b8c2ae401046682b4036d69225f9e01ad9f12018583d0ef6171427ef2bc7ff21cef7bec8ee7bd2099b75d3e0e48600c8afd99fcdf09047c51875916729793eade1866b26d907dfc271dfc0c3531a712274950deab3eb9b8372e4f9e946846e615bd9c563a561fd4050267c6e72bb97f65c1fda338dd37eadae7fd6a30f1744e0b84d5c22a415930792ed2a336e939d0222c803c3ca0d2187144a95cbee8cf26c127c400f3316b72b1f74cdfc2ec90e9c84200b51e83adedffc171c4b9419c927f43a1cd17643d69c22159a532a4359a56ff11e92a4f49aeba5f49200001c2bc429b3b81551c4221b4aed8cf1073bb39bf70a4b598a6a3f76bc6ad6902e37b20cc1da789b1035ce10350bddc3d091251436972212b7d43549c31a5f39db1a1d7e6dd805afd07b0fb942d03f52ca4ac0003b22bcfd45af3a929ca9c36fb93706c964a603963e28d9c0fa18934ac1d5341042ec331e2d90a720da3efa0ad67ba62b79e16b401dfeedd8ab53e4b742674c4d2902048e6aba56a4cea2ccc22b0130da3fcc894f33e15cf9079087a87c5d428c6d37536afdb92419466c15e4591ecbfe7c85be9dd2413d100ca693b01f0b7cfffd5f11c5e426979e73e985e4db701096ea348501fdc54aee7b9cf87b693d6f33d7cd4f59381e8c27d233e9c00e71388f32374be07596b8ad2a70cd56821966a715686334efa435fac0322760c0707e1b76680efa0df19e79c5309753c34b8c2a45651228042304b961bdb9c2a927d51ca359b6ad8ba7a37cdfbd6532b03d252579dcc4f9a705920fd0fc4067782ac334cfa333a7da9663806e9d139a6e51e044a8acc79d13893c2aa17b4b51ba12589e6652261e53e3d8874059b448f8cd2bd62176c403b78a98832e5ff6846c051a19b9832d62a482aedef978ef9ffd527bc4ad41e6bb0e9bb404616d9b424c558411ebcea2a1f1c3e48207afd43e99ca154b23a17a592863a7079f9e93b909cc336d2c367caa0957ab4d11326fa59dee54d7b373cd614389903a0161001db2434f697da4804eb69691623a981821f9b483cf4951007f8057cbae047a93460bd33d611f9afcf3fcec3f929a0288d578f282ec5a6cb8f211dc1962c33e4d7a87ea6baf27caa637c9e0158e2c326ef9e41ee9ecdc6d8f385a0d873d04098e37b379b47106ddceace910cfcfa720209c6b9a9152a503e9f0aecbade59dc9af1dd42227cf092749f9774ae8c3abdd5b332fdd05ee9e69adcb7494ab04d7472d2da321004a0eb796ac4f13d1e6aa17899e8594633c1de866d88cc535a3d453fc08fd252b245aade4dc29689b6273e526c1c9f3c5d5775b36712ec28c9b5b0f2cf1c71cbc1de1238a183320edd57bbe105340c87c8315fef9a0c96431f6f1020fa84e7db4f140e6f98c4b9ea7c6a8547d492175168fc4832a5fe934495d00d3d9d462bc82e250d5360345473b036e3a08751718d70decc97f68bfca63a436d56496a8d4729aec61140562732472e5f446c852af8bcceed849e7a0887fe2542d9e2c7767a34bd00857c00f1da4d733a6987022218d2d7e3d414302b34d2e07c07703553bd5a3760c03aad26345b89959c42b504ac14f4a5fc7f9f01e4190dbe0b7b60242a3c9e7677cd4d66923ec61fa12b49db9b9cda16d14d0bc9241a7d3da2347626920dd8745c4548dbf874f68c9171899da4ad6f3a6b306882029403d1619898889c74a9077b3e477f854e1fa0c3cf47a94dfe864c49672e17ecab818795c70ebfd2a77362912cbb4d278210495f567fa8958610b4fa9a1a36a049ee1a01c13cbf9769307afe26ef611dd0b108e4fe3ccf7c8ad557b53d15a01a850b99f65ef1e1275c0285ae02f73f67fd1c9c226fd70b234e29aefcc1575be8f03955eb69477cc3d949890052e2420d718658c4287bc2dd69822b5f391f63937dfa525f820838049a1055c9379012429a3e68894823051d16a2089586f85d94cc97f4506c5ae3df848197a4fc843100c160e206a65adcd22436d976a7543d9392c590fcde22a21af98c0dd6ca67c296cedb5ea440406f5e5fd66795007c99495294746000814c20ac4d95dd06f49c51a4370a9c1bf127093e3b8d0364352fc91cbe06f708e21985ca1956feddc09a393424dfb34a529da9ec0c8242024938978611501ee78b2d22c346a2e6759f6bf61be89dabccf31818bc43b1411180f933e89045dadb6c17d94a67ab7100a2ec88045c7912d569ef7a1de0231be91aeec71187c315f7c9541f4780dc88316257680208ebf3fc05ab5201026cacee0a1bd7587aafdeb126b0aa80982af2b1bba40d15a52a195714ea738f7c081ec0d762e78441fce0ea89b32c9de3b10a8fec5d2ed8555fffce5eaed3461bb257df31e785e59e1128354803d53ab2b2705a96a7aee10037504fb3b6e91e13c56a011173f1e5fe354f7f24f74c642c5cafc519c461d7ffdffcfbc32224e8c7b5c2f6f37b9f4a7e08a70217979fecc7841e6ce817b823dac291c13914dc00ccc2456d2f3d36dc175581b68924b7f060819f5b91adde85bec665eb9363309927ecd3a072e6a4ebf9dff7c9008b5a36a7d7fd15d4f0818b440526ddf2cf30446e1ff3a1bc80a07c0019dc2dfee84fcf1c112e472c39060196af954d8d177d15b94a444b2b09c1cfa36e08529093514e5b2c9ce52edbb05243b9f85a30706a547949944d612e36e535c991af8a30e8e4d240d964e637d5ba43f89f27232a62249703afe528ddf4941e440801fc48462a79e884f2215d87684821b4e3d08ee1c0df40ae7fcb85e6b01a5ae9a98b479eb1d0d3b6e6517039fe270606336399b664403d1879210f549011f0807e22c95202a08145e01e64bbb0351ed006e39d047750eaf1da7d45329b7a40e60799bfe9e5edf01c45d348ec14de2972bf7a98da49d2435b2e3439b668b669d543ea435c0f6c18e9af4040ae6878ff852389a2da3386615eb8ccdfc668adc941f3409d49a31a8749fb58cb274247e5c2e3cb4b2a471704a6928cb50713d88f52895013b28c7c9c97ba395eafe2dab6e8a3c75a029427746efdc10cc183f17f29fb87d1f08201b852f6d4067488cd44a35e12886dfed0cc5e01377e5416e98099210be1706eaa114db9f61017495ba2724452427820e8f37993571ca73d281d4ee2885c4fb0824a43c1d0e7e02cfd102e4d4f1db3057e67381b73322339842e596a4d2b4e296fd268836047b27ca1ea0169f17c7a61acf14bbe00f97aac8cb28780facd9fd0eb8f955ba240781651750e449dbd31ddc50fc274cb5b14a0e4ebd9048543ba99ca25046cb5371164f6a8775887614a834eaf53ccf2eac281ab9bac2ea880751d3cf5675f074e867e6ba902cf8a6ba460490d6aa381c4fd0264495dad8ed703029b99052a77991d40e7c6ead5b67bdb27d863d1025f6fd346f7c4c8edc692fe66285cdcd28d7a44c36033c6760f0a2607fada0f89ad49dde1f6412ab2d07528f78abec2117c9d2c4ab67abc0141982b6db4f3b33dbc0c5feb09865668e77d62b7a2f29733c5256d304295d40e6603f08c917bf907ecbfe5b646db9c87ca00f797810eaa72c8e9bab88865bd6509bd3069c91461af3ad0792432176e8111d4eb8dafb6bb8cc5093cd8b061c78021025f06e741607b4c75c714968fa5ef8d3fadd1351ccb6a314f3f5ebe627e05aa86e70c0f56e9a5977202cd447e756a41cf9ea9b8bc7610683f9156c84daa49d73c1e90ca42969350f307840a80454e9881fb448bcd4c086673d3d783f7c6abe423fa97384610f62aceff204cc41a23c43863a1c10d290d17d8ae57f01cda4fe26b49251498b7348be4cc43c452b2ada835de987dc054a5d1ee71e17ebf83ec3f4a31ec31b762241e39db20bea90828443376a21eb670a504a31a2edadabb0665987045cd89781c061a0f5e66770cca3d08acfaf11db394d3b3892cf4293bd9018c47f04d110f503e70dc14ed4bbf06f887caa804cc4cb5dce9e8765148045dce9d98edf5226ec740ca34acac86b857816c1c5e484cc37f9a8dcb9c230b8c9ff46aaca9c050f5ab0ef8a4fd8ce1de4cd49e8fe4e1aae5fbf0306faf38a7cb7e3a27e2239740a284541645571413a603bacecae41457d47086a85559b3a2863507279796c625286567ce9358f89a58233623ca87f5c5deb972fc286ca3434cb0c094a279935d53e02602c96fddf3b553f5bf2f9807e87def19d8c00391bb9cef6e52e943e89cbf50b9ddee545182714229a9170da8fd5e53d5cbfda30f7569aa6bca034d99ea2f65f15b61f76ae514212c0711cba63ba49d8233a0be4ec18a81455379f62514eea5adfe46e3a7033dd803bd55484fefa49adaa151580f243f90c37aaef472984748d636a8f874ee6c295f03a13c17f87d6c955f0ae432c7e188879865957300f8ac624c0b0f42ce3d55a3647f98184a213bca0ff12d232a0efa53a235c06412e90ee4e662cd42ec7ec5be2e113018f42b46f67eeae1e26ee3578b9b51cb778de8204f4635e9d0b2e955b96c7b17d31efada48b07ca3934fb9903b1b3d12dc0edbdbd95e0f3c504e5a388573bfc5371b55cd66bd0c6fc193d8b6fc1bd51474a821b4d104a77f4c725bb53bc2071c192e4c0d4e874742647b476f6fadebd2db3722fc0fcc090b8fa0fdbeaebfeb403b8c58765d9eee984a6bf42758703939862091e2daec3a4b694097bf387ee6ea3370b3472f1b0b041bc0739452e15167e535c7fe01904c090afa2f05b40233a40c287d76c2680897bde238afcfc8bd8c1d2169dcfa2ec191cd6d75af758b968e683e05858e5f233cb30ef759f25ee92f0db641fc1f1817705eec668560dfe452872d622eb1a25208950708affdb0a41c822c938ce3a739b989365dd3e44810e3d2b567fde7f0eb0ac41f0257702df19af13099d6383b7874d2c4051bad4de44da53d10a29145ca29ca77cb9ec933c4213662438113d32c2d28819ab484f498fd1325f6be1a35f6340e4d2402355066d89d49ea7dcec8a24e16a192510349f5b2cccc655bd3b0a5f80e3d5553b182a6c120cb277df22b17b61a2a811998b55c2c27a7d897a48b284c8a5edaff58dbe504905e2e11499520523012e9d3acb3f78f6d614b7a495b1e7d1bafb2d477a2e578494d31f9a688ebce3700a66c871c703003ba003562af4fceca70568250e1135cb29522cef7acd23933d7743c4243ee994a70bdc6791291884b2c58f4400943c8de385eeeaab0a34fd3220f6132f3c1d9c9ee28b0fba4005652aabd2da7a682c0fd9a506506bc6a96cb38be47f1e78c3e1c570da8001943dae61bbb0e8e82b6cab71caffb4cd683b7b0e74188a8d84622234b947a6949046529fb5e0bb4ef281475ee11f6f13a0fa66bbc7260fede45132ec5d353431a174ce0227d6eca5437dc30d8e14f025eba8090538ffd69e09afd28a364b16b1076b55e0332282c7ecbf2d3aed3cd9d660bbe64a973d104ec1096b391aa5b0545aaf97a0c27da69ea72a846b06d37ff1a1f1e14f107c85522900088a86be5bae770cb1c4aa8227286c448f708471a141f0710f9208a6ff956af7f180fad6e70c12b1d7a5f96a5db9feedbf154e788593d4d28186667b6a936d1d84ac481be7d7f8fe1ecdf312c2a2974819f88690f15a7bc8c20430569a3ae21b09233fa33c6e1fb712f1f9c45a3131b1ff0d6f70000320366fda1435e48cbde7dadb50188c5c03d02d58b4975f6c98ff25d770445228fe14d8960dd1bedb21dec7012c8f71be627c04045fcea7be56229a05eea12a8e867e8a45562600628081da48cf6eeb999116d4fd777988d831a70778e7301edb23bb3adb97d6d8fc96a1105ee99345292dd1a3356edba89e1a622dbe39d0ebb6089fe2ed8aa10d313703364b1e983e9779316e244cf6630420aeaee85522a90c3bc319d26ad31537d920610c19b96c7efb74216d50fb88a43ea02b18b99e91906bcff593a12520b0fa78cd7651e50abe69755a16fbf811256c27e153f764f1c02a7f5d49ea252672f0bf42865c9dec09e12146c7ac5c80567a9fbe91b5ae335fae54b71f778fea92693d3eb55e8aab5b4ce218695acc63a5ba8bf585df521d00600d2c21b891b2c7e9643c79419052ea356cd932a01b4621a2f5fe1bb8ff373f88880bf1259dd226cb4121af3368c5c47299d37545910b8de3684128784746499cd3153233c3a809a2d34ded50f6daeff0284393fb44be8bb940168b3f9ce4f002ed3c7f66a96c4f6ef8e5d12053c06523dbb9ad09087d89683cf99524fa2d0b976ce66762f1a8d608db0a2f2a7ae34cb6a52aa43806681a89e73f478d16d2116ecaf2f24e1700ec7082f6dd7a5441dafcd58644c9527da9228aed81234457c7669c1f5a31513dc55b238f4ff46e83368b5d315b128ab65392aaa9c159c0c58d4b1f2a259e91d03e27d541dbcf48a8d4cde0915b327abd08048e68fc689c04a194c94f8c9099c264f201f905d10b94d531692073e0d9904d4109a568507945c610c84e3b146368f1c9dde153f4e3b4facdede0e0fee11c724b5cd432209aacb6ad2bf93700533f91da8afa052986255949d21d35cdba0b90d746452724aae83350a0e4a709752f11b358084445252a27ac1c5026c07bd2ee30c6823b593edf733c2680c43cefe2aa3555bd2ffd0d9bcc890c395380d06cd48c68469a8491a3eab02a63a2d26fc304644f14785921a0be623d87f25f5a690fe5e52bf4c7d3df1d4c88a2eef77dcf2c94215d527f5c024b614784c2d1d82aa22fb709073b4db4a191e243ed59355a5cc372da7d51892611e152b7fe3fb96d3af3b5ceffb054891a176eea9eeeab94fd95d2242bbdbca5d8a121ad2e9871a966688fad393cd334ae60223c324ef2d9e182dac0b7eb8b0f79856409abb944c6fe6721a0078eaa574996d4899d04a825a8123464ff71dde06a8e616f31ba527fe919c2f19de1f9c2ee49d84e612293d83ffd4ef3392075dc2ddb88b63b6e01ae733422a8990ed6d28c7b25d82df556af71f9b46498fa58b6a2cebdc112f2c2b8e7f3ef04ae25966a0c2bc6dbb0a7759a0d15b13cd8bd35b3610bc705ba57f9881342f34bb26fc3da1ae7d483b4ff2d46728764f2ac62a1380b3ac2cc2c75f5420b0248298041bc47c94ceb6d504bc3eec66549799ebbc46f88f974f4f6d83419ad5274b696101db86b9ac414e3749a15538d5fee5a981fcebf2aed04100ca17294f4efefca2ada8117f51dcc93bafb882d60b9b4bfc53a080fb4bf0321c212eee528480bfa04c20c4ca15860d40ff036f311815711878ca689e81af392234aaefbbda6b8ea2ca5b4cb0a5aaf409a45cb91853070b89613477ff03463410f7feb4a09c450c16850d64cfb6cfa55398915c2eab77bceb366414d6c36b48e8784afc85784e36ed6ba95f863a9c400be986f09c34774e7c63062c804099130a9aa71771ee0b529b8f382fedbd58ec4e92f929983e4f2abc11f189bb9f67202726092eef4b0cf4324f613859e72eac25de4034b8e2588bfd1261b1aeebfd227755869bd42093c572aea7a62452b0a54a8d97ce911a24f427cb9d57a88857f16445e8a3dfb3a5035810c6c9cbae301a34b7a3dace657b7d5bbdbe444cb8117f56e0a7ad36bd1ee05c43a4b7faa02addd4a5374cc53886376445c9f753a79c6344591a0f78d4a625f83169a1057a200e1337e7a05e490863e090d196a1e2acb5ed8cd2323fd8514d3d0c13bcb446ac827e8126c11bc86f2dc980ae3fc73c2d9ecbe58e149f57a5f7143ad2b5459eef0d82cad016ac8b4f3281f62ee4f1564286a45c55c8bcc4165d351453a9de02496c4ed40cd678aed24a1e906afff1d56bb917120c7fa9c0e6410d62b153ed3ca29f3f761ce0c8b6125cee270409cd88c67f83a8bfb5963b4639a4ce7298c87f6b441829ab30473cdc854c93ab5a87431c031d748eefb4dff3d6da3349254fab6f5a42855b0347ab2c67cc996ccb8daee17fa7f1d442b76029168b9184d84161ab5c29f2e5db2a9a61366e7611a7912b0abe72615a04313fb71bbf542819b0b4b78ab24ae793905b637409b7c3f442f955a7899dfbd7f417af8ef2dc79b2e6a7d1134041fa7e5e3d122b5803cde6ceeda72519663842c544f86aabe17323c1287d6b94af1c19d24f29bfa83b576ddeedc0446f3f3d48f94b9b333c2e99655e771a65df9cd019df98c77f749d46a226bcd884b4fd82240efd79fb4277ab0b38d8ab95cf5e2a649733498aa610acc4861cb38149a038090aa530a89adc718c17ab0f1d97bc3066dcac55a37760a9f3f41bfc9ebeba5f90219712dd18ba839a794611a3f9764bac2ee83a07b893029827439f0125b2656f04245a034bcd33257e13bb78b85d1928598631c693d7c3142bc5a292c2a6d11628e1453b5d319e0be99826db31f4f96b04e40954a30e67c4c9c03ae1617b8793a2e170c68d2d041325dfc829bd1f2afa3e5643bf7d819b20102ea7f3b238e4af5e83b5473cfc1dfe9715ed14e63ff4bda3b56b885d251ae2a4270b70aceceee95e00eab11d0fb04bcf0342e4af935ac645a378eef89d6db6c585d9a46ae17bbe000a4e89ddc7cb52f962dc176d549af8ab7de1ef912d1b6b865beb06cf3712924f598bd41ebb2dca050321d94e58308bfd3c007a8900d21cf148239a75270126155111e71afda5f1fc4a62a9a7006c59e57c9815097ee086322be199dbfe9ded67da0e0c0dda57a7bf5a19ada312bcfd6f0e69e7f7743c2f002a477a94ccff151e20aa6004fbb717b20893bb4e5d2c7f9b80eea07a48b4eeaa2b515f4b771c7ed065a1ce8b61027b1d39644950450074329a02767900175030bbd6f2ac6de8c06e55c0392aa0bed58e7c933ff7a91c1c4a6a596d80bfc75c5f829517cbd23c5dce2105e67f05e30ae4bb32299e8203c86d7a81a5dbd5f1aa49814c15c8a174dc43846a842a664cdfeeff5a2b6737b832b72a0c36f6e4ae6bf23ebc839306c0fa1b21bab1c73876f94cdf105202ed55f59c47ec832f43db69e263c063ed0e559ad2818f8017448c366ad7349e3bd540a2cbe0322c8b6d264fbaa62d71450ee6888e9f9a1de6e83057ae106b79d604dcd02db7cddfc6a41dc82d4bbd2fe7a25e48c6ee05abf2354455a0ccc17a9e06bea101c254525c2539cae2b40455ac7a822beee61eb10f53894a8f2f69e0114555126964d131193337fc831b9eee7aa0d5938b282bf6635e1c6d5afa43ff2316ce278d9e42a88edfc5f3a7a82b1d729a051473105833efa6bf004c7c4173e2cc5f127fd27295e592fc857beb4a40d3b4bdd32c8c6329528ea18649c8cb9f244808bbb6cf778155541066155865f182648ced1be7e939c55293f8694d38c2f827d57155e5852f7fe888a35896f6b25f4e7aeb8115c8c47f77845bac36dc38efbef3d00612bc08a0fa0048f39fc1517316c2b6935c9e7f00613ebb57011656be8a93b6b282da71112fcea018279a8e661098df2dba2680cb85cd439fb96a4e440bc1e5f4494a75e850adbb902b278d5f57bf451c5a865a0964b4b8876539ac9c2af962dd1b14f949feca0e1457b4e7a8cec15b3e94d6f4f9a79511a9314eec626ca43c9bb725f3f94a8112daa4a69b13938de7b95d472ac0c34572d64bd235495c78dc07e90ec4f72122b7f216da9cb445bd3e5173a4e4d2726c009f7fe66cc7d0de35300078ef4d32bcfdea778feaf8bf13d1b848962860f7e4f2fb6f1cd5f30f3638fbc6eaf8919bc9a38f7f487140213e2747ce838f352c966ed8b4df1d4f3fdb05c461b4ebb6c1fbab78277d8bebc293156c9e0bb6c9110f635478a696d046d1a457ca41df80b5ebd90f86963b4351576a95610c459c53287b285923e6c80aa9edcb9df943316b6b950bd42f0b848b0a5f7100a26c2c9892159192a474d54337d5aaf73e769fd78f444520e6b64cca12d6cca6d5944cdd6002b39f43081c3e4629edf69527ae908d8f205e8390fe30024b80e87e72ce194b0d346bd2f30b304335f171a4072b187d128e6b78ebe7652a0dd916ec8a50e7c94df9e5ac4e63757fb61d0e6e29497e82526dc9d46ea8a4d9799fbbffdbc678cce537055b0b2ddc7ffb6e7df6dc0c4ec312a1861b98ea18f5b1fbcf2ffa141b05ed0d7108b7c51d42d83fa0dd0c721ab2c2d580968dd3b5e98edaac3ff43d2990b0325b78e2e52b5db7b9bd6504bad398b5715dd0a3c87e660047dba7dd98395fe8af087eb6c110f5b309e8b36bae3e1aba9bc186a3c451d8ce610082e68c6dc04d99fdbe326c5a7ba9d10a9028295b25840909ffae8866c329effea53ec89111400d04a30071148ec59f3d47e6525690cb109814fd1f616e674353de39e902ca114527b2756bc6a70e840a7fd8082264202c6f499679bcb46a461607512c60dbd56f0f50e2fcf88798deaafadacb34b09ecf53208b952963206bc1aa582e7ea1f2a102c03ba1eded1581e5e9107177c928fed0962786015fa1b932e151f40824b16dc9122c38cbd7323981a2630df5fe372e70c4b21aeab460dcd3ffd43125d7b655e746b043a32fb5744410277abb67d3ae21248f12b0838eb44b81de527371f8d798ac97a8814723a43a58bb20f3eb2a8823243039a7f86a56d889818c1e6818fdd575e757548179cdfae270cc45a774575e9906f1a8bb49aa4adf14446ad008c6ab3c57849f8041cc4156b39440fe43732b0e9b846e9cc148d1eb2d82494a111a088c7d90b50ac3d5337b102dd98c9cc4dec34a799ee9cc56032ca4f458d4a13de73f5d614716b7d940d3823dd78a4f9d97a5c445d1506b67fdf5a284523885049290c1989fdbe3faa98407cc7e7d5a4ad1e830c5d2258c1b7c7f307c384b8623cea2723f450b8bf86114f952ac293e4edf0c6eb2b0fac118887cfcba7875af9b14063ee8ba6e28a85bbb87a49e06366beed9b3e1254c19b13536bccc2ad44559ca81c04210e8adfd59edfe2735c98f41f3c35cb72207a2188caf9391893dc367f5765923b0430fbf72f022da2c73ab0f59a73814c2202e8cad0b80f3b96093050b391d0cf47e07302044171530ae7a14c28671011250f02de05ac459bede9ed2839785a42bc7f724d8ccb14394be2eafcbb684917f26d02f272b5f2f3e4e7cb71b8c3fc1c7628c7a9147978e1a2e3ca05f9ea9c89c525b929bdebeaeb5e02b6a2f36eff1e81cc9ce14c5741f9011c676358e7ea596c1f3e674b4216d3e393065ac6c909af9903b627f682b3f51bc0c804fb2f13a88eb46d1e2a9eed89c6491b538a46a1a585849d1f7ebb1bccb641683ac73bae51604305af71205096d9367f6b624cc540e39d026163b7f2a2e4cc4df76bc7a6217c497dedb071b0affc451e430e7a7ed02d94d114e18d23cc666c9e27de99f22449c5fe9fd9e1362a392903b59da4e09c6817cc8d2405ad5bed5f848acf177b6b9f8bb93a6a0c9d3e9b4403a1120400fe517bac154405d27f16890a8bdbe4793525cdb3beb78eb04b5da50014c8ac2ceb1bf21ce1c65e26125c03f1a72362effd832bd57100eeedb2fa09c8ac61040c0c1ecbe9a345e0089c2802a733da80bac7dd451c82145fafd8e66a50d2138054520d5ba031f91026e1c1046c1253496c4182a78c127ca0fddd9d93d5e4a8c2a7b01378d511d92e0bb2694736e7fee406bb3945388e8179702c2886122034132895c1435dd84e98a460a06d1922b6b0882e3fa02e43309d3b058598cad3da8253d470174391840093e1b73e53b28999f9712ddb814ebf242c75d377a050f2c921b76c0e63d07bfebf93eb28d0bf251e03af5ae1b79939fb52697dc44c16e1dc94cde8e3893bd26441f64c4f05067b52455bf38434cbe42d7189589d6e4844036f22817d1a5952379651ae5973c269502589c79b1b12df4ee155594b5c994c5648a4fab4aa0a116a19dd69d9eeb09b49b6b946d7740938636e4b194374c166cabcd40f01d2649e75b9bea1f892040479cd81a638caeb5bda423e52d98919a343131e397a9d76e2d115526d1e9d0c7584520a3dac293ac760d272c0cd644df120674bac1fdaf8e3f461381178b3a241c6587d2c1a1c90a280627a789c0ef53d26234752182eecd499839466b6ce241a0be09016fc27248bc4fc1459922d4160b752a97a217c4b25070032599f6fa76b47bbf59a98f44e78f46289654decf65c094e554cc28ebe8aa28f2a97a5316c871823712b6af57c33af74e84e7bee7642c10b3de00546262fa13845a12fb022f393968ee8d78fa76b7c2c5a290f5bac62d2e1ef3b7f2e9d36e0efc53bebab7d0189190df91299af7e314a4b423df5cc3e7038d0e430ed05285c5040b93fae4ddcf821c92d15b5f4c4689f04a16d91dec4e3207c94a5b004ae9941976c457ebb43ac7a3e151762600f81195beba60969b974f9a37051a838ca55bccb54db202b5c9bf7f7c9c2174f4ae63b87696ae8eec327cc42f7f39b7aed9661b934fb5ecd795248ae18bef95d04eb2893e8a32864b3d4b436e66aa2caa95fbaf37e3cc212b48d211d466ff79143f80e042e8bcd1d12821f16abd4398d70f72da5719b3f9ed364d365846469858e9abe7a328a8adb546ee5df69f4995d65125072d337b04a89ba2c758b508467baae1c8e908fef7edb0ea04901ae3a75d0d9458f6a6a2c8f882b14c257f2a2cbc5f4a3dd9c0cab737b608f98e5aab44af0245571065127c8b7d87ff2091a6588869f45924ec6eac1e7b7c214b8958dccec5b1ad23c4113dbb7a086cee6d37bd418ef211e768c70dd0a2ddc054bd00c6b4251a4675f459a53bbe2407b23af4594c04d09d55f80c1277b1ff595d0ff6c7883713b4822c48bdac676df8b2363fab71ca592e69da76038f0d686159edc674f7e3c19a7ea672835b683e29f0cdbee7abb995c74229fffa2aaffe43cc41a26bc1bbdb7498028cdf51e394d9c0b204203a71250e1b0618831649b0ef20e0c74909f0530afffb38ed7e4dffe75f626598635c3e3b340c420696615a592760bdd8686b1a1b9316ad71c53fbbe2cd87eceaa40610b063f476e37569b169e2ca439d1f9af9e8d23863b913fc6863d69abf938418790fe3794e3c6dbc4cbece03b5dbd5830be46843b1b8f23b4b623f9294888b1cae37e2ebb85d8d381562c3e11753fcea36e5d0670733c01d1754dc02538c7ac675688cdbd4b5cb832179e9512867a2dfa39581517ace6655b8a9c0a8a98474ec0465eff52ce06f711d324171540e212455d2f297a130e73cc8ab82696746bed2621bb9fb17c0b8e431c0047cb061798722b9f6f58644ac6c735e6686ace4ccbc750ff978f208db885e3b11c765023e8d554724b44bb5ea87edc1e7fa59b3e5a341e12aac979f1e9ce9b793e8bf671426aa250ec03316d2c0f6f77fe67fc60dc7760fbf4696e79a33b992300584fcba9a502fe1905757f15534b347c7b643db3926eb0f86a7fa0ce25f3e2fb880893f9d4bf9a510c2cf05218ce216ae38738f032a86fbefa94ffaa6056bb72948725815fbae3262133be0d4932a70f3d006e2bfd9d450251d25bb3c777553bd59b5f4b73f851af6c05a5db7f02a6274b82bc4a7e9368f9f1ce621bccde0b07f3f19828ec7d96bc8f69891ede53a807bd58e25cf2da372181a45b99b9915535eebb505aac94f4625b002541cc9a7941323b1bd3672337406eee40a16f169b23aee122255369982ea24605245c626b613b968ef4514a747dbc9180ab04c9a8aa0f35ad89f8a53eb892b7b9367440dbcae889661e56043978d24e8884d23f772b2789287049f308d266cb02a4635afcfa584e6c3d27eee09043cda7681ecccaa9e40aae3bc9a3d0cf18a8eb9b5ec1fe460dd495732334c98f35707aa4364b870e43d3386c8a168e4ec0aaa4183c9b8da4d6a3a0afb2f4aff4a6b3a94c518811ca7a291dae6c070c646df3b82095901b755ae0c42428d2361e09fb0168a8564380f20d582a584472ac1ff5b029d05e8c94c0961ab8a6e25814887e0ae9e060866e21a081dcdccc0726903df0ed29092f6253ba087cd176d9c1f429ce3dc77e22031f880949af1ee8fa6965dd25247f815437783731b6cefa6b51d95fd20d4e7a8aaa6abbe7192fb6ebad6a35c8876afb480a06b439b6404612be9b246e7ca2876868434fc244edf09991806a26d48d83e32349cb29eafd93bcc8d177288a7d4826df46ced05eaec1b569a0d1e148501615a79cc77e14b366f3c458f211374e4d4c7962c2834bee162ec637a5a740e0d76432fb9798ac2529337b51495de4f87c652e81182b64e1e4c3019b423bed0ce180f6b544c0ee1b56dfcb306cd0d7481d20f109a33438e80954e63a8725e2270f3331078766f44b0e7ef9dcc86563b896d86ea732cf2e0c8a522413d58bc6941ea3ffcaf643d62ea115e7aa66077a745e2b8ca9886ae8170995a5ead51e94481d685960e2fc4c681e0f0c95801a5ae219a3a60699133158a2e11f52f366cd75481321a4489952f5e092facecd34023adbd40226814230fd32fa02327bbdbded81f017fe8ff784f10b8b20e9dbf45d15fea577f4193b421d69a5bc3566acc88c368cd91a13e509b65de000a6354edbbd58fa8499958096b50d2f07f3924b4fac6300174b2e2ecb1e22936cc62161e7c8d30ea47bf20fee52b06cf65b68aabfcf34e993f8f8c313aeb37af400506d8b5021900317a374f0c7e602180cb1b57f831a2540a95cb51956b5c37eb46a25cb337dad25ab903b2513750379af5f3c0def5235530b06540612da868c1fb83370392da35e812b90eb5d4129ee60f80549e4b2e560a00e6011a740623a3722269444723adea361b792d7c46b3fa6b3411a9073e1ee2bdc59f98ab8820810501d21475dd5cad9261f47d8756148f7e996b990f50ab8c019eb710930b53acdcbbd21b13e33a06c355a938937dd62bf662f4d935c4c389c7f4c3cb49ef534e09af72b6fd03beaa98992cf81d47a2b85cf60c6af9a672524a73f75b3855fb8a852ab9bf196128f04bd9e2f66e8eeee2c3fc254818c16e00e7df69f10259237a10831c3293d4e0e662aa5ac85ae38b4c78b421d58c9da69b05bfb388f943699c5dbdf9ff8a5e0d39910850ab47fc0b5d0c3db8cf14ce6a42be38bebf1c3e3515d9b785a7ebeb4017265e3e62de90c8f59e79a559d6175402a8a1c1a10f4a5dd4ab6cb220eff898425e7356b2125c0477803f05b21e251120087c6c9f0d6c17f6c97d0671a662457ad01940bda068c1029ee999302b96a0e373420479a91985f59c356433d67fe91a00e5f63adf4be4cabe3e58272445e9c31a2c015b2ae9210adcacee2f05c665cc7fdbf8812c92c6c029174a22e1813924c4e90bbd2ff800d9996a39735971c85564185e9b2ee701909225c1384df268d8ff45c96823662843f3e9c096465e072b6bd6307073b85bcbe4c4492e46f93251007bd0e3aec3b9271ee856ca04e833089e3807b48a1b61b6c3f3136095ae45bbe65e912eeca5e8ca5c4aa0f10db888856f30296c7d400ac6f539b352023f2f5cd96923c13d92c4b66d4b0277316669575f985714a601b45605eaec37bc58405c15251d995e9d11ac14e395bb95c541da5711d5c5eb65568f3138b1cc1a2418b262c28118f0fff3d7ef73bdd824e1c6c541d03f927ea1fb557cc7f1dcdda93bfbf66de630d0ff0db5465a6b9b344208217befbdb7dc3be10b700ba60b3e5bb02ace85b105016f45fe0859ca50c28d6f197145deda2ba18c2ca19411a339f9a227f2d8a18486080a7858d200cdc528124407816171eda977af48962fc2882390d0c9f21568169897fbf2ddcb22cbee25b94256c1a326c66558aabb16d02c3740b3d4cb873b3b93ad8d517f3407ff727798971bff8a2bdfc95b2fb6c0b7d2f188b7028fc426c9708bfc6034a4c1c24e911d2c9250040b2a7cf25b921f16455092adecb14c99e3bac5370704e2b2efc1f822e8d4d9e2ae092b5c8abb227277a2322f2cb631b3bd95813d72ceea302f57f8643da67abc854536b7c2f1fd99dce1c8d2665ae34cb69bcc952bd680c01e463756066f982064487e56040127c7c8cf8a20ec6488d1b888dcee872cdf13b2bcfc0f2e52de045b76c429cd4999841bdfcfc80fe8b378cbd64a5dbafeaa6f08be9b6c4e7bc80457fb8974edf3743b913e3a257de6fa4b7a3f5966ed16099f28b631737dc59289e6b4bf9facddaa794623c55d1dc2c260a8503f117a7d087ba9434447d9a08b8df4a71cf43e39551c3bc7bdf747b57434ea1ea31b333aadfa0938c3ba40a97087bbcea37b2fb6f4b33ae9a2b863d157dcd5a0afafa47a6f15914e6d220c66be8626e9267a2fc260e8632e263dc386b8327ddc998fd6c492d6934420ec259ba35a475bad5b7773ec1c4cccfc2377a24bd149ef1d70063da97be44e342fa6cd238b4817358f2c3a8ece75c68b63ea654c16bc669cf3cf44da6aadafac08537b9788b2c1bbd844a7e446411ff127d89e624be390ba3b4993841b27b6c49d147c6a1cd176ba7d76483ad1b8138be4d84f64d929ec25eb27e00c6bc58c465082a87e02ce80595a18485ad65a224ac291bd687bd0fe6edd0d3dbbb613fdcb0fe3c8dd6972242c7365fa32333ddd40dba56da2672ede235f48f2cdb6d22dd03de8424243933ebba4bf7e5d5afc42d22cd7e941d80bb5ac9ad0b3cdfeb4ddb7246ddded1c229148093d5d24ea6426cd6b1a9e22eda221b24b0b3823e5b3c3d1383abb18227381bbcedd8524cfcfa758d9353a9fe1186dce6b24ed139fb2dfdbaec6a5ed124594747b330cc6c5298e01b9b826b208ca897571b1fbcb859545ae4539b1b01e5aa6517a7a3b07894452923b7b29b2bfbf7ae08cf9db5d3eb9b324aacd0b89bdb7da3b12ee2e24a46b2775240dc7d08b4e9aa677bdc3beee6a813e7a259e443ab5bf2299b58dd26716cf5f3db0872cf4d32642d93acd64ebb4ec2e6d7d944825d29ca5123edd8b7844e823aec4c7eb23c7efe4489f1d450405e99a088ba0b832a63c9f1df48ac1ccc79070072936c49567efc8597c68833e488fbfb4a01781fe4891843b9938b117d068e6bbbd4cbb0ee4d1ad7865b7de923c29c59dcc5b12337ae836460f3d1b8d4e37996fe8a14bdbbadef14e59ae4b7f690167c00b49e8126dd5b3cd1289b097eaf0ca46d80be8d608924817ddad337517c8d3226d302f93de5810557512bedc25910ef332099f448f21bd5e84eb437fd826ba4bcc3e9ac9739331e510beb4803d64bf9b4b3c68c3acea545561286b85399e200b19ba5adc8bf860ecf72eec08b753516996ebd63c678c320e79dccbf2efb1bca020098f7bb9cb2a335a01ced07bf85139642aa092e58bfc1eb52ba5cc8b069d9ac8517b5dd07234201acec8460332b2d172b41c244e64a2b844fc9080e32283cc7561810221594b49e2269116bc103e4208cc10c0c27c70e1bba208ce8d0e9a9e2c84ecf8e8b185122357e4e0d4e840d23344c80f1f3f5b1c3112e3c5832193446be48a1c1c59237520e9192224fe883e7eb638624427a76b7c901019027f2cf9810ff6e0363ed683fbf083c14d1906c8ef12420334f7e8f5b66d5e8262d017bb4ed0a906cdfd262e18249240bbca60d5cc5a1fb73925c5dd4c9631bdc818ada5284bd9c963da59e3d63ba465a59cb7b3d66dd2f842c19903cbb2208426ec0542cc4473ef472e24971657cfa545d75c482e24540e0584c2b13614106b43e5503956dc22927b5f2e21b793812322c036c293b2ff64bf8e514af827432995c0111290690eca54389d689fe61e95fd85c3edc146d2d35f689f190a65159c48426fcd79256adcbc81be3c0e98a3b4def8595957317cb5b6ce3ad588af8f38c6ea1ad6638ddeba7923cb1b90d810f52d97b895599847bd4abc04ccb3e22ebe8aaf38466219d3346ec02cb1bd01734b17f4c91ea65e7ea6611e7d0f9f969019fe4636932536c4fcc34bc0dc8778099863f552710cc501734c05e19218242053d0410c151e79c26993e7bb141df18aaf3746a69bb40f86d4018fcf6bb9decafc9c4f25cff722cf2c600f4462599a6e796f4549b7ccccbf36491ed2c7bc6d085fec8fc4c8b127aeccd6d123cf53f1a75766e3ce4e7a7b768dbd5edf17695412f1c95ebe69dca226cec34c2ccf5f55b8f5f1d86b894aaee783fc943be04ed2e4f9ce459eb8236d5a73b306aecc9b4cf7bed835f3d42543578c9a1871c6ed5d0367a4e0b5803edf27782cd4e7bbee3293da35f171f5a0adbe955a8f6df557561bab7ff495a296d2486b8c466a5f60888197170621ec7e5242188525ad68656f56fec93fd88669ed166f73f293dee2db586b4e360c2bca2c6f7583209ae00cf804e9436779883b999780594a9b14e8c831025b64fa89248b9c437e4e188190dce180399e526a3bc17a0aab7c4adcc8f212325fc721b3bd3537eaf5d5295ae983d1593e28b184cc5803e6e99b3fd9e072779a31b0833fd580d5eb0ba54dffe897b0400d780a7fb2e720a6a79893d5464d7a08e71be24b0bf79d28f7209d98c2f7e083015f3477ad0d5e47c9379f7cbed77b3e3484349ea34a471dc80d0f9123475bdbd65ae303711e8b2a6a1a83998f32be0153c93c8cb1d60e421bf035ddb2c437b20c672a8439b0665429e54d69ae13f01270e194b75c9625a948732567ed768961363c102bbed85692a2284dc6396f2b9573e267658c29c48aa65b1b5075a8de6b1d5b2b1d2aa55942fc8319bec793b11680b67c9bc5a52b1e5d839a49b323757c620f8f223f4a70a00d8f148d46b0870ba149a35591dce9e182bc5287148a2a90594fe942256700451e265917517206194d204803229e0ff2527282419801c182362831c29432c42f03c2c55ee28902451c230643654d05d90fb21f6441b220990f321f50616488385061840a2354f450c143c5112ab2a00209153dcd5161848a2b9a7b4550714422e15d1b57e01bf6a0a7681b5b344dd3b9711b41d687b7210f7a0757cca4b85d4abe72777a3e48f91e0c197bca48c907adad95ce94520ec9a9d4bae03c84104208a10ee6b55db1af29270f2ec01869b060e54c86f0c77d504697173a1feded4e72ec77bf71a55d64b3545567eceaed92d6453e97070352f8e885d998f1466e0302d2800c800e882d43bc2d705598b1e5016179e0831134a90d79fb0aa331939b06bd9c72ceb781ece5cbc4175224c8647e6f38630baf5fcfec0166899f173e850b95809a29e5f554530032059b81d79363ee141655843c800728fcdeca8b91ba2c976780a00e6bd8df8071903aa801500f08bb613646994a25a89434561354f1129de9af8a6134e4a59859c078e468c3031d45e1fb91be9eb0a7a8b751d3b49447b53c464d7bd75fcc46bd7dc56c4ca06aa69418e35c62f2b8a6d099c46cc427311b0f5ad1cda2c4657c4b1b6dc303397ec6447c210401cde6ad8b420fe98c8417c864b7e8a852da889797a7db3c10d60bc0948f8155d801bc2173ca83f11e8c960fda214440425160938da08492560b01a683db5df9d9be661a422158ef75bf207e4882656124b8dddd2d25758ab2da0808638c31aa7456e69aa378e6459e87944e4b12a9554a225146890439c408890449244a2231c64b9f2c21067157ec25269155764c423515c5107da218600ff0f33109159348b97d6ed4a994ad7b9a93524a297b70bb46923b99f9c38f1f7efc00831f7c74cf0f30f8e1c70f3fee555b70672c9821618aebd257c4d69a466ff91dd3b93bf27b3f9ddbb9b18e8cf1a0938931eea06996fc34f7e0cdb98d3b99b637f8457350458a37fe91fbef58ceede04d06e283c407098f1d385a368e96271b8d8b30c28ef760162ca883f8dedb29a2041e9cdcd88bc580dc4ee629b5b8d2222822821db90823d448214f4373d5f0e85153434343c3a3a663f77b53b05e5bafe7a494d2392ba5d6565ae79c93523ad920a59492464a245236b4b0e79c2de79c6ba8b6d6aaaab6d6bf9423f4f3b55a6b6dad94b5554559aaf204b686a01505249a3bc2084893848cf560923e407dd7d71a63adf0303ef002c38db7666735b856c63721a517177ed88161b8f1dfa1a78f1bd4698e1e3a7167f2833af47047a7598288a21033f9876691916b8c5cb95cff5aa746a813573a4629a10fc4aa450f9334128efb4d4868c4104973f43db748a6a71da5fdc74347a6f4ef47924c2fd32cdd8583890bd224d12c1484548cb656996679b1b728ae1b115b2e7a4aa911f41d8410f44414115b2cc7590e7a810c360d6fc8f4162341d236b7089ba6fdddb287b722a669d9bda15b084f59f6ec5e7b485d8ffaac5615ba46a3afb5dd30c3a77b7fdbda5fd775dd2cc606410829e86d2fbb8d685c9917397b2814023d93b920cc0ee1d63591e3bde205817e6d7dd0756134de7597285f7d3582b2ec5d61356cbeb7b22d26fbcd70e846cdf5f7622fd7ea7a646d963db4dd97af84c144a797f652d4346b2189442299dc64a36e516af2d72cb7efcecffdbcf766ef9c658799656599c9e106b3c4b0605f4fdf69568656b8109a3e213cb44ef0b0fa7df510655d0fdbb4d0cd2c6b74fbf770ebdf2ceb772fbcdbe8702bfd9220659dfa9d25780d92ee6128f46eed333994e11b35879ac8d9083e1bddf7067ffbd9c8a6b9063ddbe2e17681fe8255d88b3ce81083c1da255e42989a09b4a560dbb9e6264fa7e5ebba22f682fdde7b98972fb6f617dbd7cfe42db47512349321e87a5f4fc3c295b6b9eeb418285360d4d42c9df5fecb2e71762b6234e62d86490c94615373afb3837ebdb72e03bd418f69dcd98744ef401f892c9941d7aaaac21de8a08fee1241377264ec0fabe1d2a2ed46cc188e911f6d58255f615104238dbd80703703b3e8d84d98c4685497f7a02de63ea6efb12d347fbdcb70371fd24ccd3dfb108eb9af1e637f9bbbaf7aca2a69c82dc6186fe7bbc1ce4f6ef0c1a0b35e37d1ceb677bd2f2c631993b8bb0e3a86bbeb6d1d04bdeb5ff4b4be4d9a13b7fefd36f7b69879fb6a33d94ecadcc90c731f6648df9245c68e9dfb5ddb4e36004605d0ee383bf7cbfd97d2dcbb0d0d6267b0aebfcbeb6a16cc20f9328669d875611bfc253384580d0bcb5355adb9f6d5a76c6e0b95e076dae7539a45d2197d4060e9086ed737b77f34277d34873367d3f8ae56b8c35e1dd46173bbf6a15d5e5a2c8f8de07657965be4ae7fb48f19dc0eeac8f23670060f1f3fb2bc455bae4b07518411ddd23e7a45bec747c9fbb1c1d1c972a70a277464d18d908720783e84e08c7bdb737d4c4a7e8210a25bb4579712fbf5ee0db90c538c8e6bd1f622b2c93ec241724bd13bd8d1b6d72bd583611be2d0c01c9ce6e0e1cc6deac6a216242f7b2b826ec13ecc11741a395ae146ac1bc38e61eafa6bd02fe8e5ecb2b9ec322ff229621b533ed1df0cc11d1c1d2806291a309f3a5bd8c2ac702d4a25f6f94ed2374c5b2bdc6e9917d97e76bfad205ea6f8d48fc1229b8b8dda7a014db6e0d5db751f4a72c4b618ec16c5ecad972dd263167389d84c86588deb16ccee953dde8f36255e44d89e06a6f3e2e4f8514e8eb893814c640c82aee5743319b3b04b83b6ae896cc1f8f7a6e1c01ee0c82642780a7b51a23ac4603098eab0b98bf1e027ab706327f32fbc57b0a0f491d81226cccb8f978fb153d3438eef61d083a104508e877959361783266250b8b7639c51aee009b7d3bae12594500a6b3e93c1b5a7978fd8063ab6854eafba615ab85d4a9e5d7f8ee45a6bac5998b7284603f4429fb728c55e1a143aa8b197ec55d5a25f9fc975eb2e6aeb70dc3f8c9ac9d405bd82ec7541db6559d1aa2a09639e6fce080d828541e1c24c06579e6e33996e90c7275b51668bbd24e146ac791e8cf71c529c6830f1975f5c9191df63d0180f7fbef7768165c19b614fb8598e5862412405f96135b26c296069e42d1bd3adbfa6363ab105d270f7b6c2d4e6deed8642a15b1dafaabaf54046aef5ac973d71adeb92cd324138f57a32749ba36410c9934596b7301fdcfbcab22c18eb2159adbbc4ea2e51fee0b544590459051659bebb419ce624cce915794824cb9fe0ce44d337b1264b7883827bbdbedbe5f9f0327c2b277fd49050919377d24868eb6cae769aa5b3b24810792ffbd6c8e5691edec99e2c83544c891a5bbe986beee42ab7de16ca300deba187f083d91262265bef68f1b07590c6ccc9ad785db0af93579b101964c7a56e217682a9c7ab6c2fc7d06dee7a7cab6c30439a48afa8479ae502d121d4268b1c6fc55bd1d2b275652b87334143433382166ac359d7ba15baf5eb9006ce50b9ac2e0f45f05878b8bc99f4f77cc8ac93cec3161f675d65cb36eb2bd639eb9505439b088a10c442f8ddcaaaaaec2aaf6e61a02c002b01e0818787ae823b98b3c7c3123257bf91e50a1f005de12560ce7017baf598d0b1ca0eb7ea91ca63b2c3a6b2499ddea27f7a45564ca86c306f17e284e00dbc81379287b770979743e415f288cc42129145a411c993250f97416410b9238564a71b3087be84ccd66f64d9c20078476d86d86ee187d1081dbf945d9eea348b1584dac41689133bcb84ca43313bf0701589b3c3267578d8648e955d32880c929de0ba889759d53b43689166717119244b791aa16339b89dc4c131a1e3c7489de6644e73529ede488a93a5ca7db64ed22bd2f46a8342e00e0c8223bf03eeac54c1f0a639f98be756b764906651b93cc94e4e96e5a182a94d7394c7bdbcf257157e006c4934278f371c306f184773f2396cddf58eda501bf9cb53a9a5a49872cadf4bf926779a93af3629a43919c326b6407b9f0e5806694ebe83349d0c92036c8878ebd2a6596e288eb504cc3987ed65175bcad65da99d04c9b2833759deb47570487795b6ce244fda94a856c39d952248f3a25fa6ead56265fc7019f62f0c60fd66d87a854156c526d79d4a7e7d7345dd64739646a539115841f20f17f247929f9b8cc114ecd107c364bcecbe73082bcb3e86c1884e6f7a59f6cbb29388b1fbe8dd63322955d64a8ba39bea535189b428510c7086754b2b1b936479ca6a69dd9a286d80956f894fd56d127125deec22719aa5a898c45e7461302ede96b44fa2b3f9e4af5b4eae430e38c5d28a52d4854d39e12bbb56feb02aab9252d29fdc498ad2471f3843de4625b48b49ec672563923c8fa373d3575747714c9f3a959d0434ad7d91925685e5a30fec611e65c3ee1245dda78b35e1ce5e13dc5ddc994af8c71d17a242949c330912dcc95c12c7d4ee36ea6528844fd7b3963f3e89d8727c01f0ee7f67dade5d0c000c6db0b5dbc5a04cbec360e463288b0d71657968933dfe8a01ce9098ccd548f2c5ef05abd7b5bd5cadcad653af9fb55ed42b4b6b72ec1a49eebf7e677a510c3d56ce9672eb9e2512d9869aaf3866a6701403ec21498649f2dc92885c7c7c7c777c4ab063a18a14fce48ea1fce2add81284bc1d848cc26230cf8da3a83910f2563724723b19412ca059ba670628bfc508c38d6f25110741f8696b06d1dc027c8c686ec2dbdddd31c31cb4733437214d73f34c3437e523bc6996ee2599357047b39cec5fee1e11c863faa03e65926816ba75ef489e2fe288eaf348348bf5f90a5cc08866b93ea957eaa2b32a6201cdcde7496401cdf259e108deca9cf3d48c419eb7873a1e8cf926dc96b263c3ae696eeec813ea5053d05b9488e690688e1ed11c8d8f39524a296c41a61c782b14c7834129ed2cbcc1813950871e06a13b995a718322b9833e287d5964fa239aa502cdd25d179049028966c14e25162f6c1d616b4509d11ce53215a259386aedb9d8026ff056e8a5c8f60799c22264fa8ae92d86370f06d6841bdf37991e1a9182ad2c9d9505bd9a2069adb5d53b57d656d6c2aa7a652d865dc7dedd31c6183f9fbc2e2be68a62b79894a1f0a2b5d6ebba2e7cc21e733d1ebb1e7f5d98ddba2b5b6cbba83893ab4de6ca14a680c01eec4da67789f507f4417bb05364ce9e73ce0965c4d9b367f7fc9c73ce29e3c198c79670e5718ce6e667912b1fdf4f65368c3490b7b81f6414d19cfcb342965c08836c79c023e2c19037c0c1440e48532f0f7bc01fb026cb2d200fe863eb64e44a55970f4208cb5aabd68aa264d85ab98ab9ce668eb3d7746843810421376533d9645ea8f4914db3bc1289a40111c9f41fb6a2d1b5265df0a659fa8e6c4637ad018133b49c3cdab2dfcd1a5df0823ec25ee8bd0fd55a7f2291b0c8e6564c0f7a22ed7eb4cde427ba77892e1d92efbeacc768c9599b7db5bdc7bddb889927bf581991efa6911c29dc3c0f86bc0af7a26e138eaace49734cb062a62b0340407edf2836c07b2a2931bf144c2303d73c4c05009c70d3c4153983fe7d7b33288647600f74c6e3e4630063f0038a6e7a354d6c51003c0267c8803dc8cb2a6429adf0015221cb5b3da15661989dc2f2c8f381a75928dc3a4030b8251def05f77ab433ec216e1a1f97fabc6deebadd948052be697eb2bc751c12e4829e0f5e018d5c9025ba28924e4ffe4c47b9c95144505c8a9dd2a3348d7ceb90bd234b2a895c6f551bb19e1830f3b981996c310774c8149f2afa8ade629a44eef979bb7ae3eec6cd4b9c3245eb69ac98013354c7faeb131342bbfdc48280593b7d3c88a274a3b439f141a73846bbc53128a7ade188520305cbe64e70866573268dadd2ad92d516ddac1963865803bab64efb33b39a844918dd4834469a3dc68f68a4a353900be6b126c2a71ad8fb98688a44a277d44b007593c86d4fa7d3f67a4593754e910877d467b559ef6b6f677da8fa9cfda959ac611416fdba99dead4b225f20ec2e5d8394310c8d6cf08ae6e44133b8f0485440cad8f286bc0e8e204b88822c0f5b90258c419642b2fcfbb1c91dccc9523e868cd892c34fef6c4a0ed80536a5e0137eef05dd609e8a8e490fa08784d7de1835f9eebaf2ef3d8b8f680e893e00fc7bdd8c68c1cd524a4a25863f7a14017fc88d0811dc8aa10e1a8a85680e42165ca8652362cb6532fd1c7745218828c26616237a057e4ed9f86423471c43b10c03cc4d668c84113d9000b1e076170f3c0422e519a05b0eff43b73c20e017d02c2fcb2c02d99f8f346fe5e5061029c15b79b9cb1a90849fb7f2b2c41701ae49c640ee53b999d4608747493492e52bce7dbf52899767ffa04ffb984c11f2882dd0c78707843c200f087d20843e10fa40e803b7282e86575c30b78fc974efc39e70bb8853d3b2411d133434b048efd410e91d21cd023fc400b1a659b03edc948070eb0c90e507f05a62cdbbcf879766a1c2270617382b1c0aa6252020cbc713bcd08b4104ce07d781c906178a2c6d64419627596e59ae886c6e902c2f5bda34277b27aec85b1f8877a61862243692eef6b1d747deea2cbaa791b44f6c8953642984f68947c8f2edd33ccdd32c3d21ec35d7b86d608e4e07e99bac27088431cc3667d32c6ff2e8018b6016fec0253adada078b64194d9065ac7930baa704d6643e705a07ee341cb2c1ac81e7438419e4236f32dd7bad7bc570bb1b8118877b0b5e1b6b24cd8de2fef5dd608f5e91b74857c360446e077db66896ee29c9d2075e5e1d5288a0b87d7bd00b0804318b290a3b05028140f8943d0674fb0c04cae0d65d19e2909dc51a93b9f28529b875f6c6dae08fe62845318bb5e664126e632be9c370653472f549fed3dec2a6a051f1bce669235d336fedbd26d3297cb5cd6db6e4e81579cbdd1826191fd2dd48acc1f91f7b64556398e0ee19e134dcbd222f755d137b64c4e08e5c91258e44e2105b61d933858f1094bc5cdf5d48de09627b6a4f4f4f6c89d6dedb53098935b1a573f056e4a7a891afb88f005ac1eda44ebe4e10b9235f7371482c728591782412a9aa1ecd597b88104434cb02aec2bd8871bd7c61a7eb2f5b55dc22fe481a5923796459bdbe543ecf074ac4aa17ac3693cf4d48b35ccdc24435ab6d8224082ee81d0c0253716e177bba26b6c02513a482fbe0922c0f878c9ec3bb182ede7129ef8ef2ce7462bb26460eb88bc13d05772f1d0577a67bdf931f9db6f6c9b969ee84e58fd8226d856dd7e8e8e8bc9c05f6a2135b648f44c1f24873f2279be4692e8be664cf096bda2bdc3563b89d3492e5a794469a25e309bd626fca4b1ff384d32cdd95f3a3594e2ee1cd0996f3843b0b6d7cfc903aba58b3a359ba0683ecd12c3d487c924425718b66f9894b248d8c43641c92e5adb991bae6c1905813ee6bce84bb97c5e3c9f3fd1d8f3c6aa954925787974c3f6d302f574739d94e557e98490cb7c1f07c88cf622ff3d646e55853b5959a50938abeda281cd8830d05a49a1595436b55cdadda08eaa0b6759d832c3fda3a00647951d764099219068142e85b201012de73b97b4b5a071c23776f49de1ebcd99ea5369c0c73323c7c3f1942083f694e1d802cac8ad3c35b89b7f6f68081e1f625755dd4df2f8aa2ec7f6defd13ab6854b5da6ebd44dd74537b9b6759ae99d4db86b229be077afcb2447b38cae526934c2a7ebfa552a456962323d9af0f550c8c4e45936cab29b989834914d27dbcbd949663a29957e6dd94b26d31b7bc94a77e997b3ecc464cab66ee646cd2777e9ece4e5eca6ed7a693bc964966532afc7badbe8a2cd243ba6854b3d898bca28eabab2995d5945dd8a1f3d9353d9881a35f6a2994c26133e9d3cc6f4ec27a66737e193873e932f9313d366f565529ac925d2e8d748ab9a25748a129daa7c88a0b8b76096ddccda2076511745559455555545adc1d2ac53f8ea577f7b2979613d770bd73ef44e1bddde7eb495ae8dde7984bb262e45598a844f14752acb2426f30877a39b3c7a7f843b99eb6edd4cbe9a76108804025dd39ac81a6824cab2531be819753cb2d1b21108748a128d46a7a883402391e8813edaa8679b0824aa74b699c75199c2127ba96eab97abea6dc12bc6cb402757bfdeb4b0115ccb9a50b87e512a6557d8768a9fc911db4ed667b285b127dc1d69ab0ee6e4ead8568f55180c4e73d52746c3c6e6564854873bb17289d853a96457cf6670bbbaac6ea57d655dc72ccbc2f1158e699d9a8621351684b0a3b8b0938d2343dc9d208c651d5e3fc5f463e82b0b86516c084f7e43f2c362888f6c75632fb36737f602f30c4e7d412d6dfaaeed5adb6d8ede9469672af3e26aade50dc9b3e535b59bbd70a8a7faf7ae9f99dedaa839e6190a561e5c08b3be656530cbcf076a11bdb3133f036130d9df73827a37c7cef16af5f164a51de451258ea187f1ef7ee2e80aaa15d3c37732978df937f1a946bfcff71ef6b0dbf75ed3aa84d0f85eec1c7076d8eb9c0ddfd8ac38e6bd524821dc91b1531cd3cdf90ea9cc747befb13e8c1db45d176da1cbad1f8410d2834090bed3f7b1772c7b27439b94b92a06031f03c2177cbfba2bdd5ea6993a01676031f5a03fdc65358bf3d64227e00c0877c837d661bfb6eefe893ea63f4ffb33190728c39dccd82b7402f68051bf360bdbdb7aa2f835d133598bac103d2a0b2a27d264b72c79ab8b7104257e99b5c96996f77810f612e5269ba316f5ba61bf1bece26649195f6bc571e57b55bd8b632a0a089c11fa13bd8a3095935fa89379ef557d8fd83e7cb287a7de23ee6abcc777186b8d540e0883113d6222785df1542743892e0ce63d46be4b1e84450f771ba0aa2c72356915638d9d830999777247bda270e08c77d8513ab9a364557147e5640a9e92c71d9593a9ff55f9e4e5d6c9c418a95338b087780a83c11ee8da36fadda86318b63628d7f507cade7176795965193e5140a84d04c5ede2ad0e7b676f3285af4df60a751056e10b539635b3a5d95240e08c0ef664ea9dcceba928aab2371490774943f336f8186f6fbae53505c4da442a27cbedba31d4a42a348b1027e2f940c483f1ae664ab90af722868c1f8a3c24b702d7857550315cebd55da2f552d1960bd45b4cc9edec49722d3fb18d2d5007d4f16c46a20246c496208a987789725edea279d2ed34f3cc627097b0403743611b233ed9987966dbd24251c5133fdecaadd50baebe568c0bf2e53ed673ad53c7b6007ae92957d144d79e6d5df61b306f6086f4ce24dc3591495aac6016e56816d211b93b912e9b2b5de6451689f089443aa954d25e9148c73011869d4422754de492c9f63266f2aa64a261d75e6dd8b5eaa56d622f181ed9bceada5dda2a55954955dda8d9c444db5ec65edaaa6b9b497357c6b0ed867e37d2374c0bb73e89aa62b556179b5885d97a2b620f9df49a85480f5db435f6222a954a8779b9844f2637291d7b097326cf3e93b70e47d66eb2952ad236fac334d23693b551e855e82ed1a545367bbdaf21bc59f002c9994c55b5aad556cadeda7046f781dc3166b9c53c25b6da5b96ac91fdf770571f71746ed8dd97f146ec2aadb5d6d7d30d85bea9a364d17adb6e7c59d6ad6b61d62fcbb27e5dd82f2b74eb2fb68c4499955ddbfaa3edf58a68cb7007c232a6d0f67aa5316c5f856566bade7169a15d59533bd96db39ce5a22eaa22dcc50c5f9574925c67c59dfcacea84970fada877d79cc955cada3944225185f7d7ad5724ba20917c772f6bd73b327508a7083ef46c0b617c27c4a2fb18b23e0fdab66babb056b5e620c517a4a95b60e1851ef542425d3dd4a5c4be4ee60ee2171f0a76908fb8526fadd802696e7deb74e3aed94357741b7aafd8cddad315eac2165b1096794bde921d711341712d2c63cab53a85a938a6c270877c849f59468c7555028e56813d2a6eabe2275350adc9e382507063eca735290be279abbeda90d9e24b0b5831c48139cde9c4c31c9d66e9276b0a64bdb4803d54ec93e1414f5c7a08754028b8f0532748f528abcbda2e0ac9f161a1454e0c7269017b807f98c5570fec414e397b5c89537137b324db77b1a64a4b5f2bb66f88927ea277348ffcac8841cf1022d4c0aa6214567fd4037960f3409e8ed04810cdf51530f00a1e125c53e6a2c947bea259faf048ed183b7684102302f4f1a35b6c4b38a4b9ce16ffe8956eb1700f0cd2a0105cd8b255d6c69082834aea1a2cabaf960a5722971bc6ce94abf74db1c5c614ed27b654f8519890bb725189233ec12b217367a25b4a34994638f9264b7c13119ac712951d432c6e9b2b3f8c07d726b6c420f682109ee2759a8338cd4178141ed78487bde3630717ca1d4ff4e477040735553cc1f322924b5fe4a5acc50fcbe6a0769add2fc822a7e17ba42b84bc908e101b29d5705f9e32deda60ae2cebd65a95555dd5856155c55e2c56597b5595b5d57555c7acc7cb82f10755d12a56902467f959f1e32377fd01c14905aa1fb3da95698b7c2436c67d473c8a3a967329dcc95098bd284a0a8aeabeae0b77f5fa4cbeba4b402db9a8254bba56aa5254373ed990a7fe4cee1fd952dd52dc0e2e29c1ede092a6d151b3a3591ac71eed23ebebba407311091c2428d02dd075734fd46732157d408804096ed73faf079a72104b75901f10056a10a863c3c723cd41aa93b92024c2ed601012dc0e06b1acad9bea7e3dbde160104288bbee9b115c1a1b908edbbd9ea99291882d90a757e2adbd97a707f2d834cb4de3340bcc11ff543f15fea9f04ffff44a7c75a8925376ced0859fe6647e10ffece0baf44f7351c7ed20cf0f8f0f7cec2ce093c0c70812c1dd116b9a8b22b8367434d73fbd648b34cdc5c39fd8344da48bfca8a0105c789393a1922d3a78935d624bf35893e95e6bef35996cb655a060493e407e55a0c08a6c41d8a357e2618ee9f5c01f36b125be79624bcce995782226213706c9f171b60d8d1f305c98fb27c73f08dfc9cda53918317872434f15f95921041f99477e560c81060db72f0425b0fb5d6407ce11f2abe20448f2098ee457851357e40eea805d24f729fa00897830227ed4dfa3e6abf33256d8cb8b35462ae74df95eb70975e07b30b0296ef7e22a29a61468a5d46e8cf142a8036bc3958f14ac8c106584524ab933b5dcef237de4c8157342088358f23e126226f5e4754ef9f90be44ec694b3885fb77027e7b6d8832377322610f662d24c6eed2dedbd43d3a40e97fd44d64f60303bdcc5a58b83e2535e4ef9e95dc3c3f4006c3a9cb499cec356ffce30ca65a9b4a33449a5d2c97bc749f798a51394c943e9a4148097500280bb2e954aa51226c94f94d79b1e710763cf2887181feb2c954a37edb07530ef701006331fb178d3ab2cdd74ed3a946ae73092f1d5540d394141d95e3e8931c668ea1cd3d43f4e6e5935f2d5344b589a4c559a6e2201e09da632690240fa4d972728241396ef1db007d25d6200361dcec366fa0edbdb81741a15dc591600601d0e71624b6fc7386678d6594b379d74976e9dcee11beeeeb13ce926141d50b0e934f6e480eb491fe17ec2a4bdb54fec452b9d9c904e3efa49034e4e642e812e41ef1ad143d7fa8988c194623ccae71461a413094f8230994ca613d308f71329d73617583607fa693b99fc6510ad17c6830b27057172f7401cd0bba654c21792e640a351f3a0aa2da447ee26cd9288274d6ce15e274daebd625d3d5d2325965b34b7e43af5115ba22d511fb97f24f25c5ac423daa5c529e5a3483ac1ed9a075debe426459f6639d178f07c80293f9d82a699a774318f5eb1d3bd9e09e5a5d2f64e4c348782d2f98472d91cca2166824f12a39c9ca48444ba65b2954a72b34c36d31fe68093ec27d96bf65b6bad8ff6a2cf6494ede4a49f6c33d964428a1928bb62f4893d20276e273b53d72ded753553ca55aa0e0f2d001ccbeac97d0b36a6d58fec8e5b2f1f1ff3284952be538dd2492fe128524ec25de9a71aa49b3cc6f41a26273d8674933f69914ca6934c276d30c76b622f992563ac51abc0f6a2b98e9732c61aab3c156be49a7bb4dac7eb68cdc846cb69eec4148707e37a476ba40e499324779626463c69624bca9b5212577a94336bfd69aea5c4758bbaa4a2c9b44649ee2fc91a105880889fe81b98c932fe46cca59865d7448e19ddf437134370bb1887dcbf9a280e251313d3ef7662c2b2b967d55bff6a8da9e1cee4a71ada4b8f39f9cb27f854a374ed31da4bf5f596a69d5cdb608e22e52e3d4f2ce843c50f1286643885fcb018e2932d7b51ecbe985cb4590b7613b6ba4a27d5c94b5be7ea044b982beca59452b5975e2ff3a2622f27f8a45dc3cfcaac8bbd942edaee9f887488dd5ba1cbe4143add64eb1c3a6199432429b9f8b01a9d49f82471bc4bcba7dc6ee91a29e55136d3269b135d9eb4d9928bb70b9275afd7b75b684d09e5a592754ba55b2add77be255a135b6493684de928d836572a7dfea2bc622f28f854bac9c936da54489bc96669e2f514d3768964fd49cedc4d622f15c36a74d66eb25d77bba24d238d36158c86cc1ab6a7f912ee649e2169c75e4220b8b46647b384dea73c9a45e2d3d45eaef67b91484492f2a1b991a23667f28c9b4b1485248e12521ecd35ad09d1dcfe85599adcbf36aa62eaeeeeeec62fdbe6f0299ebece64a9e5d700219a7b3fd920e2c178bfac1e94ce41093b8a64f18d616ab83cb287bd3c5bd9ca9eb2a7286beb9cd6d66dde80f6d4f6624f6f1fcc9b12fec88da5e1be8ce513ac3e0aa6058804facc1fb8c4c6891e28d0c9af8a1d3b59467e55ece0c9981a7e2e99a33998733b950c6116cd4d9ee6aaaaa2d88b057a4cfd3c088b6c2ef5991cb70e479432565585617ca792214e4e968f3937e2686f32863b1abb81525ce803e67863ca29fee5f97c34cbf3d1dcc3d27061868185a1e156d7755d57160328ac33b48e0cdf3a72efc8b021a556de4c1f4fbdbf183f238cb16b42bed7f3ddafa947e54c3adf1b2ab9fded343fb3c45027c39d0c61900c77662848b560c9b69c30829afcac00028fdcd90ced10ef2efdb628645ee448821ff94b86a0b93fb13bbb527a88d5a0b9e228ec1fd6c40b20a490180d9ae5e72bb621253eddf804eb35cb06608673cac3889f32c6dcc7f0d5b0019768d83162d85ba0dff1a193a38de2525ae1688abb53ffd967ab8824d22e2269966551d2368fd069e4c8e421912609b4912e4191748dd093f0adb0b9780a34aa1689443d25c5d42cd4744d1cbd45daba2660be2ecbd2a8d61cc534a539537397b56a04d5506c1841f520d049d4e19d469a8bf4f66aa66d64cb70bb67165d8af0e82e3d8d80ac6df2dc6bf2348b853bebf5f15dbde491db3c62bdbe9b46acc754d2cb306364b8dd34d22c348a94d794cf23dd9282bbc73cd22d1088289b07911b974425710b3b8d90f0a9621b33d711c6b6703b95ac9d9e45ce095dbdc19ccd74ab545ffa6b96d2db84f46e849349b886b0212c18ed158e6c6efa19f6d235f4bb857eb291dea2ce010281b49d37bad7701ee8be8a0e04ce40f93b0d213ae1aef3bd96931fcad689debd07028970cc158140156b22dc8d44273df4ee37a98240352793ae6518cce90d3a914227854e18e957e8440a59595cd1bdf7b173687527772150e8a46b244d2777a13e08f4a785480fdd8a3b2d27872a7e31dd85505c80dec10cbaa12b0ae1774dee624bf9c956af6df72e1d23474cdb5ed680984826d27b267c22bdfec6fe7d85b0a7a4bd1bdde4d26bc5a1d34e26b1ada8b46db5b5450c198d04002000d313000028100c888462b1603c281986393b14000da1b24e6c4c9986490e44c818630c011010000001000010040018d4f3d8b44e15c51b36eb3474b49d030e513955cddc970e0217fc91498341fe38757064ab78827d7ec7751ca8d6843fd83e132d52decadebb85b0d5e8877b363d0fc0e6cabb5efa74a00f2444ae6b8336b0ab4f6744347a24fd1652fb82e1831171e58c792ce8c2ef36afa16302f8aa553c679565d297fb504bc22ed7e65643dda3791f71f29868435f7d79c060b2d281903a6e0062cf35bd5d43a5db27add816e0e8897b736259b506fa9d65d73a78b2557f58171c262099a1a3359b886e3a66f41c4e30f091b72728685d76822d164da5d49091ce08a1e6207ee0ff80f10398ca30fb31365eabbbf15d7da355bd355f3055fd47486cb6a013eda35663a0f7c1b3fa2bd5e933f0c3b2cc66219361c7321044cf43523c3980ad0bdcbfa823e438b0f2b161ff3004087c54e57804b7e8545f90fb93f119c8b3b5455f4b193aede098d061557fb6e02e0a0a51f03a41497887b19c5727e8d7fd0af838615343c7836864aad0240211e8b40742c0b4d4a9cc331269dbc92a91b87b1fa7d5a8679182e736f4880bfa923ad5c704e45d9dfb21b8fce90d644934cc673c0c796e98489b1611ce1b22754a9826ee74a1385097e1557d8de15e5f56a714e81c30a1c021cb252a2194a98da15f3a45acfc4d2f52e8408d11cc4ab6aca0a0e3c103ec6f51f2562777588320dde1b54ef0efb4c104cd6772baacae2ccb77c8dcc702e62a0c9e400a27f421824cc052362805982345cede15e9b3e2fc4bb75c4c3f314a2fcbb701750e177072ccd228fdd5c8888a82554dafe9fbd14cfe8cd940d929ede5a46752e403add92465b7a55637e0a245ac0a0b28d6939e35400ee2b1df118beddde104af32414f4f1489b1e2f7b4bb6caf059e6e69e6f1cdf98eda469b925b1d32c17a5b31572748ceb32d4ea0c8b100a83cb085153e8a88dddf51d90e81a0cfb15fee5ccf4ee073135808463a0267fdd085b282df928fe283f7180582b473896b574a2d259722da497c4b41f404c9c7078113110f9646ecef57b177751960b849a12ae8bb7887d20016c51e8294a43268f269e9f7fe243e1898ae0375acf02665f23c7eff19c27926f8433a35fbb7ab8e9a021ac9d505a05bd7593b98ff8b7dc8582a20f1956f81d8e3606d95318352b9dc927f999503fd96d2e81528df1e66c30b8c95994586715e35279792362f0c482aa08ed7708b97836336434f32d7e19eebd00dbb1c0a4ce9c1887c9a08ea14b8dabc0e3f69e551fc7da1bed1b454e6a410863da5c82bc46b270d9456696d5cbd13e5ec0966fc33bf640e240e66d3773becbacf6b5dba82bcfa2ba6368d12c5cad8fcc97d3065b574b0334fd8e8b545a21f8adb7c3be5d881f1cd4b216e9c94797383ec3a586d4bc82ee325662d2714207d81f31a98d5a546cce8adb90c592be968afc84a90189df285c3ecfb5106ef4008c2e995d530bc87999686c6b3be0f29d04710a86cb5a69cc93cc35c1014a723281c1c438d1a41211fbf2c817ca391346f18e862899f508425acdce14afe999d5ec178851c8845ce652aaf3c47d74703bcaff01078daadca9547557831438f10135f60675c8190639a843047caae8d91c1476056bf442d0359a643cbeaab3b1fd7b79a10c6509acd760f6a000d5a2b45938cfec461e5e1f85d2976b59c80b96b7dd91c101a3bcdb60971a3bbfa5c2dbef136e551c057fe185aa4dedeb0e8d173bcb49865fd115fb9c9b1f667baa5fd0467af9d3341b91ff42cfdd4ff8c7d04c3f56be8dba23600404afffc63a03467641e10c9612897034e20c01db0bba3fbe37ddf09cd6656e4be177cef296ce83c79c1a3c2b118f1e1017adb2c11b68016ee0377630e3a588e7a038e4708d3b5f2432d93aef208f58a9611fb935039e7c85c9a372185306e72385ecf1578a36143da3730a329667d33c234813d8b0ddcb0365a9023a30bb00c3f4e63b0a6ec4ff5ad035a2f5adefec00ec11ac5f784d1fba4bfc534887390f3ac94c9c5332da1a82211f2a6fd2e01c4ac092d900afbdb8ac19e8f1d56df257a8149a5415d6981a2727604abd10f88df5b9c4e0269f540bad8b8f9eaba22cf34734e92f4271e68bd74e839f934674edf2757af695d90742956d4e6c0e3d48c0cd5e41929ec7e842de0685ec9c2d6dfafaad3d73dee61b28bce7f1ca57630079d806aa71424c06594b59adc9a2eab13418cc9d007f61621f8b05dcf00e7fcc66908733c597cc20fee048319dfec710c40a0055f20e3047dff753ce2c479e6306253f037bb3b3dee6aaf1e33592e6b040066d2bc3d511222c9baa2800efd892804c5e86e6963ada4eb44710d5b4d58328c8ca275a189f97e96ec9fa9e5608dc5a94c45d5beb41a875886ddb09b750848fa0a7431a9e8c57e2eb8d3c6b69a600469e379dd0ae347a424e890b6d66dbbbf14ba6417a97f80b00738ad0bfa6180c4e27652667ed7c36064a41faa101b7b3a814e775b2df61246e8920cefd7b23dcb4644d52236248521a9b8bc5948d147d9a8312fa90d5e38632e0214cbe24e8486f593767d8185ac0fab4c5060cb5418b9978492ac24a538b3a997c8bc12936aa4345a951a6c020890d83031c2962a61b9b01d0fbe211411d62d8ecf2afba9bf0eba8092dedd20858190f532a66add448456c0780c4a603c49a1e1aed2a953b959227ff043b172a9dda00e4916c03e5615aac650ce8496f269386bcbf968e95400591ca595c59e48dd400c351c1147506c7afa8be11c7c1d287217a999298644419915df8043a3dce916d5bb9c982dff32c6e352b94eb83f0c7f9f1458dea70b35b7c0670aa834c0202506b28526b46d8e7f44248395c7d8a512f05a0b7be2d9363748aceddac8deb4cd893dd40adda3df56c44fbcf390d8aeda2646da7a8e1f909909abb49708c51dfcba66add79f2791fa61c72f858d435a48daa3898b3f9f88a9106f2c835912854a6754a162c71b444c5063299c6d4d3525c7b8eb0a83bf1e124266f51cff439396f370b60b866fbc8f296eac4efabcbd78bd1395782df932c2c78fff3c2f798323b05e6c25df991fbfec5f420886386abb031e1663006cf87b8a07c085e42a2293d8982f53014118acc5547815bb3390e39358755038ed22ba678a078593d845537c578870fb64af5a5298d1b94899af625ec8e9ad0de613f7102799d7210a757d4b9e01f35aef2ec43bd4bcc087ec5d47aad5607cc6667052820fa9d4af6fdef44013d8a573c86f0d3fbdc01b2eafaa73be4d3c436dfb1cc730e24827c1e8abc7d07acacef24751d49a41248ce84013e7a9fed244e397bf7ce7d59844d593ae0d8565e05aadb63b331bbb922954419bd9a8d2509c841599a84ec1498b8fa8c0b216594918dd94630f34e3d1774ecec029b92e3a850c065f16818d21018b68e3e88f4a2d0b70fa2f25ac870b820e74a8792c0455dcb482b46223ba66ce7307adf653f497089c966933a6dc8007861f5fada900eeb1bb9d515fa265ea66d72157f1f196e9cc649cffe9b66651ce21c6feb21dfa748ebd75c115013973b0e91938365878305898a5f6f96fb333a7a9d23a8fdbbc0ace23c66a9f33cd2cf992910df44f550a109028fa31cb77d9c929cb2e3dd4475132a2e1a37d7c3a300a21bb8599f2fde6c1127127b7ae0f10f1a475056d969d7a37220a4921361bb646ef04f358ab22c254cf62b0b3a444e093abfa8ee41d2f1075ee45543dad6c9a1fc4c961dc5e58ac182a426fc692335b45b261fb2fb437401db8ea1c1abbce24bca604e9188c219422ac41d01aa1d0464d96c5c5a10bac7a6bec649033da0a3a1c9bcf738e5eec206f809f013d17f849a22b5dc432f4f0d27827ea0724d4f4b59503f7a00775a9fe5797f79eb745a80ec1d7d36c9c92d9056367d2fbf471d3a83f9639af81e73e2f2e5a2760957f774fa40ade2c57af471b7268912621bd4259e0791689b6ff7180adf13751f6d555c56073be47f170f7711a00bb30f58035b11886c74e7ed988310d429987912e0b813d8b41065bbaf387fdfcbbc48c9eee3afd0a4d64a4c63a50667423038cd642844e459a36379ec701e719ce2cca9bdb9fde13ab1317233c367d951500154cf97999d94a0c3e03905f10a4b0805329dcee9de63152f6d4368f4f63139a772a4f7cda26bb9e742927bfede05244a6ff4e6347cc14dbbb03902095c134460f7ac4755319941b0a454db79400528c247947aa85d7116e0863ff11d8d2f553edee5997aed4d61b2ab663a631f56d21d13e8f7f468119852990410c53f161099a54c0fde0deb1b4637cb41f1b3d85aded0742381743cc0a003514fe12c8f88ae1e610968262bcbb4923b6215dd1b01cbf61d26a52ec7582c4ff78deceb9ed84c8d6c0f1255a642a5c1e12df9ba42b4a6d9e61fe188f629784e9db9cb01686fb6acb46c3ccbea48635eacc597af153e9fcf028f19633e565e7c5e19043572e330e55775d2285ba2073fb44732b501b1de5adea1a8d555a7237a292ce7b2c0e7e025a14526e2b6b7cee4a9058d984838d24d5250fb397a0449e920bded150d8096af831d1d15ecf02977fd5076a29adf04e988adc68d88d1c9c10de38242495055639f2744a33ef44f2c62171a201cd3442a2ec6aec823cb241bf324c8fe112a1acb07612a2e91751d1fc2edb5d6a4317a3d5a96dbe5840352388a50314044a86ef63b31d3729ecafe4dad240738da386f412c4515b91b8afdddd8d4ad6d0fb580e967cf59bd847d6d2797b9f3bf35176308e807476f2b80bf03c26331b5a3292b74373ad469ab843c06d3f2135a1632442fbe6e41055cdb7d7aa7653a061811098df7047f3fd8f10594c5635acf95d7f2f84909557d660382bcbf1871961562928a8a9bc42de260053439398452369e0480fd6fd42e4ab29da70c1c5a2d99b1493546f7318b5175655cb4485b9947543eeb17b2b4d6c91df17da9806862339df9331a2e52d81bea5ab909aa0e4bae7ad22f43247b79541a1b27f4c7f1258b99ce7442fc25b6fd1bb300d9d18df5a56023ad669990dfe7553420905e7c2e5f2d133cf9c4446e29207f14fab57af7edae062e249bb409d1e189d1824c0d681b6d1fc204215b3eb5ff80fa25b4f05a2fec4b62f80d9e1fd3d81aa3087b44227626b41914049e8fd53a7088f6cba505b006ec4024bfad47b9869b07baee2a8b8386ba7ae58eff0078af3453f86d1295ce302d90a46f2029e2396f5be08e4d8e5c028e37f207607d416534c00575ee8a43eae7eb1df90e990a8c69d98096b983462c0dc55d0f021cc6435b4ea570df79272f7b2d220d19698c8e8472b5c9fcfdec290458acee3c5934d39a95998c38545748963b30540c0f224e694bb90101385567d78131439d0e031a1b49c3272644c6ab21013b1f0ab31a7502bb116f9ba1afd95e93d7f8fc91b6f013556ce34af85185a975bbbeb773fc5de83f3cd94ff24cbf9e4f36f0014cd690e7da73f4d62927f04b42c6471364935c16349af4e47d4c9573f87bf0d1834b142d1cb134f7be1e49d729c10b9bf73e29180e9df8e5414a1beb7812194461047bdb258c8c3273b4fef944546715ed075382d4511860fa724e6ca022151145530ef90c8c54d6bd0a51a3f63955e32c94baba4925a45d1c93ba51312f95d85a5ea2b305a602eeb59da8ba5303ee66ec0b6ba15fd8fbdb7534c0648dc198a5bc852e4eb2f85f935b1ea1ecd531e8f3e945eaf750337e3faa118891d823f1001098f33dfa6a8c6f845598013209ff449d4a7c1ec1c43325feab58a907eef64481ff63c3441541a50676c4b4a5c0048055b9368449c3d9d46b6f0aac162006abac96209e66a2a3bb5706ebf48a592a31863821c065c37459a697b761a05f9e184a8b8bebb62d431a357e4cdbb83cd83f9a738db91153b1744bb82605ed296a6ad482e694514793c87eac79420d81a61c545b2278049b18ea204c3490a07172ae7b6421848237d0a95be46051f097c59e8ed29587cc1cb3a054cedabcf035de31102c2eef62594dd6afadebb90e1a825ef4edd7593be6be867217cadb93352296b0af893ce40cac4759dfa33acf977803aa23414706f2211ac53d8282e68a736d9b2da93535a1e07ef407aaf26eec75fd8e6f50562bd60f85514a607db7bdf688b19aa17da951dcefa13f1805f9c5b451904097847842e41684a1914198a3c1e85f5cc320c751b2696e936eb0c0219500929d0250d093035881a37f4787786b1c521df43e270e9eef53ede0c505986a7666766b9aef8e5cfa249a77aa36d6fafa05b284ac787a64aa30e1359469dc6333cc3b53e0b83f094406ca47e665bc983aa23b70822e796d232cfe1a77e60cc1e73a88a4b242befef5f8f9e4953c552156176dbfa6cb14926d48244986c320f12cb9496a58eb70b73809412f51d465c7713d7c90ca818af49e24129f81f59a7402134c8cda87f1f40067e57f3320b438e6086afacb256986743c3a962d8496b839907034e3f0319c0c38f20cab802ebe14ca4c23045f1fb6ddb937320b15fe294b4f0095dac63169ca77045d15bc750d0ab724ab188f303c8b48bfd115416ea30154a2db07e3baa60e794112c3cf31a3ce758f874ef42c7c2e1d5704148c6035486be410dbae426050380aaff6ce9fb783a614d5101eaca23011a006dbdb07c2a143c0e039b4276cff66745f5538273a3c8ac123c7310be5507820dce2247bedf53ec5e08a2db4edb083c98d623d687005664addadb48452e20e83af39cbe54c8d39fe88d1af8d0ebf4b3d5572c8bcc9949ae85a9a01963b2578d7870b129eccafca9558453e3ac44f3cad841ff4f7e30805e987efae60fd2fed051cd7a2a601c75e5824ca3b4e3d01aa4fad13f1b88cd1b78e250c57acf57012c8879c1ef03aa6d6daf1ef647a60a9d8bd9f767d48ae6e302071dddc64ac049966e2dc5590586b681e0b6accd57e2b58c27d1a8bd5959366b09a99ab6fd1683fba1f25ef42adac80932fbe0863cc152dfc377434c35faa0021f5ddf15908f0e7236191742ed5a3ecaaf0120707eadd1889a9809cb021593db39421a283c3655fce0bac2c2b0160c8ab703c70802ef25d0e37e42517d3c0c2dd82d4ead3ca94cabf0a63839ada04f30b8a65cd556677972cd71d470f99c23e464c6c32cc8bae147a1b0bb171c5b1796a88774585e7e5b155230e417c0df938dfc896b6f4b0b5a9a2f61077d3b54e47fce85b71b7641364105a28297c184e00f7b6ff92f067f95ad9b3712e0951d2d903a0fdc5c77e3230fa7d82bfb58c03dd6a811289c077db6916d467897bd1d33d98bac8bb518c9b0371b29bf85920f8791503dbd790aef479937ad0e4fab34e8d7df475b562b7f8ef0b296c83a261011c8c8c56effb2fdcb476ec0040dce1f0ada437f07d57f721827462c78ffb62642cb892555631d4544f5451a377605b06e903cc7ec8f9ec342a9db0c746a9f631d06e52e7c9e95e5842de7bf4c76a87e8a5648501365a538c95d366a0ff3869ead2804bafdb26cf60174257cd56791df74c298527911d7a33d80dbe703b40524d7705a6c049ff4cda1ccdcb3745326a0c0af48f16d7e67552c6091386fb09b65282697c76ebf1f74675609050a663735544dc42214f436c2239c1e4ec0c67270b47ceb81dabb070c40d656238affea8bb7be6f2aa6c57e43d0f9f080434397e7f95ebcf737eafdaba7d38f4b9279a922384739490d1f7d88f88e45ee6afd5cff7badf844a38c86f3ef0ecdfb52b9df144b0eb3c39905e10e5cf77648b1bd9623a75ea4cf564dc7e4ba276d8326edd38cecc9d9d67f67eb1834fe294a96522414a576ed3df2a9476485128c82292f973172cb131b07c0a602f928244a0a023bab824551bc196795267c4d3ceca32ac365d3a61d22fe6b2e5272842c47dd79ee2511c8a27076ea13fbe358877737b82003bf065f4f17d1800c2bec1faa292639fcedea63231a4f12118692730b71ade94286bb1a7b9f5ff1e2c6cc4ac0df7d815977480f127c9e3072cd022c8d11ababfabe65018865051e35c9ea731089909ac06f0fac50c9f1be070b12ead3280dfa299534f2f8292e62d98d30a0cb090f11acd52bcf95e2bbe9012db75e001df4d57bc839c94775f89ec6232285e4839a81594ab21e2707d5a645fcaa13921fcf19385404dc929002bfc52fd47d583013d01953697001985ffe1a65639c2655ae926243450ede4a81a9e8ee38c2f97250daf69734c4f2298caa098c34c4c41ba64dee0ba8884270e4ce623bb5edf3c0fca3613044e7527e5c08ad867b2e7c5f8fb96417c68a17c23288d27d89861a8732e37360007fc11b8320c6325ce157cf0e3008ca1176f7a1ede953ef94ad3b1da00e02971981df701de6bf6a90079579b4490d1d97245518b07699040c542505a824386e06311a8718a78a315cdd5b1274ae450e63573835b532b6342f1c230150a265e54dc998c39084855d0222ee9a8cb9c9c2727993ec9dbf2feb0e8f86a20d082e0fa1d6f3aa793938f6dda93f01e85a76a5f98730f6336045c71c8156e967c2c7e28a0fbe1956bada5e8eced1c52a43b3edada8f7bfc8d5ba9ad9905494456e2a27e7c0c31f4cd8d681498c239324c0ed26d073688e65a14737ec67b4222863b65df1cbf365e829ee76023e4a6cc00551729dfa510be76c3f28d17f5445753d1583a165c9df699f617864bab665e5ce4d0011aa13e41e95b043fe86f2bf8c17b5ed0c607f9a859c47dd51a4c751e2884ad041e4358219fc4f135f26fb681636f702f29898177cbae4961b2b015f01dfa1331101b6a42832f4ad0cf0fc138522a5ad63171212d8176ad2953a3995cf73338ea8c5df905ee93983fb001e95decb0ba9d4619490482dd9eccd0000ed84d63efacaa8689bbe5553ae114a2f423d99f1eb7606384c756e00df9d7083af4251db259f51ab65ec1a569ce7d649fd5e29809070442ea560d2dbf5945dab89a1172ef8eea4cfd45aedf22029a9a7e63d8a9c5ec60b0926f9aeda6fc039a3bba731bd74fae83af298d04ef386b2227db162d383544594efa415f424deb31f23a55ec8b1cdd544f07ee7478860f4082d54875d5fab4dbd7c97f8e2ea4a348cb751df501d0105b3ec1e942241028c6c323a7e6cd83c89cc306031816b43395250bf34dfb63b747ccb824fb800f1daffa1141e546ccdd78214b1a4d3e34cc70e7f027b9fd3abc03872bff08b4487fb35ef15947c9da7d26676497e9943e9956d65570731937e3cd60908e50b0582f5136618e38e07f731f071da635b7383408bae3158e015425b4657eae04bc7e87abed73161bce3f9342ab740813842fe56be7b9be6716887b41d095b73ab0a2b4b298b77b7254eabf087d9825ad5844ba66c8000ccc5705bbc2de541e39155ec62307bab4e361ae8deca529cc2e7f0b2ef2e77b8d1d310049beac49c4ed74fdba863534137d60b5096b02809d51fc4d355cb9ea2f37ed793acb9ec2d3c2d128c2d903bc11bbc7512b2229fbc91f8e4bfb72e364e1813110cfb88ac1e9e447c5ebf50b6083f606c02464e422edf93c27d8d4bcdd748ac9d067182d5ca8d416d6bd680152ad30f204a11a0309c923d968e2ccd1b10fb305f4007a7deb6e0092108a779437a2fc6782a7d8404c563e8be4981450f9efff7a25f75a80c16277df8b2f6a5a47a6028bb5901cce29e57d0940c92ca54d37a52cd73dd87bcf9ce9ca598519fd82b7d89d6d2c183fa592afba07a02735c2277223ea5475f0ab219f12cb4cc523d356868ec26b2832aa53ecb9615f2e192ed157617804d0c33c9ee556543664497e2a41bd7ecc56f28ea3c855af6115eea54e665f50a9e0f0da9d3489427ae36a79ea596a4544fbd4032802c26853599bfd3095297ba22161770d8552e0d3481267477cf392357b66b887c9473bf813161b490dcb2de9a40046f8ee9304ab4e452826c215ed95e249732175fec2fae3f09443e574168de8f2ee3f482054c3d31b776e05da9dee60ab3d96eb17861ffd5f0c9a5c55498636ff2ae46645ed0d9c5de880f88019e6f74e3606d44ff967d971f8545dd681d7fd6aa22a99727b650e1736babec29bb8b907c071f387892c3e962d07ef97297af991c88941ce582cadc1cabfa97055c4df17494fa7810b5f55124b458018d32a88dd6a2c0440de8a2dd0dd5d7a2ed99c8f3c2924eaf0e82c2cc7d1f450f8dc44c539f9206d8b167b11f96be7a6ffbfdc9afede64cf9a3964b53467047a641ba611e0eb899d19a176b631915d194410b02b2e3a0eef27af1e7e10b103d0abc329e6515369dcdb1c38ea110542ee42d7cd5013b53f6c11742285bb43edaf98dfd7fbb63e16df341e7c93d05d68156c16c490d25d57b8753c4271670391b3658b0b6dba4a7f7816995796b48e82cdacf3ae7100cb02f4a0d724ff2c22fbd95d343cb551776e6b6329e55eb8fed880498e004099df4a3ae4305d967803fe16b3ef72a8d14a0a641a8e74a708229584afc89533a636e6567cfee7e3b9ef6fd9486ff78869b6fda446eecbe800c32ac7e0bdfddad29bdbe8e0fd81098ed96c57f47fc23d744f2e892f90426ef62c15aeb1580fa72d5f95e0e172fc125bf6cab7509779b03f43ccc1cdebbf468aed6fe86932a3d4e8bc663e36d262b18666090d46b33d4301af094d5a24158e65d543fb1ddb680bdf66722fb31b81cbb7179f3569bcce53046c423ec4c20bdf826e41b0e6e96cac9b0bccb2a5a516077cbe8dd31fe87682cc9bf5ac109ef5bcaeb7a0efb9e68486a888646a616944850111af7c1781446c3f486ebd01bb963bc30b51a30cf2fa832d7eaf8c81d9bbb52a2a6a71fa52aa59fa1cb27ab14cda5e23c1c18ec08a44b51e5123be5c5d20eb73c59f2f827e940bf197792c6128062933d336bcf063c7cf96ad5fcc4e8f9b09f6260acd7af767485ef16eeae9a07a520131ba75fd613a5b4a1ed8e318aeaa10db46a2e15b68f69d93920138162f67404498a43cf046d8cfd68e313ce513862741f3d1eaa54187309164fa5d0765db5c220108dd77361e1e223ba8df6a189232007bac45c75d28da23ae7abd382dc62070c508ca248999c62070bd86114cde24c452666664c45904c2ce5005207e06a5e77e33d8abaabd35234760733343aefc5fd7adbf6c67809129bcaef4ae04ceb8d1014bbe8839cdbb0800845925adc17cb8d130f6e6b5a72fc4cc774ea11ee475159186fdbaf47b61bb4197d5d600b058019178fc397b32c1a6833f7ef15c81f381a45350441d9d9c67bd5962b625c247219731f4838092f962ff42224b100e10aa6d539cd0eb3405b953d823b277599554a416c797711ac0089390bf15c0dddb6ff24214f982ec9e88df9d9707d996464353abc67ff5bfa0a4fd539c166cf4c941c4e2eeb0f7371ba25f28cdc1001e00447e7c895651d8cc0c90d7fe0fba6f31dbbdbda36be0d657d7762287a51b70bd925f547b538ae1be245da25d74076c4bba49e5aaea10cb23670b5d1037cbcd6bf7deab5bd4ada001f1c9f71080eb7081a5d1a230ff2eb090ed6ae9781b273b8d0e4f465f07df0c8b09cb5b80881fb077d436df1213614942e16b2516a23848cea14407e5d41fafd7c5cb590d6c6609ac29fe86058fcd3cbc7a40d7383f3b116b631d6972fc7bafd6492e6a7a7e50a5564c477fba868f8297490c6c48c191503e0ba34d4780d577459878465bc70cf8636e12d318f9fabfcda0ad682b4ec7ec44f8a661602d2a90e550cbf222912afcf0f7222485413d7f9ab195ea55c7810c27c410880f79c38fbc505a8ca4f2ef4fbf2d21c7c186b9f8276fb61708a600a786f33c6f8ab36eb1d723c08f246aa9416446b30d64f1f7a4b5e6eeea68bc3dff0bbdb0567bdd20db6bc194cb751418a569f6d8ea8086895dc58b2683f3037b83ddea43eb460112c93b203ec13cdba5ff46d5e35a99fd911e8b54a42a15aae6260f3de19b54151591869054454c2822fd328eb6ddc62e49d82fb740de58788804ca6b073b530b21296c8a19c0871221d936c0c95eaad450116b4db40b10218ea8465f618e83e54ba708730a431db07de226de53369f932607488dff1ba62abd9869513f18c682b2f0390613709982d42812f9dd6affe103f8a01429ad2e933dad03d85ee537d8dc0dfb79627f70be60f8e7da4a5a6f0c462b8102bc8dcbaaff8e12cda165ba29f3352602a97a865e0304bac8a0541046337151c249f6ae50e3a1eec4850f43d7608caa41d3a226886360d6ad660b22f6e5540ab19acb3fb3e544d013243a4efa1ab892913eb43f3c1814a3524cedb913d461073aef032a748199030127cc3a705c7c9816eb6df87a60c5ad93ea6d0316c151e40b642a82814fd483e1ac51002200575c5c3c28083007c02adec50c308d0bf4eb47d3ff7a9b8699aeb92cc9ecd4cd37939223035153fb6f83ca5e385deb95f71e4a258cdfe56615875ffe3002c3b4b7c35b5f3251859267eea7ab7392152ed4536a7a8076f2f1648fb54d74b8ec580c3f5f6bae14bc18198522488f9d9529ee8633224b6e58608456b1f88f28409508bf044e21617b14776b0f5f858d8a1d38c1703bb413f1790d294a251162896adfa83b7e6799f71c2f679b290580138be9dfe7f8b33510fcc2fc97fedcc6196d8ac51dee0c7b85860dd299b1cecb79b4f2c7ff79ada3abc4563fa83274053988e5780107fc595cbd36f40df106dabc94505a0d1394ff0682a591e606929e0bab2d52edf1b68d730458b71e0f7772882843c208ce5f27c5f1a68345f5540cc3d79a65abb0d9bf3cc595d6f6f136c87a38d05095b81f9b3015eaf6974b2009eb51a793361e23a6ffcb41f311e6df36401a837c6048515cc41ad447269ad7aa55b6364602a330dd986e4673d270ba0653854290d424003f02bdf1bdc6869724557973421f9915cf3ec93053c7cb0c6f0f84e302e609c6f83028664e624ba63f70663d6062ac77d261c04062e0ef44c0d2bbdcd6d1aa9b917e77f809c1081f9e101598099d900a381bc4d8ce1da023ad65f8f954f8a9389bcb652916f156a000a70c1bbc1e3da02f435612411b0dd18084dd825392d7b6d8154a768094e271075435b93ac8e9e5ff342cdf1fb4b87497eca1fb8c1b46da2401e96d3de9bd816775f630d6123ac58601c71120fe24bcb83fb30bc29be0bc26ba2904e1a149e63a934bd69de81d20d89e49975077c819bc73b6180fb7fd00609f50c7e3ce3771fd1f4dd4840d98cc9215389dbe717b489fbd941f02897692e526060d395b52e0a7b5ce27845ff99a21065c1a2e1f38883bc0ddeb98937f4b2788862e18327178ec7525b52b05ea9e39cd8fa44467a028d3a01f70967e9984ad7d1ff92d17219bf82e0dc771b191108147c86c7c681acb8a36771b903474eda4a7bfc9b6ab33cb5b49eabca65338e6d508e77eec5ec1ec703ab24a2ec900dd9bfb4cc0abc860691fdeda2504d94290dc40383ab035f6f4e665eace094d68b066713800569aaf357fa29c5e67b05fe270b4e59cf06f6e70a593e22aa7e482372fc394c563d4b990c53afb8da0c662da82d85f976fcca7088c64558e80a63ac20bf497c7b3766062463ad52e00836eb8060aa0cf16363ba6352848d8d992fa965156febbc092843d8489fb4f9e5bcd127d92a4505355a0ed2760dacba42bba7fb7b446251a98a94d006525015a6b6273e19c636b62e015d10cc14e49d329f8c32adcefe5fab349783d69ba0ccb96d8ac33e6918c21a8e6c330c94c4eb8b249b67c46836d264f8ad0e3d0427a2427834c3fec1f0c8cdeb2cc0b59ff04a0563613e015fafb64505a632006e96aa150a56e14a2d0377c4c4db724a292e1d04c7aec289c187922eb16531686b3ed055ff0252a557438b6e0be68794d1edcbeaf5be52550c64ca4ba7786664c2fbdbf0037a0a5a885f91ace6696698abd1d4c71231c66793e1ea6fa94f6c70fabcedd5ac0c9474e63be3ae7666fb776ddc29911e4287cb84202c3d1da254ac8398efc9f5ccccb62c20829eb0bec16e816daab14f903f9a056ab31e6685db716dfeb43473186a9a3eeb068943bb1fc01d68ac6422bdb728ede9ab932fefd874bf9018c701acec29e142911667b4b22b6fe03f9fdadb59f69f2530875d2af4162f4f66184562fa641ea76a80ca4bd1a9d9fec85f5c29d209ff3af68c5fcf215b2ab2d798b589c1b8acbb1ed8fafe9c7894598aa20813bdfa497c79884f03a29e9c3f57684d41a67828c605012b39f360e4395fafa09ecc695394a0a647c4e520b6f6fda10992455a85b5b41511ac3f2c0f9adba525bdfdf7fa7afb928da4d6c3d9240a4e0e35de25aa9d87b13a70e0fb87b21d580c28c403f26329ec1ddfe239af9b2515e86613b8c8bfe930111a5b066a8584eb11a18da6b869504340a6c95b356d00697ff12d366e02bcbb6f1ddb5350c8447c018435d6289e7a9d7bcb088c1fb8cc3449aa5e0e4d4c3720374e6c1eb76d81c13f0bd514be11c07c2528aeadb5cfb3bdbdda9d1c78acc375afa0d8576372ba512bac2778f96be345dcb6657e553de651fbf68e7b5df8d6d121be4e280a1865ac20a647a73e6170970c8b69bba9d80ab9e2828057a73efa0f3349abbc6c5ac561af98542d7de42a8fdfd58125fc28ca7b730715a3322f0d1f209f34cd850ebbbf1cd0a73cd0f065af4d990bbbb5b102f0eda384ea552d2c507eeb2637dc22ac5a205fc0c8400ea5387fc9a4b32eac65ee897c7a95cdc368d87f4878368117d501f68f8a0ffe5cbce11685b711582373fcb5b542d370e68b9d7a5d756ba13fe94d2430bb671d83935ad363710a43152734cae8cb30b96aed7984506845cdfbde1d442e3b60c7dcf051ff1024bc823174ee0cf3491ac03a47f8cfdb03472bb086ae03f742836ab63150373166f09df7f28f0321802cd55c63cf9f7a43e9dce44f79cadfb57ee5a910d164a43502763e1ca635449928afdb4352952490f65867eb805ac1696387876822857e2afa3fdc142004c66480c3c7b8e28b2dd668d9c63080b568c83482a2db6c6d49495a78cc03f1701448e3863cad27a79b204a72c29aa6ef4a8c9cfe816e4a1c6e2aeccd758fd66919c516619904b3402e8553e25d169779810dda0409ac5893465d809d1a56c5e8a4595f3306001eaf001419c047819686b68daf6405ab2e8a908a5bba06182f89c3fe1b85bba954923a5f9103a5a07aa8dc68a8d505dd0ff6663911b5734de86815c43f9798e8bc43dca2cb0db35b35eb7a259d135b4d1a4e0b72ba93eade6df6c561c015dd0b8a18b4a339ea94da6b1bb345388193df012590811c9c558182ada0ba995f08ebb66c311edb020b12225c82039495a10cb916d9e7cf0206712dda3abb97f63445a7f0612ac4db82a316c37ed9f0244b89b695f23a90a02075aec45d9556445c6003d42674a641f9402b6b13990965cef6479ef9352b723005efce8113204fa6a98777421bbb11fce54cbddf3949173eba987fa8de4a57c22c3aab95b9318ab2502e076217f1e115f295e04a5b5ddc41d7ac74e1aa1a4b8e2a97590c95e6949c0a55ecadbdc850fca97967b1ffa6e61ee7d8b5376ecf1c241125c3de180497fac0361350c0dd0bd87652085b136bf552a487cb73a93f50283a1a3f514c4c503670839fc5fbbffdfef4051811f4e10d12318926d623c023c73107c651e2f79c82c3f71038c2dbc3dd1248112ef292f25040e44b1cefcd478f9d5f865c0a8c307b16fe1d4a681f199b9f21b300d058a4f7a0aa902c72cd3908f636733bec81d98d73c0d5f47fbdd84e31b3fd860e54852c2d16539fb7db7b92cc70daf25c8d8f74025496610ada1d55233d74df1826ef415916f51d48237238208eacaf9540587580db15e5e267f7a606cca89996bb9381788679687ae179c7c4c82b08b7cc269fbb231b4a612f5367fce328303d8c7d3af92949b049bfc103e347ab442e124934ac1da53f1f03194efe3575054336926b141b3b19362a462311ddf0fd14832ce09e3f3cb28409c5b30b5f8260ddf94a51348ce53df432a47e441a366c049aa5f7829de50f97d7bb3d78e54ea8a8f4a73148fa2e8c21866550efcf3cc9d066bd93076d172daa723da47b60540384980eb599480d8ba4ed3b44951a082dbaf4ba92428962e0efeb75c54cd84639bf2b49dd58738798d424c80a4a1a8052b278e0f8483559395c584989dae02f5234190f809e76918f01de1ca38c9e6789b6a0318b8c5e6586599b2a88dd622491d10a539ecd3d64a4aa5a83159970ae52842b93f7528ed76d33ce3c380422cb96530e54f9d54699805610ae5f21e74542f1d21a400ce8a1a283b603752faa10d4777808bc52f9847fc6e412258c3fcffb938e237785381c87e358079dc9edc6f7fb4df276ff72dc2222182168a69ccbdcda2f29286f8c264983c9486fa3ac19d0f4f10e2b143204682a0ce795ad4135abc5e77c2a4906785ec16431d27df5d9b5e3c1ee3e99ebe14e6114d7c92df508a7dccdc3b2466bed60e5a4fb6a71121525e27d093f5a69800a2aac42add42bd4545669ad327c380df69bc80ee4f133fd6b3457e66370628ecff23aabc85c39d510407b208dcc8a0242e3b3a383a144871fa80e56f01c687b747bdac997ce968ce73ab97e050aea8d24439c1aaac67c8963baccdcd3b5673179def6f084bfd5c4e04448ffac4246f1ef9c344a3e9a946dd4a6dbdd46c40d9b62ffd92da41edbc72242af970230bbbdf11a461b83a237abdc087664fc5950342f9add882b3e464a36889fe63182531a8fc1a6170bd7e8c26e8bec704b52aca61f3af4ced469f7410b34e0296c6b5802d2fcb4b238f7282ee9c8aa71f1fbc318befcf589c03333aecf42219e434c873ccdd0bfb39011bd37313a7fd06f7e73558aafbbdc1acabaae5d7305add71615248da94c6ff747402444ea8cc548d1f7c257a7ff1e6a2828b1284d1ad355e47df96d7d0bd3343003f61041ca04b071b42ba49d6a811ec54c37355be3cafca5246c52e4d4d2fdc92443dede4413e4201fe1270bff8649d95b7ee09d100e688697f179077458718f597625192408efb79c2e03107a017372be5c5461d38ba1f99647fecf9f9257befcf2dcffe254c33149f23275e83e338530b07a07f0e1ed28cae2704fc741d37b82fb812c47795fbcb1735f4d46e1e146bc1063446c5197666a47e707023fc70f91744129f88ca0ae741936bb0c92e9e9b69190237e9ea8b80cd0f6f91bd7395f0e85e55593eee0fcda6f44e58e5b84ced3a745875ef89a9376678cf744fe64b9f3440038360dd47fd5588f4ac5fb788f372368ad8fb00ac040a249870c0463c38c8b9ce54948092c131d7c425eed507835bb9d8ff237d96ac3dbaf83513009f00b6d9c21790c28e30abd494bef729bdb4dc8fe0728b1d714ea5361e9af98529a1a519ca11f03fcae3c3209186563e88076e93204bf74bff7bfde5181a221e61efbfa9ad842efbe215ad4359b63c2e6103157ea348d4403559b386d910eb836a2260f4ddd29fb56a8fcb39624f9be577e9ecef75992e796d8645443d7bb8fdf178255331fa8b209b44adae2b6f134c0260bfa09810fb30693d1334cc03098b2c3d0ffcc6f7a8d2fb0cb9de40a0673d9ed52b7e5a7c24e547976263f0c2b26f0e7d793692d6a75d469291c578b1f13a83fb2eb84ebaec52f5482a0200a351f385bafea96b013e80e052cb2a8bace11319ed39cd37aef6d486f0480c208ac92152f08c1804196ec73dac6d77c1e45ebd617c36aa0f6a695fe8694a9f15773624b7b61a3bd90151ab0ab24f40d78866a3a37b2c43a4042dcce899dd277c92ed9d1f136907069ec84eb58d05ad1ece272fb3b13be67c27b73ab26f1dbb19351a4ff83d807e0ab17aab2e6ed83940166e5cecbc4741bec3ab10878274aa5a8e76930bc4373594501dc489c27baeb38ffa2481206cf4a8f2c24be0a3170d94085b0e1f26218d8286cf296bef27b39bceaa575d81b32ca8542b4d57f311bfe7d99cff5aafe5704610ba58630b923d475b3036a5095a8a527edacf9c13786d5ccb007619d052606b73d2d8eefd71e6484727826ef894fee14701b3939fa8e22297554e1f5a30fbf2e148282b6f43845225b4fd7d9bff12fd10723611ae5df18dd229840a1a204ae56d0474e55a29b2c831f4a4c3d8d1762e3d0630746fcdea3eb0d826e5edf3b44713ed4ed571494ca4cb8133350a5f12b8e0a24def2eea92271f169b387ea3d8d3f5971676b8900de493db60d57772a131cb52c35c078379d9cf4f6b8c065a1de8b37a5f1446c0b7d691c57ffbb2fa980b37e55a2be7c18957cf7f35d0f0ac3885c40266d252a017e07108628ea3da9c30e91270f5b5022d83c626ae4a803b5df60094c5d3e0022018d062bd17322a2f57b93fdb618f8aed5b3dd5ce5b4ba4aa8a0ed88ee00b1f96dfb93d4b0172023815b72885bd0e685cc07d7b4d89b59af83c4b87385f683b46e896932b0e5397ece78ecfe6c4bae708c6731897f9f6bb7c29615220cd0b34d512cd831bc38bd978c377b9caf0a4aad682360b3c1256ef3099399fe2d9c9f0ae0db64af060d9991188787aee8e55a8d6cdd285545ffd06047c1e03274ed23e1ab3b215cbec8074ae5141a6aa00cea240c6a9d99f3f3b5c6bea82201603cd852d1340b1ba88c1535b07acc67774d097eec89accf3439ea81fe62aa80aae8c25ad6fe7f91e75ee33f2e34dc8827134eebe17f1c14a1a3dd1f13e5b22fdce2d78c84b5e20efd013dc62820a11ec85454bd4ee21d0048de08b2d22525e6a8109ce3990cc39ba026b0cb852c15088a8c043a2dc0d3dcf3bf8146d075b5eeae405c40e9c30019265915e78a95e205761b3f43e802f45e8e593cb2030fc06c21649b8185ea3f44379dcbc7c685ee82d9068a7f36a743f39901a6fa6c0baa4179a78018addf15b2f27595fd290032f5633b2beece63c8757cadf34080575bf2010ca055215d3f8765c04955b9a718bedffb03fb17d28454d484b5e14ac0f6b7c82d5ba12f81eb1c97f10c9190cabc4107cf995ad8aa559885df172ab953d1a865f1f4f58cc4bb1cdeb8d6e04e5594515b7e86a8da6d42c94429e18e37dc28d7966f41fc87adf00988361aabc5714361b7036c3d23adcb23fd84c5ebedb159214aed2c5b65744ff04169ed8232ce5aeb763f8722b691f0b02ea81024ac3d52d180a29ae4866fab11ea091e2a1916ed895ff446ee9f9304c45d5148a265c8523889442d14cbbeff1eb4e94bd2513d6048ed4688f185e30d02d204861e40cd56b7ee2aa3528b1725bbc176f4e92f9b602f58d09d90cae6e21cbb3b07f598d14ff62e5f9e9c15e436df95991e4c55148209d44f9c83aca34fecc096ac184b7ee0c7a10834c2ae318d068b0bb5d2008472652f2863970dbbf7d303734bf158501ae07c7e37b65f1657300920a7888142531f0cb2e07b22732cde5786fb857fd38cbf44ee4886077fe1616e258a50905f986e6939d45a07fdf388de8b621891c052a1b7c39394310cd498146064f9ddfa44c88337ab77b7cd1fb2e653d150a94aa5271d069a5530cc99ef3b7fc08f8d008f722d79e9b5d58d6d7979b0b344e514845b2528c55a7481cd83b6aa664b2e6df82447ae2c94c4ca11e3f17c48a269f0c35c3dc277f6f43054766dc388fcc0fc5af202dc84f5ed5f7227c5836e903db8443cb4d2890db99a0ae2e588a124a225b37f5219cb935c61b0ec6bf137164a2660a270aa406f5b362447393e7f474f3186b2dfee6a343b0b29aeced09203c001cbce31e16718ddb55be14f05e4ee2873bd0dfc9894883364c401ff5ec6e9ee41197ac6e861709f14f4428f660cba52f3033f3b4914b898778a260cafc69240e72facfb6da2eab0cc8fe42a8300d0631fb73f6f510d4845ab9034bce1d3603f329eecedcde097424f996e86ad6fddd16862566150345090b18fa199e0d99175cf2d36b5b339921461be99296d1b111c2570cf5ab8f365c156d424a42738c8c603c2ce5c1e09497ff2c1e5ee7af9a43a3786376a767acd1a82af878118d510ea84950633b63fd3187108f845baa9b570d824d84030085c049265e6c1a0320c0ed3b2b1949a582d25b6780a0aaaa1a5c56b320ff3522c2a52c56f935b51ad46fc4cb82c04d4736a28065949e29af50f3a33baa0e0821343aba07836bdae1209437a0d07ae8be02bb18e12c9b79c3bc0e89fd6857a92fd0907680b45b93660467e100e85384388f51ce28838effa6762f4bc228c66ac1a4fe7ee43df9413c1c0162785ef8ea8e5da2a1882bdb7c95aedee4c6caed945aee0444ef839f5a146a6a43ffcff8ae66612938c71b8aa39e3580352c256c063bd969cdd1251d9dbe03d1473edbf401142b81c2d8bc3ab71b522931bd7d62b4de5dc92e8196109fb940f10b71dfe9789152514fe89d87e4eb6980ff384c386278310f03b75da7b8456e55c111888d7424b2304a5d0de8ca5f8bd9f576044ac96387e6a2cf4cbda61d717c20a47264761048f0d071298bc0500f750466e76a01d1d8a31916cde0223b9ebc036e068c54e72528d82039f7f3a9cb733118b873df8fb4a86647fff7400d644041cb11ae352d97d0525ee19f372184f9dc8525d4f09008ffc0a6e00b69be56e0add5d9e2f003c3b909320556352c0432fd1be36fbde0070e45c8371c3c9a6d50306287c7c06c0326e39432f06653767804df3bc0943de457a69af0ea0638c3d972cd3a773d7497ab2eb1a119cbac8e3962afddacfb0904a11e6793ad1234e234d0c9fbf1098708a55ca851aa9853f3394b148162c57878704949231bd90ee11ec43dd877b7ce1623b77de38a55a9262560d3a4428c7e618eee0825dc2812e5458d035ce0c1f9e590d205f21dbb0ac0531ffc0f723ca9d6fabe755630c8ad98466979879724b96039949f3fc8a0c7d87a3148d8c31e480caf2b335112510386d31ae379c95440a5e99030fac85739425465293faf4f87b3595cc07849c7b699bb5b029ffe366cfe7f68f0d59d4c03564d394ad2554005adf9d08d7747d1544ab819b55ecdc0786aaa15a347c4bff84af4bc34ad41f5799d7a0c798eaf831ebf924b288382c75de2a61ee312a50e4dce3e1a470ceaf3308d610ac752f5770ca1190a77941233fb6595f8024566dfd6259073e4652b7ee86988a6d85f958afab12c79aa182a4abb66d4fbaa56b642084f697d0ab1c768636a9f83e92e52c62f1cb27497bf79aff1400b43544baf588038a3f48c7d867cf34f8620f2b2be44f4c7a12293fb605d866fedb84d1c8ce5486286c7b96838a2b64c04918a37585adf8c358177856be13a4588cc198c2f2255e64d12a98536e973f18534b2c0b4de7606b9ff8e4a1f111e3993f77b260b1b38908d9ffff2ea48d8f86d6a3a54fd2e45324802318250d46eaff519388f7ec046876e8c8ad056d86160dcc4ea85b7fe580b993a548ebbef730293a8ce0dc73d152539b69ba15c92928412e61a1ae36c03a264d26c03a32230e8c071954d45ae32267e076514f9e086755ef50dc6f497c59991f038e80bcedddc372b8963c82c2caebc2132e6971f103850d0e0afda86c18121d04ecf2c7b0c3aef07b4eb64c5b16c0064f789564d65ec80a918655717a9bfc403b178c0040d2951d1463927c17b9cdc115d78b19d5681f88020a5438f055ddc8accca41624e9e47ba6e5ebf493e0d24189f65c0562bf0bdf3b681d1a911ded75394c60cbc9b6d1510c5bcdacd07da56d389d9623dd2ec92c3b010a422a7972b7b0c22518bb48fb4492d9b2c4ffd3b0e81fa451bac7923d17f41b7b1a7b027cff10d9d78114509c4a2670c80bf57f49da6f8bc2d0e70f3fc2a37e1348a17ed8bf05049eb20db2e6e2443e09d7a256e70b592776063acd79ccfaa8ed93d51360b5397acbcaef3e151cd95af701fe3f23b9ba1783271bd625946db063f500df76f83d0c09fea6fcf53e002b68c7a29463e86dc98cbe4de99ccb7cdcf3361a189f1fe53c2682d4e9cf346627c440cceb3464c558ad7c4e8ccf1403cf2f5cca31b32b67101a2180bf9a861426da99edd05029c992e3de84558bdac6128cff7b3dbea951e0d83ad336f584208e28bcc8cfc8bd3bd9b142cc33d8d8d8138eba454eed1108ba8eb66ee5a8e755b2f94235c5c463852e039a4a8a45c5dc1b0d571dd289e7f0f6a27dd88a61ffb50d216bc20efbebcb569762bc9e582423e2a3b039935d8599268c83a7ef948c2d44e8c47fa6d7da96ea9e145fdfd5e8072adcf45300c812a8514bf69c2d8598fa1ea189b44cfa0418200c90e7ae8d019240c058671d9e47378a87048b290f12cd612c2050b5a11f15f2ac2f1ddf2da1a65a57675518f133f46b4c8edccd27c5a073191f71aa433bc57dabf69d0685d7ca62a93cdf9ad3755bdb0f3a211142b124a54d4420c22ed1cd412b6dd2b60c7736150442f0a1ef8f7c257c80c2ebc5df0ac9789936dac4a6eeabeb1cecc121514650acb82e6e14abd167f08e15906b6285a3fa12b0483046373d5d306cf3eff924cddac20226ba6cbbd2295a69b60c1c1e7bea26e5a70b10ab37b16bdb033398aa43b730ee1d4d065b9a73843a57334b954c68d884c803556085f78c83e44ed52006b19351413f26cd963df255f4ef41ef3fd3a4777fde0befc64e9c0399b23b9d047c0a3760dee376fb43cf6492018fe3429502384a2f9818e895080665e8c7f8a2a2e57be52462284438ea684ca57a26440c3c83139a8e30ebbf572ceb4813193ed2558885b05dfdd2e501412b295717a1f786abab79fcc482cc9742bb63806957fd8b047fdd2db583d30830e98e9e489f44cf2d980633c7ad1511e29d1ee67812ebb9daa4ed27ac94edc435a452a49274b1874fe70953416b4f4aa3c47bb538a63a9562b36cdb5fea899129653702560fd5f467c4c5cc3fe713ea77b8081bc42515b683257608d774849450ae3baca742167773f9f3b44c62f85250b2d5abd6a46ce7eb5b0e5e330c49033a001894fa26a5c1b370eb2faabc283bd92f53b3469058e8fa4af3c694a665a4df1b59482c88602f5bdd98710e3b288928d1fab089be58586dd4ec192b786a133b8ec6348a2207f77292663886094134f5d518db2b9a7831689bf4d3660c0de3b20635b1c6014043cc5965e093bf5ec941adcb2ef69536fa9db6ea8d88d9574abcdbb6612d45d9539d88a9a183dab812407a638d78580a0b36a809dc584a111b3e09162accdee9915f865c61741059546f8b0d877a1e73f58f0ba36dfa307c102fc330b2d092087a6e8270c45e77ab16958934783ed100e8294a58cdfe6d8b12856c228175f1bc2cccc27225953654bcf2964559d44e6d147f6e481bea5e01f03e0013a38dc1eddef91c79821977661a20d8aa893112ead6a72a4724bbd93d2961af9ac19eaec341d879f2a70e9c0a237f838c93126e655c36304ea7439a5b213fdcd959d70521cc67ce6518fe2ab8e3e505abbd74c2ae2767bbf0a4348d240a4749456a5825ca5b8dc24df8ac675be6f098734aded5e208fd64e3b601abd50009508f046c046520edef41b6147366e46c673be5cf1e88a2b8ec82f28578eb83ca0ecd068331871c371e302b47a9445cc20dba32909977276d9ee4d5deec90b9fcbb6b15b5e31d2a0c44e70e72f76cd8a930681478417f42871e8ae06ea3dc9b46d79c1cd874ef3c0972186aef1cb733a723c969bd77f86431c019f1f2a4e9de55b5630066067508d67b09bae0b03f10a73e2bd53926058e55124bebfe278a556929ade2e394737ed9ef275798b8bbe58dba1b3b9d36b7bbae4b12adeb820b78fe7e0bac2bd1a042a8e908ea5c52b33410aba0a254d14ce8b5f811fa30a9e319f49298b552bcf11fb3db3704c6f81e6e0bafdcf8cef2a070819e96d74b3fc08e10f671655473f0d3c722d62d7f9a72042be570cb03c49b1b9004e13687955cd26e3de0ec89fa3bddcf91c7d40c5f5b9cd82b464a31f7bd06885d7d6b9d173059627c98ee136768680112b5f509c93173ba0975e695bf42ba3839a0f10872576b3c5a907f8b2970243443d8b42a07a0d264c0a0c9f88c6cbdc52c200005f557cdac3a51829efd5fee670238378b989b5a78baf60376a486d915a727f50f62a0724d29191ed1ff5324333c2721fa71e58bc2a99948b119d8b2f84c970a0bcb8de4074fc7b50fc8a2681938c681653373b24d3aeb52e3ae290202f46b30f95e4a332b4f4ba8c4ebde405aac82bfdae328ddd8c7534d1866e8292e44c7a43f70ae7fcd9282174a1f7f74eaa7449ff64ab34057ebb2793b6a82c8e1c6c68750ed654224ad6c04ebc55e4383abb2d060f1ec5f1c8918c9c5af3cdaca2877391374d7b8a4ceff0109a9eaafe1acb5515fe68e67c9237c29f0933f276778472268639b0d57cf265ec6790d56f5e466de5958f544f8f6c6328292b784d6a88385a273303a690f094af7ab2e7950de16da12134d3f812dd26611843d5ab5da405984caf480e88106c240b5a7d505e1077c0886765d57fb6bc3fc94abffdf8a04058dda83fb3525a66adde05bd1c6baef4e48d192623174b000d7e4404665e242d4cf98ca9ccbe896c3a19d0b41394d80288957fd09a53fcd7369822e65ca20dfc52a1fc33e86a1ce0a222f9e09bf05c2b3792d4cd7d6ef1dcdca60085895c406aeee57d6f386b0cf58c133633cf9c5e94349619fd035ea4cc35eaf1b37562f169c3313ead7489ef7e22a93d3eecd04e24edf0c59314dc52dc9070896923e0ae663cc0da33c0df682954051c6950128a822c41a124e32eaf1701c19d34cfab0dcafd06c57d17b98615c138fdf44c7c238b00e14827f89452f247811fde440aa9d8995f6b7577e611ee3f111d92d83336df21adc3a578f2d02a163f18c8e412b1b90f222f05c040d1aaa8c16b8ba4a8794eb9ffe48a34bb2050f1a4c05206d039b54856688d506be78eb7f26147bf3309c8efd69ec5f5978e1951677410c4e2c6f837f3767a1165e2a145daecc19ee4b1991a1e576ca970684f6cbc192457229d49e6c63a39dd2d013a92fd5bde09e51bd51fc03607f1cb4cc326d79beaecb2080ff0e28ce12b08e014778a50f53b2a539a18c98846bce48a134172e3e2b11a109dcee0f1287aaf8082a131a9b3edb070731052e34bb6cb256bf587b6429c847d9564af95e93c63f5d6e46f9bf3c479bba53c5bb6bdf3bf24b2bd993be2bbdb488ec466e85d10bb43886f7395d6de0f795d926f4c48e9d4064a23df8a6a10670f9af607a43ef42e11c017eb2a6400a2d0d2418087201f0692a2b50070ca495d4a4be4e929ee4e8f20f5df20ab5b94d1118b1a1f04bc510159374038389196f2ac7fa4da05288f2a2d0cde5009fa62eb7057832bb1d7770dd913ce7744bd134f2c7355e51e0f5064a5a5565645862fb1c6559f083640c4f6d31aba0360d3ffd03e63d01d4de347853d65104d0c00d5ee3cf776e8a7004465ee62943354cc4c320653ff618a6c6a40fc496f2f7326c874602728bb88566b0397109882b0b57def8727225f9e6b8118c89564e244d2af562e88fc3b3437b16d2bedb184f378e9256876c394e07122dc7df26bfad3679d04be1093cb6c4e4f3d9f05562e2f4f2487de9b3fd7de0c0f2b83de9a2642e53e08286e0d0716f6e19c567cb84f8044a7d74d391c1cd7574ce347288d465bb3de3f3da669a2e30d71a80caed9b4d7e17cd3b5f432e75d62a0ca41fc7178299be225b957a4d54630e67646fd88842c7a8515a5ba74b103f62d3744a9d30b6651a69ebe66a5fa30dbbe510d625e418346d75e7f9c4d872367282d52de473ab40db151055c3a671bb0dbd469db2175223b840f978914864bc1c0bae4bc5b18602ea8332c8f98e349b0a2e6362233a4dda82c5d763efb9a1c71ed1b14d65ad35f7de058a118b6361a02137d8e82407fe176734c1c171e5eb3d8bff12b94d3fdf6727b63a88210608e475507fd8618aa32e3a7e3262616e2cb68c5d98512c3b7f7688b3a8957eec37b397fa9f524a1e2909e4c7f4c1db86cf47de578fe9d5798c3e1ee33b710c8705ee763796cc00fb47552c76e0e4e653e119b1413f9c263cc42d4ed353c2369843e05f87ee1847b83fb45be0f3501b131f0fcf61db6198efcefdd49829ea3e2417f068ee967ad7e169a48b3129b8b0a0c258513f1498a053b0f5aa5120e7b5693f051f61cbf2f09e68540662afd2c38aa61efa6b261de7d802c783a3ffcfa61eb1641b4ec580127dc3bcb1dcc043ac89523165abca582c10cc8b723a749db90fdebeadfc179f674864adc9e3f5e4790291dc784e46f0b70e9987012dd214aef1b33204b4bcc0195a96b420784e88d2c62e8780c28ce4744bf2dcfb33d68b7ff22f6cbe2a7188a10cae0af69e9af6329c3d448414ddbc9fca55a496f780b22b4380101a80ab5ea987d9e75f974341cbcfbe31a6024c684af46d6e0b626e64b5d0c69ec4aba364e39c2200d222011f5edefa74f10cebd38f4c01436e5653dec01985c10394fc3962075655f376c9ac6a54e3aa7ed531025513dbed6e600e1b4106a77a82465318517a768cbbf405e25fb6931cc98fd06035bf798508ba9bf5299fe18ce51615ba1b1e70220f677de5e1ade6c0314938999d3b3cd07e887536a71e5daaaca8dfc380c8ac5d8e14ee134ac8db92ad7f12f98ed50e4feefad7f331a216047fb67b60ac35b5ca96c80dca80025b493360f388b297cc3b26631c1b96960f354b3a248df8b5648f60fdc577755bd66d5164e81fc45c5c4d73db426c74046965ade636b55e8bd54f485990874875dde408228ecc17db935eaf8fd39c9600cafcda3560fa69b9dab780a30dce070369090d99243446302e17853af940ed4ac650b34dc293c4465a419a3605ecd3485968d059ab8f5d9e38f3556eef3d9bf16370ad28344911e01999e54c8268adb4f208e7a6bfe86932abef9a4137520690d831fd2cf093115c056b0efcc6c956dec1cb57b49d3b47cd1cd7dd02ab0a0f9a53df61fb0cbf05c2420baeea7f8b4d1296c64efe6905c3c2ad5dec089eb801c87a66a5957ad2e5921854d29551ab7af00a2ad94fb443eb3ea309fa350a75a8700c8b5c8c9ea17a00cdd24646efc07fbfce3129b1164af75730ef4d254723600eaa0592eb1609572d76c572d11b989dc1f517ceeabb0fd8167b7315d3d9a596e51d458fda6c9e59b1c18531de420f616ec296e55b89a3acfe86fe49d145ea75eefb80ff0a8b067a7efd57053018da74dda40073436cdbf20952ae0ebd307fd49f4b45774594214a0d0996cf80329a6a990384ce5274bca846859a34de2e8705dfb0a76f24828f8f8a1f4c7b455e4d545d9dc1141ab469863cc959df3f63c21c3cd540fa3caba1f7de785c4a773f9c4cb71bb310afab4217355704ad481022671b35e884a8dffae6ada5968d123623df7c6e28142ae1151c0e1707522aab18211e6adb0a25658ec4cea7d5be043c7bb982a9f74d65db1c3fd05ee31faeb1af5597c6951f6aa9b2f1031c374f652182e66a74925c88a79ecdf49f940bf119ab886e210a22987d79d9c75375bf96b39263b39762318b23144793961db03f43c6a3152209bf205b4de389faaa36bc993bcbc8439ce3b60c7c2e0a6b3ada15981f39df9d735a7bbf70292cf022f0197f8be2d99851718da0c90c1981590914428655c10ac8aeb044dcc50f9b2b5160d45781123f5a1e62107537aa023667d5670022f0cff72a44dfe59c6375dcbafd21d88c79c6b94fdce94d496ba0f401755993f1bdd909d9f9c16db0371248d5e6a82ebc8c511a98a04d20153e91af26c0ca41aa84d7fccbcb5618dc6ce54ef3d2592b8333e7bc2cfd32fb86f4566a01f413c5defe56b89e51050da9ef6ca3a9ee75b3e1460bf5d8215c79279dc75ff43dde8bc2bc3082092a4e271368e72fc70decb2a12668d2ffe3c6baef50cf7c8103194f6cf3b1b0bb3c11d99dc3c1fe4a4104dbda5b09c1e315ec9553ee4adfa70a3b55c9113804094d43f6640c01eed5a6222ac6d844842133421336051ce059225d96c94d50660aeba8e3a2299d21169011c5488dcf00b20524471044b2ade59ceec0c496bc19ec4a31624fd1fb208b2b5eafd2806a40837b0451ca359cc5d916c4727b118746a3c1e16bbd05e33debb99d2efd74d1769ea9df006e46cc81994cdf46bfa7aee54e803f08b1efb0607c5179329709409e0917576906bdfcff1898ac86ad43e5c62d9acba8e1a5985b70d181964249ab1683927552752586866c498e3f0e2e858a97a038f53a7d35b3827d1bdd8f235433d0dfdf596ce7fb7305636d1e600ca85c2efca02d1694323bfb2f2404c2fcdab205c1871433c6d4cd19fd8b7ad1ce2d8aab0411472e0ac8eae8b830729ae5d648beaab616fee909d005b1041a52cd894a1b0ae635f31c3a496ac7464af859ccf83314b877c687da4f89ae87e715cf5ca6362838ad97db03538b3e10d6473dd4fe798b26c98201887662a2a3c4f0c068a13948b7285710344f923c09bad0a30cfafaef7c7939e52c49af4ec491ddfa30a41d525c03e4eaba07c1ddf56fe4fe20f7a83523276f0c7de70928c68818fa836118e64000094876da39e58bd3567684e230172a9891f0ef672de2de46d8c5086d26733e4915fe25cc029a3ca743f6a65388ec1514a6ee8b6852889d6bb479dcb25fc85e911383a0d09cfb4c2807b991843e20aefca38b35675490284ff76f2407324b0df9de47109284339426070108177c78311dce41d47f834dbd2704e89807c0dc8ce72f288fe123772682441aeed5f5ac8a0eeba93dc6c91d4a6715c9f2e166b7df163104e976ebb6c2ce3c11d878f4b041969f15c65d5042be2f471b50f6f14272ce382aa0c7535484583edb94e56e64aa98432b2b5a3a246b5d5d29f0c4203690a00ec0b1d88a55d4bf57358e4ae950746f1c0ce1e7e0f1c847551e4e0bb0dbb29ca06edbd324ecf4c904384783d0383edc6276490d614097d243f9a65c5dd169dffe48866d007964405e0a48fa081eeb366410fa8965899513e6794e0ad5bc0b53632d954db32b6ca531f6b0bccf25cba9cd3350d009e4f70169738e966609ef1de9e1746e340ed1770cb3ff5b445e6c4d95b7e5e5965f74bffa040fc20572cbd26011448db92828e4d5cb1f52f61361eb9d05ad26a820dd602ef7a61a4be64d61b404e50998d386b6b85b929aceb282ab8579a18f91361e58e5b646a4ea3127c41e4944e019d34eb6f2d2ed254cfd55b2dfe343a8ab6081aa940e5992dc9ac1f60c69b9168063e93ee575801bcb8866843092ed977d201c62748b7a588f77150bd9c9bec191fe3a0a0cb0a430e6c8d49700b70aa68a6890764e3d345d2f3055002929f241594ddcd64e2ef1e963bbc4d4ea9e1810dd79f46f497d2e5945a76e32a22769221fb6a81d1bafcef1f4ce290e85ef744e29b30f9905613a16a4159a7625f68b03c656d6fe3656d50c30dcaccb3294ffa37337ea84137b0679fa2c38bac7bffc4c56e8539dc6757814d848e248b004ebd4784f53a7c2bcad5b7e0c123a41ad6329df4ed1e673eed785d633c7677e4a0083332ee77c81fe05b711b0dd5ef2aa4839c6f6232d0ec96d922f001169c94595dca2202d223429a485e4094ca31649c6be425a030f97149303e1b24b9e280e48f47d40cbcdce15e6f003211cab55e71282adaf8b0f898945f55cbe3c6db93010fa86d0ac6a04f3e30d24a824823f603ea3a547e519c23cb88250f1d601bb3899a56ea0eb4818426cd18761f77edcd5b8cece809e6e3fed525678decde502a6868552697caf58d1f4dee6dce77ef84c0699e40e44b4e75edd60da9212e2afd2d3517c8f60407f6ddf19ac197bbca3145ed88d62cb373b92d572ca94b74da53407d67fb3ce27e7394b8aed9f4b934a50c2235dc6c2f4ea3ca95b0b75d314e5625beac4ecce966d35667ba357753cfdf6b61469bad069843569b4ec7cfb307b473d28554ceca3e1b9d350c9149fcb35eb3fe1aa74c541805bc77c2dc5a1e9f8ee41684d1746de3a53e2704e12f9c5a332261b30a53aab3e52801324157dbfea7b782ab4ed7d3f3181b35e012ea7bae0ab0bcdc748e4e3c3b41ad5d7ff83a1abe48a74a39bf207c0d785ad98414bd8bffbba8877643ceda5545031e37403d5da9bfd6ca99884e63d18b1fa4d5d61abdf379628218f1ebb273fca6caea3719e8e1d63521466c2e21a8dbe3262e7b3d96f61c544dc42d0fd1b887c05b43a63fb142b77641e87c15da55985a4e5686c7f63fe18ee0cc3068c3d7112d19e0f75f065af382ba2620116662ac8647ff32490e4445419f74196716c08a511c3affc323787d8a33049670a894211ca1fa1633c2612b301c3c332e19cb9509c22dfb84cb41174ec2ff01a432c0bb6dd97f03ce1b697c7e244ff078a1ccf96cbae19aaacc6667370a52d9748df88262b5310ec0a5d88616515b92b6875a9a9884b4145fdfd0be2a5d5e4e830a8273cc4ae5282b8976189a94a48f67af7dde2c7a6d488fbfdc063956fe6129b1c9b8fb6adace8994d3f2901c64bdacc2e170c93a28d79c88b3b590ae6da97ffc6e10ab929c1b732f43f1758bc9a61be968cec89d9cabf10f33766c851d27eee2ed7c18bd8ad5e39146d035dc0f8c0b3c2951f7744dc678d81feeca46dd6973c02f994d2a12f353b1a30d8b4dfd87b5da02f3f08a413b3561fc7ad4d0cde0723aff83aa158c02c503764f9c2e32618252b060bf5d422c9699d6f016178e36eb56a3b5e06539c520339ba156f8042ebc7b5436d8a2ec91427182e1744df65cdd0e50194fa2ca071208b205d5584f19a429025ba133ec52f88af37d5ca80ef94b6234c6c23c1b39348ee685bd5b5e755b0561f423cc1e333d7fd4e19be89a70d238bf0c39320a2e0b21ef56202dcd44aac5a0dd7bcaa90908aae3a4f264f1c42e428a52a3ec556b6661fd7a563671de4a290298ad3a601e05aea72ec5f314535f559ffd5b342c44f83dab1e20bf8415a55353fc7be52b0a2b31943819930407b2282e8bf28144a647e9d7c57f94d28e6aec7c5969533db2767fca072e5301c8b6461faf8c05011792ad4a9216017b2a0944c483a0f966f746c8576a17d606acb61a8f00d0303ee7224a1dda1685f71f5faf9222b95079585be7c59c93f53c5724a56e37bd14db73c1104b4fd34a9d82ac43782a19edaa54c5295e1c6464372c8f25c9fefeadb998486a11e2ec30081fd7843aed1e3b7cb2332b853d9245c6c4c5621033f94c0d5261b89376c61bf5468a8755b66c7e213b6d80781cfc1da1f0967c8349a2001aa6b9e2466f40d258ca4af89a5ec153a0da07a22204aecb8c70de3713495fb1f5a8dfcccaf6cb2b488ba8c9dc42c79458b4d37779360cc7aae64cec049ecf9539f4b6e38e4e77998d897fc4085bce170b0de3436b7a10010f169383112fd72f97256cb7b3f87400c4e2e59afd02d942d669602e1c5860fbe65b405c8643ec1df27cf2f2c8ea9233eff2e737f45b4b647427cc5e95cdbc5a867b00aca446cda29e46117b8cee87b58d2d26baf90d62a5eed190deb68ca837c0877f27d6516f92e8ce7f1718ebd642c060b7d3ece04cf9a2ac6f72ed8227b89e8844e3fac27618e3dd02b03c7942b96d07c4376175a559ccb23ce7c11192c90e8fc465c4c66d4558232c2b6328121e063c1065718afed382de57eb41c3fb0088451ca2610a0a93d725641693a968c84a58500a9024898bda1d7caee2ea048a47af5f588b48a9771c4fbaaa716dfe2a0fa543130670bfac4cea66afa95599ed9ec65720e40a5ce7d22294afa8f8a01d23c8788db1b0fce7e53259838e0184c1f813f5bd30784bc9e20e6ab80e2114a5ad0c85a49a68727f12900f3df7701c2d3425bc150c419ad78f41f4bba614808969c269c36d4a8b75ec1d38247692d9cb434d016186a4a2bfab4e70f8a18e0f8a4cf369942688247428e5c0b5e66b9977fc9296dea33402b29b2d3d1cef505761021e9a301b0c3f3ed5cf4c4188146f0629b20e5a841c0a0f5f668a91b6846e0ca61a339734cdc29cfda4a1fc0c17031888754e10f2d4831157df9c38699510315adce6530c5cc692a44c55f87843bb63a98582b38f072406e3bee3b026de9a88a00768dc0c49b89d44a86cd386dddab212460208dc205069cbd60852106015983b61e4b1190d38ca16be58037c61b24960f469ae531bf834100696ff700a39996ec48db2664fa0c324f1652f2601cb24d09675e7895be145c784c66d694879073b23e481fa0df2265ebfa8db47fdb0132a64999cfee1238c342b37d6752d1998e4609eaf0da52d10a096cf34632925a3631d204546ee92233414f9389042e291974e4e48725c0d994f050a01b635baeadb297fa3601a1194932e379ec15bd6dad1296aad4dc89082bc0629a6a2a96fecae6e31ca4e630338aa227cba6cfa0950ea138507ee17c58b905c907525ec1b803042e432abccb1afee47627b743a5092c4147fb626851b66c2f47bfe50705497c69126ed17c59d2a9ad0c681624adea03d3c1cdb8d131655bd16d6e9696ca46cdc4badc4359052d6351d25471b28b7ab7b91b0c49aac3030e09fdab5d9a58687495cce19afde9612415fa09d865735cd395d80381c92ac9352a850cb841f451996b1d96fb90f06c774ac22c1fb7804a69249f8b901dcc51dcb323d40fe9e3f1ee4f176125da685c6222eb514730281e75a20609353d4365495f2a56b0a0585466b7f4230261dcf243cde303ce366b8086a43705b787c69503e1199f34d1b100f908858a0955490ac06bb336281df6025a26704f89b2b0f9859d1cc79f80b3d4033e4ec2294cd8f5181ee6beaa651abdb0a5331764749641f918e480c05239e885992f53d43c55097fa985a851cd01cd61865556ab701fac3736458383752c4d115ceb684595e586f0c5a0724529e7e7cf0a626e3e070ba211a4d5ce6f52fc9bb47ec4211e871fe8fcb255d8d405051cfa6d484081e2558e3828072620808cab2758a4252b83b9dbbf1cd911f84abfe2a9a0af4054672960f2c3a6b0e20d6c8d6aa93b427c9080575abf5758b9de9e63e4e27b6d0ef3835719e2bcb131efdf06a7ad5448477381ccc8360fcf206a19df0365e87834c272ebdf8695b7574203b2701193e9dc1dd80ff920e5b0c51e060e46f2d7799bb50ebc8eb9b078b0c902c0419ffbec875c0875cd4baed9c503a690d314d237a5eacf5d00cb5231e0c7cfecba9e72a25ec4b7aa9748c65901ad55187d0899efd86d00113fc9b7cf0d48d52d1a6dc8470a279f50f4b130ef9365f4aa5349d6541644d613546ab045034a43f4e91f9e4d7cc528d50145ed516b75ae1d2d76bcb0e853671d0197bfa4c24c247f986328decdac261652a1025d70c692429164c942cffea01618358669d6bb82e3f6f37117c1625607055dc2ac0312a6aa394126c9278b29e7c251ee4bb81af11d5ce5f6c95c0faab7365e54e8bb1dc682e81fc28ca77701e78959c346ef5acfad3ee9c71720a3a2c836fdd74801be1e6746e3a9ecec6512ef4697de728b10350466cb31e7f16a1cfbbee39487930528a63cd9e27ad1431128607a6513a711cb16a21399b17b2a3991e0b8d6e20593a3cb2ff6d352398db497c38705eece44483a04442e13b091e21fa73961da2ffd53c9184c17ec279d6ae2a2803e8ffee038bb40e9346ef6ee367da391f971abdb3be699f57d80bac15620883a32f5190bcea0988d0a072cc4eae65a9aefde7f25b3917fa9f41681af8350100e110cdae6002d06847ef7f0961c7008ecbbb79d6d05211cea2aa000f0c36ea971b0a5660730f8dca4677cfa01489267c71c3e8816d0c53c621cb611aa56b676b9679e3e253b970166cb2ea9d4752480632651d40b41db10a0681512d85b072e499607643282170a6ff2e7ab9adfef8b258343f8b25122c8c51907d1fecb9e5255203e0a4fc0a77de559ae7321af4e415c071a9546086a78769990ae2e858e80460e3b1cba0edaae4e5862ac93cfcfda431c971d103a22ef6e60f6660c1aafb8ad1cd52d8d8049d83866050609cf36699fbbdeb424ae5d627f914e74f80980487813d37f7eb17d21bcf47ca019a16328a92e0e871da69b4dffa7247cd308149ae5017b08da7c5bdf0ce0d308daa296eb482019718db89627e4c9233d4707eaca2074be4c07166e7346839579c7de1dedc5c76642077e31631f9f2d07ef6da068fe3718c33fad5428b8ccaf4be84db2203dd9f2545de6abc84b9275a6be575ba26e305330ca821a2d87b2ea5be933898c3ed138014b73128cc2a7fbd030302ce0a63e94ac90d102aa6cf8705f0cde1fcae7c62aa3aafee5789a58a7a136945044d58108ff20a8d2a90c0a0fba6e358a94a618d7f28689717a959cc3f250f0ffad658939587bf8d0226cc2ea470f6c0d25045166597b9129e563d9bcecd138f6f3fd158f7c9b07a87dc33058b2b4f029431c97fe0d2d97922fb9bf57a66f3e5e4002e99744bef4201b9a5c2699b86add4419dec32713d65adc65cbd17b30c903146c075cf44cc1e7486e7d66519c186103606289afcbcbc67c0c6be0b0654a53ee54b339bb8a87c103a96ab147a886d4969e06c3a73a1d9f8a7098d319a0c9d8d17edcfebc8ddde53322594d709886f6ee276832eef5243dc790667239b2727d9677f9639783219be8d4d99c4d32953c65b08bfe81328ac3a7208f696a8b0325b7abdcdf6525ec5262b7203402748bc1b8b7ea16a84039a477584c51df900d68112dad1750bdb2b493e2122bcf9b5029875349c86d972db20f7d7e26fd721a6c56027a3b26611f01b16991d5220a13198ddefcbfae0cc70afd02c7147864a13e14e9c745db473694f68e3b41c000e3392b333b74b0ff27116a89bf62b06ca21e6dc1075ea35a7d125b193008b6d3d3c0be363e204ec96ccba6fbc93130fb8d5dcce95d1bd72178478bd522e848f78be2969a7b8eccc2cb3911e39a2a11050bf2488bc51435c7fafd51bef0ad99e277e2e0dcf72ea503940e0e82d0168c733f5c6c906034a01f175836c546e8997b29433960f49d4e363ceccce42103fbc81f9fa6fd166e0d0ecc5134a085fb1e06dae70d308f8f39eaeafe01d12e1351f9a4217b0ec3e283794737d78589168879b75522af059cc2798b08b66455cb0d5ee4cd6da0843aa0e524f119cf5589aba6e2a7bead2a1e023ca6a1678cd5c517908972c795e925f0f506f31e52db21cb18ff8c487f0f6ec2fe4e77eafe444e798fab733f67ec5af79715505850e950c10b80fc06cfd989fe6c1fd166a01899d35edf7682f3d08a8b578a0876cd5662cf417348bf3b84944d81f477ba7f688b45dd912f2e8ab8cbf0601ae8b7dc8162230354480baf090c1026e8acea17d73f5a8f13bed706a650e12c38e7ad812b6a9b4c5ead46867e4f5018695cbcd531cca58f53f08393d21f3e42ab200354076a4d83dd15407ad070a3aac2616a6a87554a72614f0bb04b8981e359f95d9d0b210f864c1a481506067a89488e3e0674b97813554b36d931e863ca7f970a9ea336c9e786253fa8f2a033ba457bff45a1751e2893a3d75b50b7ffa35131843884ce7ac106d8340700e7fb3d38709c6cfbc34181a97d86191b728cac662f513c3aa22b199c04a3cfb374b37b61a1074aacac16199ffbbbd4c9766c7d539ee81f17bc33831af560fb15d563cba94dca77d867ec3c0dafa665027203eaada17ee3b96d792d2d300b13c82201ae95d050cc32907c051c0af877bc7a740c530e6f86d1aea09212de7b4e634b9cf8493f37fa53afe934a114c1ae341094a33e168ec4003424d0c45dd12b5eac9acf2bb7929c62aa33da318e856ac0db6a215b9289185199da54519ab01a21e86ad86f7e6e017a3198d76958fb54afa805d27f63e0497f8a1b684faa3971d6895c01b2b1869a95f23872bdbc7c5dfa19982bf4abfa6a94b331880c3a917836918c2bcca5d6c41e63ea30069e70d692490a329dbd19ed93d39f67cc99d8a8cb2cc981c9c2b9782c29dc17cae4013f7f06947c47e13ae2f533ae5e94bf21fcafeb3872da8d40906abdd10b562439f4153dfdc954f287bbc279905be8245d1222bd2ef9d0b517ad145547571ba17bef2143b5b460414cd2de6eb480c0b0680819fc08e22bd27e50219443e59db74999dd48dc4870deeb0fed561c47444930f85b9de9b70c633a8cc2b61990846d4a9b5bbef6e796c3fe394b51b74acff9e066f538aed6619eb89502722066d1fdf93f14c717e4cf5861f950326b4b9d5c853133de34773cedd918f9adfdb0725d2febe3d3441d76aad01b37723c7cde492fc7f89f0f0968e2c53e82db6b37bb9bc98c4f8f26b6911abf372e1a415137a2310afe15b782c1720255a9611d4048ebd14bbda5ab82f70a586971ae0d9ad0b3edbded8c1e62c27a23acc614269a410d6e4696a34220df77f26ac10732a9a6368574d6c353d54a95c0d9f98be0d984aec10fe625a450d9a6dc2c928f5b3ca2765151fb97c6012c67640c06c68fe2978cc22522436832d95a69429349ad4b3bb44c6e555aa1cb64aba50d5d26b72e8dd032b95569863613ad57f2a569d5f23aa6a2e3e444b24bf6c04a13c8f915dc8ce7a3c32684df9a6f58e9872b10841d29afbe4cd89a5527ebd23eb1eab25569852e93555ea0439bc9564a23f44c5b0163254c392ba6153a265b07b212a68435765ae5298d31632b3d722aa0ad2688e8481962cd1b3b0ea0492e9e4c32ff66083092362655c486324b124e27b76687ab7df790ac5bc24f0684481e4244067380694fb49d88e10dc5c25267f4e8640a9a560ddae122813b7dab8a4ca534f0165d2c1619a4c3941500308e855d42b9b087fe3c3619e21c9e254655435428d9a99a501638ab25d155512767661465810c7d1ff5ce361535f55152ea50696707d0812114b00e79d9822ef516760f8718db85d93d9f18118620f74bb29ff616b37880689b4db6e49276594ec88c0fcd3505f73f36d34636e8e9f06f932c84728240b6fd9a49287c5be1cfa23212655248efe02ab6660e3d77deff5c6536e8b509f27e869f4cf8c622cf466b5a49fa5a48ba3ec127dbf38e22da3848418826b5c20895b04e176697e6a59ac8314b79a2194a4cefcaa1f5193c03c259bfa7209a66b3ab543c13e72b9b7922a9afb170c53f9d2977a22045a46fe049b2774897f04f56177a734381c7cf95f5c28e4fddbb8439ae34f1603fc1ede59bddcd64fe4f6f13fb18e7fb55bdbb81e0f1f3c97ab5e3b78e25d88956fb096e2fdce48e3af37c7a5aece34959fc385665979775e2a6821688e86260d25499d6e506213c3ca94e7babf936d504b95c8614bf2f49b743562df8981b464f9257f553ec9a97ab931a57ba1419b93395190ac199b7dc1c52137b5ef3a21dc73ad14bf8a6ee24e2b8ee8057966f22a86f6635ae56633292d8b63836d7aced9f756b5c2657d61b254c838888222b8088f645f313bd4cfdfbd8847f58411ff0dfe34b263fc1fa41d1fc242216ef1b2ca768d367bf111728cf5da9b4b770e1bdf0fd29efcfb9a28ee8beaf56752c5472751b966c4816116ce4800e493313b32c32443b1686bc5d3f2ddefab7e206cfe4ea6bf88bf6fea4e881feaa631f2243b4908939ac8927961390739cabe00caa8170ba28caf0c1290cd738a2b756fd91176126b1045d266bde61e3749d084ac944a710a9bac1136d318c48be31aef6cd6a498dfcea692e189cf2cac88679a1b0873529061ca41a69bcb2cb8c239196f54ad9643108ff367d70ba4b89da5f21fa858a345a5c8514e7cb9d30da136bec010aff8156661dbddfc9c80b1c078079d39d57146a1d496f6472aa6c0cc30b897628e79c60544221b80ae641dcf5ca1ec5e1cce4fe3dbe5f5e005487ca66168fe2986acc77eaa6381e34d352958aab8e702c03af0dbbb19a16dfa9564e154e55b4db0eac1f79bc5544c19c4b80462fff1bdc463955314bd2a528d894e79997b6fbf867f24994ecc2716c5564bb68e79fbe31e98def73eea1c1575e6504aa2eb8f5edc40483c24d521568d45ad276e9bfe7e50f7f0e4c0f779b1583a7e45d9e1a69128f1d7c6bcdf6f779ed8d3d8410d2f53007690b5e9c6d4537f93811503d94f76ea02f7a88eaf39d588bd52c14c679a0aa7e7d3c2289be5c591b8eb1a4fda8c2c1c5cba6b6a50bad00adfb8797bb8a949503dd58da0c93ab53da17ed06bab546fc06ba5733ac73788d66a818eea0191295040e56b022217f86343d34ca4043d0d64ca215850617ae2963e75f45dabf67d148279c0a44605020676655ec323c0f97a50d82c7258b84b46f841041928089e679ca4a9e2468aecad9a6d0081cfd0b75ff4bfac336a790437f10d518742b67249d69273a21c1c66bcfb76a82beaefd89f3da96b496d1366d6a9ce70447da434b41f4872bdd1db933b4f82f31883abf6ce98d667aa41a038baaf6642471927e30ee70b9ce0bdc6f9c5393a99b3728c1409c58b77aec0f7aac1c93a682ef8ad4fbf84cee07b4492e54e12789ca5c352745406f07a8e69bb58a4d04b8df556680486643f182550d9f6cea372881f5d39269dd7c0027d52c8867f1566f165728a462b5b7586f8dde113a932825789fe824bd0be271759f24aa25c0a09486f6bfa07238e58df28e229ae208112d80f66e38ad24880216039117f71452cfe05f3f66a28276a7d5dedac743db7537522a9eb2da35ceadfcf63fdaeb8243cb0ad501d9302cc311a54c881f1b7e9b4e2cf23442317d85ba006c6f4ab8b9989f1a64bf25e5987d2b6580484c5f31d71258ddb06b2b0d4ae3b0c06193f196759b000f858099462c966118737225f2aeca1feec902e63b66313b9b93ae331c5e9da36dce732dd99ebe92899e90724210168df1f6a632539e2aee69e7a340f39ce5efddc680114a65fcbd7cfa68bc705002acb5212c4b899a601111d6e6789a456a1241a0aedd80e523da8bf511216c7d8caf9752a9cc59c5d8a22530effa28fe76d00e740729360b3059d8a3459717b08c79cf55cc652cfe4ccf711a2a7be0f85b835654ab5bb6d366e2f933283eed531a3f2c4cd50c9d4c449c2184a00be1d7e57e30b5582bceaed586119861cf018ff957c151d54d3000edef62b3446311912da18cdcfc2aa297e96cd3a891f02c4840865de22859d355845cd82aa749fb368c42a89350f70b430d0eb7b688c302f084078b7c60a92bc520854eacbeba0bedf153cc30b9dfa8b4e092798356d916c12125c6c1e704a97501e133691b21669780ace1794786e1152ce8fe8d293d4005e56198fa112b224a8bc9d21f0eab5ca32ff18ae3dcfa0bb089cfcd5763dbc4c279ded709390043f7926c06c1e3406a31e149d70b72e16fd6820b93b8a77d4256a8331f727c51670223659780384bc87be5c851135c5f606782b4fa19218da243068e33556c0eaac0aa53f0444194e80eb8b9c4e89a72c141a026e794173fd3e01e5117a7bcdea439db1d22df1b5fd57ee8eaa7ef5dfe354399341177c8a5c65c84001304f2dfc732e4e26ac77c18d7bc25ccc763dd5244afa356e3ad499c8d01669f49036bb18dea3a6e09f37ee4bee23eaff72289f3ac11c882d09116f45c4a96b4a897c7d26c69b798da7dbf675f9e97458667b13d5d149f77e68c459f0b0bd161390ab33b07dc3e2b3abce009365e723bbcc488d4a73105f73276206641b11bb86dc6ab029c2364ce0b7e019124cd018be6332890c0637d022f0d337ff36680a07ef24fbc23795bae126b1dfe1fe8a0efc19ee3ac00e7b41c73b7354e0fed4bc191c68ce730c7bfae62387935d7f588b539f68f4558c9a5858b7e34d0e42c4a6a5b99bdfe4107937c65037f0187945a177560aee6992f2798f998e80971abe544c406e029d8e9336ae9b4b14cb1f00c9807aca5230ecfd46dcee8afa5f752007635426e39a6cdd2d5e27754285b3fc79456ca26a9b468aed9d837939ead983b1a012a01963bd22dfbf3028edb6434a2511be8d38af8fd25a4e19a7eb6b0bd9292e95f41c9baa13eaf01884ba29c6733f36836792f2c33718fe11ddb750c30e33f9af00c69fa2c1ecad9e079442b96c5978e4fa2c1ca8d64423b92e230795aac7a23fc03905ff129ed2518b785db9efedd82454a6159b3b5962ecd681b559c86de349b640bf33f5930ae49b4ae6154ba29e7c064d26f16ac7b1da866cb701e5a6030cb94ab01a9de1e816c285b69261a501900b76152c79cbb6bc3e7c3ecb098ad6fe3ae9927db4e670485d985e2763e9c33689bf69ff84fc12a14e467d873f4cc27c60d555679c8c2a48407c0ef114577fa30d2ec20036a42cee22ff67278243f952f57b7e37dc7d7504fe5d004c5ba98c5989d331db9965463a8b6be2720321a545c1ec20f68217d846781cf07635fbaaae552300f76fc54a1c9a0323ec152ea3d973bccda6f6712b94ee094816621916fdbb4b77162ae5a03fe9af721ce90c4d08d7ffcfc4e54afde55d5e87f0caf6da6fa854bd53311951eaaa23b7c9f3048345651d7848d0951ca8ec5b55c3c95db34f51bbab3138d481794b3ada68bed6bba63560dc583248fa14258b205ca4289078f387d9fb09e87c29abed1503e8de4a22bd5f15e22f2a5dfacab2511dfa8d60402010fdd019c08e6b61734130468a6a454ea95cb38f2de105813c17f2136871656a448beea7ef039bfdf980ae4c4d2bf3e8bc64f1bf7ea632f96b61cee9171388632227a7c1f76d1085bf6af82ec104224eb93a46bd77f7dafa199397c60cb7f2b827525045d7d08ccbc83d71fd1c467e316fdc8cfd83624134b12a0ab65ce12735ab199fe8084f6ff77dd190384e07be853e4ab0af181254f6f5a59bfa36cd5bbd68ccc5e3e78b70480d6b8e5935870f770663576f07acd7993199409eb99dac21414daa21bf453845627b13bf2c5a9035eef97d9fac3a59aaa9b8e2572e2f66286f681533eb394195b444207f75d3b61d0ca895a20f6be758341ea7d2dc353281dd53c8c7dcdbe2540add78e9a43dd917989051f0bf429ac267d0c876046bf0ebdb1e8d3278fc74295e3114c71cf8deeff43ecb7c13d7c80fd36947b5aa5683fcffd18a1ee72871be9d8f7c610461e1df616091ba6dc4de16b845de9b435a1f1262c7580379e62c873ffae96ccadf8cae33c8bee1bdc3612d861efbd21f4fac0737752acc168950abc821e0e53b9e681f42d1ee16dc460e1d6ab88875bce9372f6ba4892984bcc49622b59029d8ee203bdab2dd8bffe1de97201fc84d22333313a5ec6a7a434e20753fbd4c502bbabf7f700fe199a6ccf9af8cbd795f9428da16af6c8b6f9c90276e8cacd308be5e23d4858aaa34202b983cd4e09612fdd748dcbb68612a0562b26f526e0b82feb0c9dfd093afd31ade094323884d7eb4ccbbb74029ce76ddc0b0b97c189be5af666c2650e790678e32b918531df6d40a16a726cebb0363cb644862ac22b61c3d71d01e9c60302afeae6d49774bac6ddf0b5060336581ffaa08ffa583e64b1ad360213a680afe491a986af09a62332b322b50b28444f4639801fe5be277b689f1a15238fc4c634b8812f47f2d9539c1c55f381f3f6e2aa00779ef471c4a418d14209ee6fb245aa860ab33613efe16ce4bf38274517c1176957bd8ddc9975858499f20c3aaea69378179ad1d24962958f57b3e9dbe2373aca2bd432e4c42a1bc2acbce3bf94039a10b11e3c6b1050826a8124bb1f8f247938017bb570d8b800a70767fcd1674930a9158891efb492e20f144ecb88457658ff3680947db98220aa83b586dfe1707860200f5cffebb67304e510a404bd6fdff8b54d166bb50b8f236d511befd0dc66dd9f014c8988f470831474c0b7d046435641e02288e89c830a74c139e56e2e8c918adf80bdd54444779a60e60dd1b6531a117cb6ad90e28f38d3b67ad72c2322b3948baf0bc43c55e174060bc3b009d8e176405bba2cc785fb93df90ab137d1ac0d32f0b51ef08ab1634278913cf0768925ce3f19d798d83a1d0fd7cebdf8c682b07521fe3b9737d53d28f148ebc0eee0f51ad027eb472a0bc0ef623e0b1a101a5e8d01199cf567aedebc7400bf5a7c42550036f46eb8b1b467456fa3fbb874c75eb37c8004ca665da582441032b9ab778a35300d38ce7d7abb989cd70a45eb05abc61159d9fe0ff50a52674d9bdb796e27111cd2386714a90a76c0e1b8b8a190c6d966adecfac25aae431a17f292987d187d78551bb8388f04af99af35f77ba18a89448b03ae7b51ab721c7c5328a9c4f0771712ace2f6f6114b8c051492b3818b3130f4405522d323cacd0a55170cf5bb3ecc20e8e1ef18c5ef7d9e83961aae70e8edcc9880fc0881c871cf59200db3e0b9328364664c5637e2eb9cbb8f27da69357a0650d1bdb08ee85bf9064c6791d4a18deacc9c63413f82c4782f66c2a3348cde5d3299c397a0d0cbdf50697094053892ae7655d698e6c2afc7643f6c45f2d8435aad56451c049ad1b82b83c0344b50db96ff1e54659a414d9da026dc2bf256d8e9bed45ebd9f9f2d04962396b54681559d7106e964c4e6e5e0bc5550269f6d1bcf4c2be090c7fa89ca6798b972c4a46199981a38fc8e96015f5bfd345ff0b90458529ed5e2f932e7951029f6b784eaa04dad52103cbc5eb8ce5eac8d2662c27a917e3bf6a330e8b01d96e50f8e7203445587548528aae61106a0044f4ea8b698caa5f5e8a31280047d23e32c75672b4ce420776174ebc4ef8f27089020e4fd517a00a6c85df913bc5cda89758010f700f27dcb57d43e8ce2ad26007d9d94b072d0a7c3e12a093cb504f763690597e49592b868147c65e554dd1eba8374ce5bee1962b024fe2081345af6c1cf68fecec4ae667398e69248a6ee13173d706e55c0b1e9f6c4c2601de7ef31021ac96a7d0ef0fca62966cfebf79711365ea59002815aeb9948a8a9415bdf0418ec3a745ce7517924cb2554cf833fe510fd010f7b5351b3620f6ec12a513ffe4cad4911d82c5d94d428bc751e4c3357018cced91c84ce3d885d4e01cbe975797335ef57004dc0cb12c0c6b0b16999bd21ecc969bb3bcd020dd05d311bb15b8e9a4023d19c13070652e3f92db24e16d0fc4cd754155d8ea544c2991380fee8495ac4c41934da74bd3dcf105dd3ced9a26d57b2d284c0b64ad76022978410acbc929fbbd6e2ec9c2e21456d57b8d409e711a5e2564e16c998dd3f865bf7f771f40f5b5e4c0bc845932aa620a1b70aca257c0a3a50c0b7130f1ea7157a83f41259b1b36d112732025765a96b1bef436282f099275fdcd81b90531967498e5de6555461f208c2033d3a53827b3f8e336d56d9248cda3b3a4d166143523c6f27a85fc3f2d02398a591c8a711402c8a7a42107ca0e43d2f71943972a3e515dd4230bd1e2f6704b47917aacde06f60cb5228fa2e87f84466b3616fe3c4b47987c4225b663157397fb6363795e988da5af6d02f5084307102b69a07bdfac26111fdfcad68e0ef748fe70ec103a76be49048e2f59e5f7dd4e1dec9d67a875a7276b9175ba394dcb0ca0e9e3b5b2f2341dec15a4c10562c3665f56ac8433240c22024f61fad9e7c5f72e79ed2be2a395fbe98d086535b7f8c287b79c6f1b135f09cfc6320811beb31a142868ad1db706eacb758cbf9aed032374cdaf0cfb340b45fab050ad22439bcb838aacb2329c4ff2ace4609e46ad0ce7081e32346ba66495e5caf3147066393ac43afa7cfa185147398f179f37c846377fb8bb0d93f001c1e82ad379fc1adacee6b39d43bd297c9122c132da36dae679a68eb0e3ab77a2a12864038e6da043b5e99513306172b32633f333a2541d4a3c65a3fcf8f1925fc4e435e6084b076a301f5bd1f3a437ab6f7952557801bdfcf4766c997ea158d8d19581ea956bf43b4982bbdc2fa6709638fd331a576f842c23433f0e2c054befd0e1418fed42ef498bbc586302aa01e18d7a84cd69edfca4b310d18168ee36405a2267682542a677cf765a46f44fd959404434b544683209277c1d1f98221162630a02781b1e5ed5bb34dd6b6db0353ee7407a9160fd8df78b181c70562d1a3f6da9f90bb762d59286a55ec2365a18661c8d9ec20178a4d4da7f52ec046f67ca18c27037a18d49c19c7aae7a7f4818e2f54647ba40c7b133bef4384399220be77d3836050e80dcda8b2b902b0d6f65803b004bedc06564d08be902d625a64fa07fad153d583af492cf6f1e3ee2f3d799f4026ff0feac8e310f771e25cb21541fc7806e7a5b38aecf613400d0e2a37fc12d051cebd34db631bc7b072851ee2095f964b68b4eae5f6c3a42a7cc1d1844cf5b9047b19604e4f78754c4f76070a18f9f8ae920e7605b297af837b4f46e3828b04c06eec5eceea13188276de869acaa5728018ec7e3889b51807571ac45407cdca5ab04feb20ac06fe1b5a3da0eaf17dc87f7e598cea064ba147669088a57e2e42de8594583e27c6116b3ec3f8114a7920cf4251ab916c1bad9adc57260119369116b30ef8bbc3fa312ae8369a87df2800a81273381c3304a5b23d04b3f28ad12b13c3f256a2f360c666f698e6332174f9ebc6edd4ff9a3e3e16bb718584979bbe0f5c1c753c912c04345467c47b7b7537c3db678e6f180f0928a9606d62b6122279c3b9490fa3f300c304e1ef2cfee74dc61f7ff6a1069a4f90e4fdc4dc5f50748845fe0396c07396085e08ba45887e74a5a03e9d53f69b2dc97043ad233bf55205c914d3a5881339e5ce3c9359efc9019739d4d8746d8a2c9507138c6c3d23a1cac6cde51e48ab045a28e0291a209235e9af1246f60be4b7b6ef440dc05046f694636b48731bbab9935620921441a217bef2df70e510ba20b890ca7a7a1f57e3c514f3900bbaffae35cf5bdc95bbc4f82b6467e470d257b5868fdd534109477881b375aacb45a6aefb7aa3494b7efc4140e7bd27d091262d621414dd3344dcb5593adc9cc94cae286277712ad72e54cd79d0d58b8d2a703f665a967bf7a405656550506ee943be54e9f9d4b43f79194b917b91303783fff0d81f47dd23550529999ecab285d3367972739bb49ab50cd7a48d0736f15791351ba64cece899a16da48a245ed15572d94f4e331c96a15373a79b491ad3c0a41abc82c6e70b23d095aa5ca2ca6e8c9f6291647b6da7c9e763cc1c8737e4ad79c4ad86a6551962cfb0f08a0bdb42926bfd12a36a4abb37d120b902e99eda4337407924ad20cb9cf7324d9dd972bd2395228f6b672a266d2839480d493422766aee1cabd1d327315bbc08db4bb862b2bfdf915da52807fc2ce29837b74e7708d6ed19dc32dba47f7280c25ad32a44a583d89fb131e897a3a68c5da43b092dd06c932294896ad46bb4b4fca72d01e5dd77536b396748ed6eae920cdcc5a9276ad566bbdb7d9bdaff7de693f67c55d37b916225313e964aec4cd9bced119327166ced739376d45eb5449bd7f781ea4a637e9defbceb91da3f3a673e68b5e93572359315fa5cbef5e12d83a59862579edf597746f5b4192e18a9492cd7b755a27cf7bd7741aae5099e50443fa6d1c32e7d659b8919a482369b76379dcb4ad619b765ba7add86eebe4ed433a53f9ad84bae05642dd4df3b69c8942af793cbc7747e1d33ffccd1b90b6a4945a76eddab665a46f5eb66da4b0ade826ba5576da4af987b993176a29f4ddb5ad98afad0bdb8a79536dce72368a20d7b662bee4b89daed4df6bd73e36d1d51bb61513857675ea643a71da8a79d33a6093b84801041a1a20d008618a3e570a3ba773623f18d8f169950e996851eac0785aa567891290d21a70b543cb52b2ba4ed81c6a71c2482c06903b88dcb731b272bbc02398083427fb0d29cd096b662ab13236a6c5fe11303802063f2dca9c448bedc349501b69112daa3079be67774889248bb6aa85790919c279ef770932f7c6f382788809b6ff08829482a75fa138e0e7350f08cee0ed0c3d4d3a3ef57146a62499964c97ad4a4f8526ca3c0dc78af34ba7ac5f1d92ac4ad4e27c15fa9e0a4715ecbb49c8c2d4210b235d740a16a65556e04936a655442ad4e2769a69b6da4b789c91b750c8cca39d11323357ef7d5e8a918f1ebed7a719a561fd82008e9d4365baa5fa7ce748173d898b8f601ee93003616d91c40d332ee89047c22df23c0e4caeef51bfe2dd0b7124694aef570a1735a7569d776175815d5ec4c8b9c65507270d20f7b1e2ccc65c58755a9c3b66a6efcf579a1599b7cf572aa4cb3e480948bdedce5adb2dd3229116a7a5e9933933340490e64831f21c4d81e848c883e61baeec49610d5776a6553a24a26cb09f713952a2308794474a542b4d9e9f328b24d04c9b3cffd980f332db9d295d12366ae2b45acec57e83aad640af9b20d5f17a9c642ad4e26c2ca4e649d3a2ddc9f36d77fab333923569c0f9d1d2a25659b137a247d2957d9ed6e1de76db86b5b853634300c79ae77c8509a95467a755faf3b2c65a85e6dceed8989da1ad190779323feda0416167da053d833ced0ca8a239dcd7599856b132daa4b5a76ca116838a68ab97204fe6499a091c67e439d42d59c4f70370a439799c2fadb2a2a20e2c68a8eb94d57eab1e35d720730b0db4982359939b1e0f2d6bb96e91c799bb9baeac7a58b155c4121f80f735ec3ec9d364d262f6bb5ad5294857109f31e9aa49b4789f5d3b7343baea350d045562ce9174e67c66be909028fd7985ee586e588bb33b27fc21593d19fc7a008ef3e541e4596162e80ccdac9936adb2eab9c412b627d53fbce09e9bb75324959d64dd2a5bf77209c9ba57c208e0d8e52b8366cbbc9f2f17903ef9b6701c031590af054c94fb7b26a42bfb0dba37571c0bb8f7ca2d540dd6829325ac25068a9d2cad0005ce6d4f0b2598d54c0217182173275d14e6a68093269ca681a0ea46ae373a45660d7c4b2cf21c4010795eca9606e849bd0de99a796a80d21bd22cec2a401c560979677f33bc2159b5250698280238dec8550e79a08995fa0dc897c944a9bfafaa3a44c311cc37a48be67ad16464e5f93ae7fcc843ae29170d598bdb460f5c70e370f54065a276ac0ecd79adabda0c583fde59d2ae751df8ee971bae9aa669f79dafb6699bd6554f0777af6ddbe7dc362edbb2a91dbcf75aaeeb2a0988962ff574d8d96277eebaaefb96dbdb41ff994ba5d23b596b9e5b6cb1457db6ea118e1d50e595fa44eefe449d73ceee9dbb2ecbb87b1f97855bd75dd3baae93599b975e76e7fc86533553d3bee1ab75aeb30b47d5bc76b57b0d6b977aa44eb6d52e829f11b76d9d5518c41224fd7a3a4ebfa773f2350f12c9e04d3bc69abf73260c9e9ba0e6bdf491e3bc73de5738ef5e178e9ee7ddf3bacc2b79e79ebd1f49d0001c9bf80fe9c3ebe11d0c47d3bdaf542a954a2ad555e1185adc999baf9a0a8f4eccfc3dc323232fe1ce5b07e2fef47490bc846926c132d3cc81e03fbc52fa47827580af1e0fca6303024c965966ae4ed95f295cf5e8fcbdf3a7694f5dbb76255a45d3813ae7f1e8ee5dca17f852d8f93b15c243666d760dd25455d992fbcc44134936b614ee4687c31be2cb3c64ced162fd9052e621b3015afca495f40b32b7b8f77d5d6c5969a514c404dfee02af7af4bf0b299d3b786e044bc9da9e9f413c236b1a87c12c65ea0a1b9d97dcddb31d4ac3677982f552a10435d2559974295b4ad0939ac2a4a3f0cc273c6119c4b34f0411a450824a702530c4dd376ab936914b1f1e53d9b31e900ffc56befb4ce6eaf9989f29dcd1ddbb465bb288aa5d4cc141611b1efbdb3b0b581152ab8ca47f26d23f8d72df90cea483600bc071d2196466ef4ecccc9d6b6ebbffbe538f47a973fdb0fdf4766c42a6647d1f523387471364efe321f3f7598f87cc9d4b786e91271e4d70022da4f092278f99579e0eefa4ea81cfba79aeca2ae907bedbf09c9f8783982083975848e9de39c9fa30016464f03264c03287e516b984573d88c8e089c8e0374c44969e8f67ed39c8cce03fe9994e3d1d2612febeef3369f7c2510504672d7c6d91ca6eef1f177a78d5c3fb770f3c57ba94acf1fbf61cf4eb01be3b58e26e3f0f7721d781541e9bc8376c2295e7edfcc85c0d49204e360094595cc193672665710550d6a8a578859e0ed1e2ec414399e7b78b0449bc2fb58b01e7cbc032e79c33cbb26cce9e3db367fe7410664f7738699a93e21604516b2239e826cd1eda2ad1cc97d6c31c9a91ebe7cf16845cafa53ea7c5a613789003633460387dc45c351f7903f0f4eeee8bea7bd4ec016f3fce9e53456121f3622173c64832b57e360067265d3ba9add4f02442c27366fb04b973df77b053a6cfcfae9f3fb367fa4c20f0f68dc15f3c7a20dfd9b7fb13f5890a41302377b83af54f7d14eadda8d9f7bea3e0e7ea86478007c37b0d65ea0decc6a009ec70c57454b80263884923bdb9f73c3753a9d44c354edd67a9b78bd4db86e1c87d08cda9b1762a1c39d73d418ea32f759b3ec3550fd2e74920f88ff48ff40ffcf7e1113c91fe7d07ef9df3740a51a8394d9fdfbbdfef0b47f0744a7de28b3aea17f5d4bd779ad3840a4d3d9af41b0ea9190cbf6b606e4f7ba7711b8914ce9949a406e07c085aa5de48b1b100e99a5945e27889894121a8268fb2888c8f520bb94a30e42acd90ab6443766d68238505c5e6db0198a36f44ff30116da4b4cabd7799516f1ddc5b85d2d70e3ce2895cc3ceeb5a278f5e87c2e04d26d379c86c32dd9a6e6f0a2d085610972ebd0ae22772c54d74b914b61513c53bf5bc7aab7556f64c1c234223725015805d77777777eace000d45b20f596a81869e6e7be98d47d0a5148eb344549a4f505a3bbb24da2428957afb4aa5ed9eb77dde39aed45e6fd9b55da5a5eeebfeba9277aebfee5a3b3db7855a38e6cc076798e1de7d4b90fb643822434f3e034c16b3d4c20c43f92707204b2dcc403376383e410213e47e76033e844527224cee211c55200fe108d60847cd24ac1268be6811a54540c732873d618f4f2eaa5a54a22a5481e06062c213f4131414f483b1cc346c309e9986cdcd143158ce10c2b006e3cec79db92c27f3c972329f122cb38c123cb38cea525f6a0a2a4d255263ea0afe3093c97032994c2693c9706260996708cd3c4368680c95e5a8a80c326404c5c09d4b8e8e8e8eb218159619064c459b00e4c3439f505242757e8c7e8c5e6099572ff0cc2b4c67e80d680ebda13534073060849db99be3939393e393c232bb383a2a22071a435f680b1d542aa33c6570aecc95c1c93493f88ce099497c7eb05034149405172ec29e3c3ba78e6eccd18d01b1cca753df344e1381a7775aa78d904ab5cd8fd1cfcf0f0a851ba65950d3332dd32e389d422959608e4f4e4e8ecfd0374464866e29628406100ca70c8e95b1323899beb453da895d11f4d31385ef0ba56475ee8e6cccd1d1918d99333453073a383693075d17fe18fdfcfc1865fa2245264b1da6cc84992e76d8b6b03357737c7272727c327d267fa6168c88843419dad25868c5992fa99219bbdea8370090e7690f8c4ba602c8b23045b266e74d6552a2b174378351d9f3058145bab44cffe180fd964c9b65b479de244f0a93699e1f33327c6600658b191932168d4563d15834962cc8e6d9d258fae4250920c502c893694f472fd32ae4694fcf7ddd94f633034ba6f695c492258d272c30e70cde506f3a78af439d8eb2269376d30908575b45fe9a6436c93b877bb4793aec498729941cc57789f9ea264adfebce76a089527aea6e4da7cf19e6a0a613edc2d2ed7c753f8df4e4514a3bd33cddc7295c950ecea79ac586a3e9e04ff6274bed3dcae0e6f138dd924ee0774d28cde361fa0a0dc7997940ba6cfa2c92ba26f0deac6696a6358563b52693e994b598e6efc15fa3f9321d1c6f91bc523d20f4a63b877bfaa8d9ee285f24e46b8989d23ffd8774917e0aa77d77c1104877fb1c6daa79626b5fbaa1e9d768a2d063f09b29bc454bccd7b4f9424f02f5b499af5964a2f46de68d690ed6644fbf0f4d3fd970750fbec87c3131368b3de5003c9df200bfe201e93278264eefd34749861f92753a85a07d09656fbea308b2e91cdd726b1c277bf28ae99fe933994cb7a44feae930d1a3ccd5ce648f84291ca516e01ce66b66fb1fd265ba0d8be43dca393afc2159935a6f0777c23c4ef7de5e8f9316dd4f28f0dd57a8af9c7e8f3a85abd4515fa5421f624685cfa0f44ea7f390f9149e66c067301cff4436e1efa5e768db84ecc95f78e730512829aca3d4225fbcb28d9a3098904aa552a348bf85d9c78bedeb4d659a5ad56bb762e67e434ecc35dbd1e1d89ff7db589fe156b5484f5662aee1102dd26da3dbc7546ba99c12b3c543b48aac4ce0a4c97871744ba5925990c95892e5451bad72a365043dc9bccb6ba46dbe4a56be64572b95d6fba8b99048a5d2bba3304347d15174141d85cc1da3e91c476503d2e4ec4d64ecce7db4473dba70ebb1bd147ecfd1737b6fb7dbed767b8ed47536731f9ea3858d16365a988589429ab7a333150deb22dab2b0266a95bed647204c157b4fc332120c4682c1babbbbbbbbbbbbdbc2b68f7dae47df0bda5101a5d4b4987d65c3ee94c62bdd4f0b6b95edd66ede4bf5e787fb76ae3b6fb87641ba8a8c35c81e8148f621cb2ca828ca5ca9f4e109529a164f7225bd496fd2b36f09e048676e0d9da269d399265958fd912c987471e1153b4276e8ebfb8f72dfb86fdc37eea5ef3be7bd61b9053a35ba7a4e520bb3f494fe7a4636ecf045ea42cdeb412a7dbe7ff280f43db5b0163391fb780d546d16366e9f61879d3455fa20a54c7f45228f51bd80b72567594b1b591eeb93b37befbdf7de7befbdf75edc895e8f936c5b5afcac00fb586b930e5bc9ea97f09d282df634a26394d26df3be93d7e3f3aefd3e5bbe8ff4ddc7e9d2838663093c670f627b0ed77f4b00470bbb42868059bbe91bee62dbe9473b859cf28070dfac0feedbc3516b91d21e60fa03c9ca70a4f08c25d0eb510a9d98193c77eec35f0ed87446babcb007d225696ae80e8d71d786aa24f529e146b513672261db26ce3671b689b37d2465a7dbe68d9b06a41476a113337be1b81df47a7cdf56ba15f043b60ba91568dbbed91630940265e3270507d703d7528639baf6d079f5a776a17aa102d5a07a5484a5c5cc08a39c55a29c9d23ddee48977519e5cc4e81276796c8168d6d8f7a74587de864abdc40838dd750c50047b2f4b2bc952d0bb3519099b8869741f505300ba3ad422691de955640b6a63c213d3b13d2f5fd2f5e984c92f49194919ca8f97b9052febe691e90ef5c88f2760c99f90bbbb1fe54cb9232d2555c8148baa006faa9427d92fda7fe8cf5475577be2480a46bb9ebcfa445be67a7471ebeb9ceb48a89aae4a8b6d8c87ec391f4edde39175dc5d6c816b548d462f689ed908d4956f60edb294856f612b65498283cb6a7c5cc0b8fda08dab23e7d929d4573016172a6ba327746c67206fba8a83fd295e58ce677cbc2ea94affaf30ec7264cf2ca899ac1072965f0a473da170410265d5ace6e776891900a0ca016b090b38f568b9c61419e641f2d16393bf591462a225068a44045397bc685238cd61f151886a3fdc9d9ca76cec4882660707e6691999d1e7ab9f121225d9d2b2c83188877d20b6fc776eedc8b8ecb1fd6414fc27667c3361664e68da74fb2e3c84229403e1ee9247647b2b2576c6146f83e3f5a58ceaeb5c863653b77d2fcf1ba7045ba975d8ad3b32d94025525ab1b158a740607ac67d5c1f07674b8f26df75ab870457a4767c42fa4332d66b4d3526089ea59c3c098aae73f6276eea4fbac876dee86ccece3d97ec32a9a9de73e2250ceac50ce54d912c156b22c746266cf48af99a485d9e5da070ab8a0ee39d42d5bc0d9225ff3041245ba261492555f6f38593e243021abd4666a3a2dc301274b76e9c972022bfb763aa93e824d6e8c01ac81166b4f16496dc8448b35d5e29368713e94d7a46a51b6a8013bb3365041c58d14a4701364052d6c4cc60a93b373dce5a74bce84947cf5ecf365669f315386e4d9e74cabb878f649937a867a767af6893373a64eab949e7dc2e6ce8c4d1efb4c7b0634b3eed927517698692493677296919c2339a7bd354d9b5568dde1ed8d63bcd67bef611217c4042c41e61654d3c076818165906015d7ea1a7a926dcf1a1634f68f96294cceb27f4700b9afb2772478e612eeab933c2024e7ae793e48ce919c2321e93a1998e2e4e8c4c0b5c5a54fb24f0e03f244ba34996532cb56241da9d3328591fd45765576124c61d947b2a7b2a3b29f30f5a13f1488067d980a613a4489b2dbac48656931fb0c5c5bfa247b87eb0be63133a5e117b44369e80cadb191b3cb1cbd691e127d6dbbf6ed52d336d24ba84d8bd94d8b59efe4cc8627d33eb616f9d703a2bd7b27e6436b2d53e52c0b572a8caa4b5a509daa8880eee9170450087dc9ed3ba6ed9b48aba86e496e5ddc7ee436758bba7de3e480b7a59d5689354ff7b44fffb44a76abddbe83ba35e3f63dd444dda214c6a848a6332dd99e6bee5e4a8a917598bb07a2f06904a7ee028f9c04bbb80a93bc9a8025df9760ea42358d5aab592ce330f0ea2f308cabf08bda42841857c18b98f1af0a70bebe744ba32796c6569f6cab16b2fd770490bec49e3423ab70100fe4927b1e10d5e93bcf87ead4f3a122912e03539b1b9c189816a92c2ed27585cc5ca2e2e86c4a3ffa12acdd15a6362ddac3c0f486e2b468afc234a7457b124c755ab47781298ceeb4689fc2344679684f531ffad3a27d0953202a44873025a245d4881eb5683f03d3227d624fc2b525a4346700e9a4111a02ca23cdf9fe640a265aec3ee95b59e427d9fe72240f094a83698d18c0b1bab8641a185ba3c8f6f5c5deb05d5a45c284f53a31f30da78bb5b6ed7db72672178b253e00b34b8f87d6b8d622187da2829b5d254b7e8229a509ca4f1faa6529fbab9225311e4d4c5a949740b57eab1e33ec49b3c049137b32bd7aae92b62a5f6558c35abf53fc9d931c7d0db909d279aab54ebf97524ad3a5042f65c9fb09a7348771ad250f8f3348df3e4c0a552dde5f233e2c80638a04adf2a3c5fb4b5f57b42b6995fb31c638d2dfeb3dd94a829edc570f47a6a12a06e0f7278e79d4d31a9a8b4ee99bbe913426cbf614d62aa95b2260aa248f14c644674407bd6ae45c259139e8bbaee35e741d9e2338d38064cd98cc5152ed6f3441a6616d963e816cb9b8a5b7972723b255b8ec2f5e8c7c348d34cbc8c8c88394f2c8c86538635a2cd983265b947dc4ebd12ab57126d82d6dc38d74282223db38410dd43f9dd3a2d56162be9a0a8962ed613c554832642d8f847d930528a5eb4a97cc34a75bcd424fec35181853f5fc275b91521817364d632b8da1304500e78c8de053a7d0be59baa55d6eced12faa929b8cdc74e9ed381d0c290c95e9f03c51985639d9647b2135774b8b76c4d42c2f311a0b0b6a82ed62eb473e360b3290d42b4bab8cdcba4897e723f55abb8a1315f2059aa4abd403aab49aea46b0cd3940ab02414d03c1f123c714a6c57b6f0ad07e9c31d2575227fad3795a250b57d9bb4be1e2b68b69b1a75552a11424b747610d48963dc56b4843b637d900459270c6b808896834a08d0ae73adc08ed2947ecf785df6bfebc100392653f77a46b0b7790c221d33ebb212d22420732958274e667faae9eeda66d4a8f0423d46490a35d27595000658b3485aa54a586d52a23d79289523b6b525fff62225f58a2a480c60af20ccf3239da49396d53295e5932eb2de57b4bf2add63e9b543b046b4b99b6674309565b3bc70d79acd20a50b4216700fc72965680420d5008b5a10d56687141f6a44b5aa1058a2cad00451cb207f6096c15a94a764ae72947b5d5eb65361b679fde0164b60790b977f4b35bbcb29ff662ae371469af6148a6b1b0b00cc93e277c4de066cb29024e9a8c932557b0e7ac6234a1dc570558ff2a612a1c1f13c07a3b25ab279d03273357d2214d7804995be42959932be928e938cd80fd1643ea7175ced9f320f5fdb13ad13dec33204ebe6c0b78b2f66bdf2929292929292b1f62b691b53ca69ca20083cc1b4166cd36f298721f150fa5b5e6fa5bc3956b3f367a4c622594a6744acd4ab44a9f7200b69861d807a9ed75b8727f5f6d8f203367d9fcca9c3ea5d9b4da9b69a48debbcaf049a4ea8d4880b12d50b18ab4e8e00943026e0e4ec5c8cae5bf4e63919dded441b2930a0fba8248f8c8c8ca9c6ec068dd1133a748ec6684bd3868486ec877094a852a0647da4d4335f2689629f024a967d36940d6544dd42c1f5e3e1ebe113bf937f5f8dcfe41f37e43254613efcaf73f968fce362b158ec0bff714242dfc7d5d4d4d47c31fe713b3bdf8c7f9ccbf77d9f8c7f1c0dcdf76d43434343df8b7f5ccbf7adfe6db1984f0ce87bc73621a1efdb6a6abeef1bf9b7edec7c24ff36979798effd6d3434df471aa22776a8e8fbb696ef43fd23c562b18f14fb4eff484213c50a7d1fa9a6e623d57ca49a8ff49d8fb4f3915cbeeffb3e12cd97fdbb079a90ae522c16fb9e8415fade43be34a19a9a9a4fabf9ba7ff4dfcef71af2a5edb8c876f93497ef9bf2a5d17cf5dff79d88e1a8ea81871a261f16600973439dcbd0d050e792ed65d0f8c298500cc76242d93ec6bd185ccd4e0d57c3d5ec64fb92199d0cce85a3e15c62702e9c0b4793ed5725dc8b21ae656868886bc9f630561b8c9850ec452c2694ed5fa848235bcd4ecd56b3d5ec647b1589e66273d968369791cd6573d968b23d492a330d6d2d43270d6d2dd9de05ea9e62b4d5c9908454a690148b0965fb11d0926a483b5f3876372a1548aa21ed64fb548605c98544437221b9905c4834d9cef97de1f8c5d0182d26148bc584b27d8962fa6e86353b3535353bd93e45b34c63b405d20c0429a8711fee723a1c0c66433634991ab2fdb7038edad09046d42d20fe4ad3ad6cc8871f6a7836ee692d279e78ef95a55b190d131ebc1eee653fa107d4ad4c4646c8b39e97e178bf779ad3ad7b7444614734e679598ce79dc674ebfefc5022d6b39e778dbc92a26edd1ceb59cfbb3e9ef7e6e9d695f164803cefe278ef1de952ddb347473747399e77633cefddd22dfbf3d32f3f9e678dbc5450b76c4ece504e91e7591f9f2ffc489749a75b5646664786c74382b41e78ef53a65bf5e868ce1cd5789f9e8d19d2f65a11ef1c90af6ad4ddf3de8474cd2130c87b12ddaa39f4c47aef215fd5674766881bd9bb8d6e55991fad42efdd08e9aaf72eb60a37f690bdd790af8ae3353d6ad1237a629f3d13e9faee795e586f00d65c61d94636a4b5d418fa0f6136e4c3d049a8b5647b29e62f0a60a9872cfbe121ac6112663934c230b447e14f46966532b22c3bdd9b71b32be3decc14a38371ef8571ef054bb8d5bd17c6bdb7f4627371affda722911cdd98231747f6de8876fa31fa39fdfcd877a90c65737c4e39f69ce96a56c6e2d86fa0cd8e6c8c2a1c55a0171e75b5547faad10d7fecb52880348f1950cdf1c9c9c9f139d526fd5caa4cc5a9381faee78ab27aaf088d6e8234bc1e27f923d2c5b4b8e19b3dec1d79469d4c1753a44f2c91f68aa8cc1723f311c9b9399a1251225a448d7874f68ab868349a8be6a2b96834d9beaa6a89a654d32d20bc22d53b6a956ca2e029d4279647d3963644bb97a9521dd121dbbd64fbce157b45ba9821daea64fa65beba28248abded6e542ab087ba194896fdd8c120db6f1d0c14c0aed4034a5082d22bea137b0bd42aa3579409b58acd825a057b459e51b758b7f78a7c2c5f4bab64dd866da632b4253b187a22345fe044b1a71d4c8c763031214d034195ea5226db7bac7397ca4c8a9c3ce94e071995fb632793ede6f178ceb8ef0a30fb8e004ac1baf6198e9e51b6e7b49e546af6a3af9984271078fef489b5d75858d527f615dfdaf9b1d022da4a89b3c3514543f0678ce91695a11d4cabd40e46aa83c9f65486d29667d427f616cc36d7d02b6ad1bee3825794edbb9782566438c99568772a93542b0ba22738a03599663213853e0ba22d29335f594e8ecce470498a336765ab94d20a9b93335f59d044b1b82451529c24d5e40599d679ef0d4b1e359756c9c208adc242afb550ed25639aab57ff99011c2b11062e6001930ad0608202130825b0440420f007cc50c2010d90c180052820012508582501e4003090f8e1e308550f122378ec70a1c3000528229551390840846900a7215e2e5088128e968a174400047003005fc7a5a000c1f2e1071ba44d3b117be0a1c6cd3070010b5480090a4c40024b4400020f50c2010d60c002149000042401e40048fcf071440f2378ecd0618002149173108088010cf1720981a3a512c40d01040000292840b06cfce0c389d8030f356860994d784c787aa63014f453851a3562614d586303841d1d1c294c4c6868ccc032bf65e6b7b89c604606060a1a34586404c9081202032d624424861f68c60acb5ca2b3c23397e8c096f0d31363c28c1939306460c810a1018e0d8d0d4a4a62562aa3995546476f80716189c36a55e4a2c7458f8f164442405750a978485058e694cdcc299b9b2962b09c219090d464938bc9e5250534446256904ab59c706794cca0102834340696a3a232a05041383232323830d84e1326900fcf133a9764ee887444666e90735393835249e68874248bc821e6a5850e5d67b4e1ce9ce6a3f9fc60a16828280b1cd793725350341b198d6ca55ce063b5b1c0c78a5381d7191dc8d70f263e561ee4be6c2541818fd565021f2b8c04fab448abd0789fd641be9a58a2f1fb1448b63c10818f5408021f29d1033e52a32c430bf2355f94f848b9e0235d2539b235611cf091c21af091c618f091f6e419730131dea731b2356f14f0911249c0474a83803e85a12f90afc993c4471a83dca72fd2a52a92add903e4631f1de0236541a257af49f4e363af6148ba5ef0c8d62cf2f1b17d8ef8d8403d3eb650ac555c60215ffd62c4c7ce6247ba5235b2d5303c3ef6cd8e8f9da3e334dd03f96a1b037cec1fe47ecf48d7886cf54d013ef64b111f3b26f7e90ef2d53c393ece2d8a481718245bdd43808f7388888fb36800477541be9a68888ff30b3ab2d5afc923c4c7e98304f9a22f1385e2f83895205dddfb5346b6284cab674d10bff171ce20f76714f2456d86c816bd11400a07e48bf2c04897f7be079a902e0da56510eb49c816eda127d4460ff9a2441385fea487c87d23f76dfc3042ba48cf2e7eec21f7f0f1b9c16cf291d25c43be2a8c8974d91a3351fad905426a8170047305c251957fcc5706c44412b4950551a067023d126862beb29f25887296434fe823008107842ff395f928f102435b998c03c2516b000c03c25195339c05d8dcd0d63d52c04d026e1070c3335f594c123c3db4757f80f41ca00789907e12cdd735fa118e9d6f8e8f5974c42cea41af8f112f30b4756578c0ec80d10163335f17c7003699daa302dc1471936f78e6ebc6e4e0c9f4dd435bf6879e10a087889e0184a32a37d17cd9219a2853fa325f96c2d096a536f3655fae2e12a28b7c323d8e97168c4a184478c326d37a444f78e6cbc64c941edaaa3ff48468beaa006e027003809b141e94705481408423c8eab11112659a03335fd5a7ca549c8942ff4351168f7366cd96688ca7c51e18b8faf4494fed19e23a484a01e3399ab650171861500775d04a7f88a53fadb222330d1a43a524da514abb4f63ad72c3d5fd17ce70756f0f25427520ed675cf52214323325f2fad32a23f5c9f50303582fb3070470a4451f1048d40dbdd01650035bc8eb719285d469526b8f64d515fa2133cf4a89b4cab8723f69eae914d2d5afd70332e77c3fd322daf4e80be0486372ada74563cd63078955a41d74d42a6dd42a479d037952ff036d0860152a26e416ea20fa12546725ca15365fe7bcaff9d218993a611d9e3a33270a79523fe3856ac25097ada8beb61cd197167bf2487f7ba40b0b2bf4733edf39e73ca5a1a41f7b14733d7d69155268634543f12dd67a0dd4b166b0e73dfda5f4da6b298b8bf661059cfc7a542d66374cd19405b40a49fb28bd2a408dc77c225feef2b8b7b7ce7aef01203579495e00c7929cfd25ad22a777c41359c31ca9d693acbda18314dea03d33998d26c8a430a302288fe298b291425b9aa64aa9609048dadb238512d448da65d009744af35366dd59d657cb56ec87f800bc95aefb9559db039265f7da2ba50267adf691de37bdabee8b997419ae5a1bfbf5e2515b699e2c5d74b2cc32732d5d428294b294acdae2b45fd55bbcaa148ff2096d761860e69ce138249332d370a457753c268d07805e3c5d9a61ed396f386932665f0df84466912c7b6e669983008e76a645bb2f31d9fe9e40be2c1122178a6c7f59ec1da4dd22dbdb22b60ed2da21dbdb2339484b876c6f8dec1ba48d43b6b745960dd2ba21db5b229b0669d790eded903583b468c8f656c88e41dadb200b0669c590ed2d90ed82b4b73f560bd27221db5b1f2c487bdb63b590afeb2351acbd42b6b73c760af2757f248abdad82c542be2e9044b1b759647bbb63af90af1b2451ec6d14b2eda109f27585849e90edad8e74f1707bbb04f9ba4312c5de3221db1ab7b74890af4b2451ecad12b2bdc5912e93db5b2ae4eb1649147b5b45b6b737d245e3f69608f2758da435427edd238962235de1ad1d42b6b74090af5b44a258ef568a6c6f7f302451aea504c15c5e642fe84bb4840f6270c64d334c321e43468cd55dab8f9d5d97b2e4af928f9d5f977288212ee5000670298920e2521280009732478e4b99f3a52ca2884b59800218e0521a40c7a5d4b1e352eeb8943c7818d1e3081f3f2ea51197b2c7a53ce252fab8943fce09f10fdf9ff0f7117c7a2a751526790f3dbc06e6e126b8c66960938798c63d1cbeb1f7e3fe0f3cd2a12247464544434241403f3e3d3cb11d984e0ece8d4dcda521325520a811c916a4449d053833fd68676e85d91d193b94ed57051046875a5459dd0fdd1aa385b00aabb00a93919191911912454ab51a036ae91a64abd619b84055a100e36067706cddcc8aa4c1c2510bbb90cea02cb4332ed05a36fce00cb90716c6d0837aa84707c4d1ac4de9a6d7a2ac2853a940302bd2b470a4373fb00f7c04ee818dc03cf00eac031b008f1728db1700178133ce81098089c003c043e01776e1f1e28cf7e6dadc9af1d28c9688965fdcc3b4055317fa2259f62698c2601a2359f63cd8f7802911c9b217b1907982479a826cef83fd0f78a42fc8f636f048a318290db23d1018058f540723ed41b607001e2910b27d00f048a7c8f602c02325c248a9c8f641e0912221dbabe0912e21dbb7f0489b90ed71e0915e31522c463a85916a91ed67e0916261a45aa05da060c816bf66dc7566463573ac652a345f153651ec2918e4ebd2d0880148ba505d90af5b53f3856c4f7fa4cbc5edb5b1b11c16e4ebdedc64a147ba485ac8d7c5c1b9028f746d5390af9b9353859874552ce4ebeae864b1235d425c215f17068b024cbaba26c8d7ddd979828e74694b90af1b8b3121dbd31ce942827c5d9e8962cfa3846c4f716ecf44b13df64404f9ba3e53c8d7fd01827c5da01ec8d70d9a2856ba466aa42b4533235daafb1801480a7baa03f9ba423790af3b34512c0de4eb1245215fb768a25822d2b52ab98ccfb810e501910d8a66205d50d8bfeed144b1d9ed690ae4eb16992856baf0ede909e48b0ed923ea02ba826ccfc9c0313e03cb380ac3b80b8c3a875dfc78c64998fb8649af78bb100f431a344c4c6ad4e081871e7a10c593131f7cf8e1071b36582c2080404149490140000470238820627c854baec2ab9390347ef1131ef9874fbff83bbeefb07621f0ed4c769bfd26db9c30ac301a1a34686a4c4c6a6c6ad4b0b9e181871b9c1e7ac0c911c51c9d93131d980f3ec0767ef8612766c3468c87c5e2e90102881e1f1494df9f94a77cec0b0480a000dcfe0a0960e80651100700500082b200846e0c054194ed6f918a51eb0887102a452d231c1f3b0bf11742e0d1e6ec38700b8f5eceae8283c0e348ce7e030b008f3172f600e011e7ec00c0638d9c3d05a3e09195b303814700e4ec2c3c0691b3dbc0a31039fb0f781c40ceee031ee51539fb091e251639bb884739859cbd87ec3ce05162416a216737c1a3ec42ce4e038f120c397b884739869c5d4a966465c75893acecc79d64659f8141c9ca2e03a7242b7b0cac92acec25b844b2b2aff0252bbb0a9b485676122c4a56f614b62159d947708a64653fe11b9295fdc338242bfbc5434856760de730801192957dc33f242b3b09272159d939bc00c9caee022b2159d9517809c9ca0e03332159b26bd7afe6dcf6d29b88b354bdd0b49dd99351b2cce2084099ab36abd99c950be2b1ea0568ca6e767a9cf40ac463d50b49ca3348da986590ad59853cb1a74163b542a1f238b93ccea01665e9137bc942592a8c569848540570bc47f948babedbcfa090bed8539b56397dc4d298cc425bb486a306822a55165514c99672d66f7c2267a72c1546612c20bd51e1a8301612ab050976595c582c4f8bb6a74f6c0fe489fd9158f49999d1ce4ca151012e417545b637349265dfe1a11b20647b5bc4e844aa21cbc3052cde041512404b049b857646b22c15c0ec23c8c917505015b02a88e4ee06e810f51178b254a94e9fe0967d9e8e0aab3badd2425da48b05d265833903a9d2cdd72d328d0033a5983cc8b688cc59d61d09ea82ba70b13a09afea4956f7a4d5950209245a3a373b39164672d23912c98a44fa7d255dcc2f4e427a21e423bd5e4aba5f7df6ec25cf47adf519cdc695910f39fd74d41ba6480603936545eae5c9d48da450972a1f4e7272883e8d7cb894e787bf0f7b1fee3ecc7d78fb30e9c3da87b30fdf1ffbe13a61686ee2c3fde1995d84365aec146e20928b6e829bb44f91ae2b4e961fa98a0660c2428b2c650691edb99abd66dcb7118f074937c1ed53824f105c68bc1859d6c89264e547549dec9ee1baa39a70190633874b791291aceddfd19739ecf97899c3dd4d9739ccd1172e73781bda3287493ba4cc61ad67b4cce1acc8fde1b0c5c175c248d686696ec208c9da9e753691ac2d4637c12914e3a5c5c6e9569e0e4a5beccc69c0dbdb29a40b0db60c52a59bafb6d922db9ea1831b6c0ccf47f7accbb2ac6bd7cc54c28da30165fb9c6ccf5d3c360e65a127b60668c667a6c5588b4c18294bdfe421d1bfa0d7a37f6b0d7760f50ae9eadb580e52d266febafb3d835a6ccbc323c489ca7a228fabd6421ba3692160e6243eb60f908fcd7300242c02423b134a31891f884a758050f6090b12a1ec13966cefed4c8b344e00fbab9f90fa201ec8f76302987dfb68676adf503bb3d23e8a3b569a66632d5a3b53334f905994b55f4f4785656b5f6175a755b6ecdd924504f1c0769407e434fb998f7ed6cfb295903a855ab4e3079eb01fe2e9c87ec39598692d616c5a658b90add827dd41305550b6dc8eede22a5936f7b9ecca99bde6ac9fd9278c6812488193269cc5ad01976c4fe574a9dd35e4bacafebc1d34cbb07ea727f61a2c064a117c8f5c4e99c01cf4c4a22e9918395d9ee3f0ccdcccd67e5300e9b96b613872ac4da552a89f503fa17e4abda65e53afe74cdd0453ffac006f3d75586aa8d3ce497dec25e4532a95ea9b542a15a29ac69af634fd691ad474c8d3d19d4e2a16108b1aae92452b461d75eeeb3c8f476cc64eb0eeee2f0616a5b891ae48a7554c1446c59f5a0d468b813daa9f078943a873249d9594d2143d13f335250a2503089eb48c76335634a750fa531f418d14a1660c3563a88f2654ac847a095542a15097a814eaa9af5c7a3c50e14a09f5540915ae504fa14ea1094f9816659f9c4ecd723a9d4e1f456f1b674c87258763364bb31c86ca9c8efa486f90ed5140503fc1c45019e992a81075c24ecc8c0a03aa0ea8f4cb36b6491032440300000000000315000030100c068462d1602c909551660714800e8ea0505e529d48b4204691103286208300000c0000000000800100bed418b07f358df66c33a4ad9c81b2c03720895d4dbcb5fe4e9164a4ab296484c07957c2d8320b44e39fdc522a1b4a3177e5234aa3434fcec69c8d767a7cd61362d530b2d83aedeaa67a5354f412a27871c315b4dad6fc92cfc5f716b30e80188c1f8a6749a98ba5a50a96b5c7e7ef97812c19ce5ede2714b01976197396af328f05438d210bddd940649f9cc4772c5da6ecf39208d2ec155ecdf263506584b6dc4925897c47cbb2491a209a5fffb2ecdb1ccff6d09c6564e574368bf1cbb2f2ea080bfe2a7933f884d925e7dff794c7b71180f2b04e38fd9ed30050c8bdc583ad411aad07685a1888622461702e68fec751cf8bf82476aaee26dbd298d5b4061ac7b2dbd729352f920c89f3b6ebb281a826b080be092e9589ec625f371abd75bbb6240802d2962ecc978b589cd4dfd68ee28357ba400bcfd6c3af8c763ecf62a330d2dca19e096aa547d961a46da7d337d0d54eef909e5c6acdce0e8a5a20910cd8fa7d305d762927b7b3ad6c57c659ad6898ce4a1869816158661cb865b55d601df1b25c9f3640c257a3beb9d0b87cf77c64d10ad4326e862cf637d5315533fd79629dd856f1bc7692676b6e8a863a49df07cd24d7aaa8cb6bc749f1f59f822866da4ff0b6f5b2e61f70e9c127bb14e52278af1d1286fb8292667c5fdd36a82dcabd98b9abf6ba323b7171c10f1022b5298d6c8472363792b0a8c7985aca3eae3d511682cca96f16750320e2001de3e944e00f14c6414fb0f597b3d50739ecb735a99fed8a1f07193ba4b4818b5e30c161df95ac082c3dab7ecb81bdd5692287a9ae73ecbf8713b9bb3b2b2d5f79d18c0600cd05c9b80c00e069aa18355769841daab2d9d4ad43ed6adf5ab2d6164ac7bd74bf15e920e162ecbead60a7209bc3c8a9b06533fdc97f049e8947ad3dee894d28e2f21810eeced30f7ab669e63b6a7b5d72f59b3d0ea6b3cc4f9b2e1516854b981f7327c43a385e79bbf642e9674ea7257706640e92633c4fcda59f9e440b5fe42b7a44a0a46a6e4faf6d06d77cab6abac055aa514a07fa5941fa522498ca5f217aa5c623db0dbcb0b253a350668f354f37e798380f19888e7b15d4d6f752a8da2ebfae85a7b403bd9231bb44b0685d1746cd3feed192b1601c42f069f96589a04c8b729d357744b29d25cdc954ec77495e49f53ab89db9d14f5ce33460ead4123d5a96426bb8b780ed581f9f9e69d85002b0cb9cd9bfafcd9fb26699f2d20fd264e45469d43169eb983ff140414499c388e0492ccc228f4c7b86013b459377e00b0438e1c19229024218cb8d1619d9aa251098119469c8be08d518b9254ba393e3aaf7050f83cce83899bf383159a32144a1ec7c4b782da672dd6ec068993bff97409f2e074cc368ed4e1c9368570ae48b768549a261d3983eeba0ad879c4d4ab1b21c1f35778a763943370a148bcaaf38262b7643371af598108e2f16e9289146d2456852c42bd2f47b941f173b450bb5f2bc9f235efccb98b60375a7e8c15a7239ed6aa8c93460b47fd02e7bebf2f0773cb4f23acf81a816b2680dc4a8977c56f1349a9edf571b565c2debe9db2d8a98ef886f3c81e1834bcfe56105d7700586b0ad692b5e57a68263787a1794597693c93144c4378ab87deebef90ade9029b517e8e49960deb84db46a663a72d6189fe18ecff64913cff26fde4616af0e5f8eb55039908c8b5598b1d9cbbfbe69478ad5227bc3e576f2b3e0c4ebee94593793c254d2db2162e106a987ea80daa7ec42ff8a97457bac2e317f026e6a4c59b0cdcd51e7163e619bd099e5735068a95af53daddb57c47142d75d302403520734c8f5660097405759290afeacea28043d9006d8aaa5f92cb0bb7959d5655600c717a794a70733a42d1e4a488d61f88ad1319eefb1999b8d9c6d984be840625784a4602689d8e06dc5b1ca08340729d267c59bca32427126bd237154d9cbbbb9cc696362b0dae4c9682de35ea7fd1309390df205bca2ff78f20c283d098a4109d8fc43ff1cd2a3731f5b84eb0561e132253720401af158544166b978733c7d9a948b600b52bc822768bb49aa8cfcd77201b448845e2d97eefc44486ab9ecce2f42af964b777e2224b55c76074f5e69b190f128e5b230388513964b776ce5950b943180592ed63cc4e79566c6fa7b2456f30ada123106147ac1e218c010b9a70502cb2e7b97d84e4d56d4aa872dbd8f4809e94e2cdcecc2bb15acfd84fdf6b20b0eb14c340dc23fb640df7dc4b2fb58628ecb2e0a2c21b994b5546b28dc39271612465dc0978fc192b422afcab8223355595dcee2cd9d74dab794cbdb70db75c9e443e2212b4f1090e539483a4be3dff506db38894027b550277741477a414f5613285d1f251db885ab3d22e7f98381bcddf0e23b73ed6600975741dcd435c85cdb6742f1380e05a6c10d039bdd393b0343b5814e56af094639e1c33a0b2cb88e799184092c3221d90a8abca3dc7fb4133a42f4d0a0825c404443b14ba588e3b78b696c8a0a41128ab50f6d9286f0413fa7d82fe50073f1461072f55580166eeb0ad111b7cfed79bc997d5af65f32ff149e9d426a4eec4cded14aaca696c25b5977ccce3f0fbfa67a36b55d3bf171dda4e74e174807f27052c9350b4ec7c75c21a422e611e7aa53f225c5a745d2224ab68103783b422ca7737aa24ab3cc6e96a67b44b6486edfa0098fbde65b7d863e4118640ed1365a41fa5689bbc49c1ae13796723a21d56c32317e84a09e604306c7d87c1d25daef4bc1fae54c41529454c27ab409b0889c80d7d904e8fcd0f84d6c93970f9e47901657adfe6f73171e3cf7142cb4c7cf2018f707942c7532201f8a2895f6901ce65d22b6437afd33a31958f4b02e85616996b1cb8509b9248d9c2cdca9bd3702d6cea04ae8873e97c3645fb5befc35876441edbd19a8906aff60a29040174cd038fe035c837d13c7fcd4b29b398816db16af51aea3e799dee9894241c762a7fb04e968239024f8a7dbe7cd68607ff4e52d333633aa68153140f1282f4a659809206a847b5994212ce561243adabea58738004fb23c24dd86ac1858bafb611150019ebb2db0648eb9924af1b7d82ca58bac780d2d1c1097cae2a261019fd5da3d16b5e70df8603b19c6bfd36892b5127f70ac80bb73f6808dd5a39f7f811f6020790b2e1a092045cbd4c08ac54ab565397996eef39b558553e52514946ae6b87ab548ee46b59d0428b48d88e1823a557e3067ed6f92c5a01a27ce0c94c2d015a80813ee91b7952e31965f597cb9e8241a8ea7c7fc81385376ff06610e7dc2114a71794b1f2ac0c83584b77ba68dc56e3f1c4a92c3091598f4c4d394849d4556d0810fae8b6b5f2da7b5b39b1d9c78ca420a5651e100c29aa497b6bd4997891e5c353b7fc12037b8066ded73b9c9ef38fb07ab4ddcb4b43ff93651caecb3af24a18c17fcaeffc09506b542967d17838b4477e9a2e82036df2be6f616522d01c4d34464b0186dc3e4f5cce45abf2013aeef7cd89dcd6b37d8c463ceace01679f22fe8d31866fb15ff7679e7e8724686dea360f34942f8c1a6eb471ddaa3766e685cda864222a045102b4e2a869eb2ec8c7705530ecc14ccd57512fa694e1a3585b386246f0800c97fef8be9d1d92e389e679df9965cf7aac651b8aabf1e25de0b7d94165bc3743d5bd197752dac60f0a3a4989d97c4fb4b2f871fd1591476bf3f47ec07a19ed1d564b587d61231419a806c1e9fd0d057e0ecbea80a7edf204951233e6afb1f5cb13cb26f535e253e3dd2769654cc0b8a55f435fd7bbaefd49f12bfa7fa4eff29f973a2cfd4dfd2bf27fb4cff2df57337bfe573b4825d7530d14a75ec27cf37fdf1051f218be10b568f85a7e53da7315a6b7728f6da9236b3f03552afd002e7a9735a2a7c8185ac3127644424d7ca50e7cbfb21fd3cdb2d2c4f949535fc66c5e32dfa37c2d85afe07c3f2d76aff7dea4cac23ad6cdab6df3e7126d5d1561e6dcdbf1fd205876fcb9ad3be371ee2da63cb848d22546f09b4465893af69f21ff2a6a59fd9ce5c186ef07618dffe0ca109564ce1b2bd01e18def2741d63719003c482a4c0b0c28d5810a1fe69340105d101a639f68dea2a8462f15a9fb8cdb52cb99c0046ef533505d500d7d694080a6a67a34b9ff5a58b307407b9347f93790e6fe23d6d60c90922cfabc7d72241fee1350c457ebef21fb822ea2caa326bef6da6f5d433bd7e34d7b66b2f8c14f7b2ea2604fc24392f0b5c48ab69e8c67b7158b6f820d5923c3c5d94cdf62cf9feea733803db69df59673a0bf57b60d0fdc7a858f355a02115a212e4f343d9fc21863f06ffbee102d96b871c7848fa1cef057005232ee5409145c96196fa846138b120564ed4f8cbdb22bc3c329d9fde6d1236976672f938defd58ab81581a4b4669625e76448dc6f6514b66dddf555f43409820c474f97072a3c41e348ec2d1b42c7c391a1c08a5d01114395aaa723f6940b0dd4554ec1729d3b0be4be1e49e64f41996e3d1ba4e01b49329c4a027a130288ac4297a31aac7f28f1f0e024872cc93756dba29d544e9ccf9534750dcd9ca2c28f602e9fa32482d1309c7cf8881325551905bda3f3530e7b204b4c9a5b5897260c52ac705b3c33844412ef566c6e0c0cb4ac665b1fa10085637f8a30a5fc0d1980ee52ece005fb6280764bee74574299f983647b7bc14807227f1bc7d4df3c31fbbda8f5933d73c4ed8819c741658487ae453d0edddd9d249d5dee1dd7102ab5ad8275f7e5a0b5270e4315110963322e05dd7f8251d83fcd373c34c43af0ed2a61401b4b930dd2e9857b47721906c090ba3ab22abb532147be7a0cd711a7bdc56c52085979a1155f2112bb93af35fc4c0d9aa36a0c9b71a575a7557066ca362f1244806ede18acf74e5d25175134643432a3b5d77769b6d5a70f871466fc6ed5df4d57deeb92c56e77f33caa3d4d422cb9e8d6f6efacbb7113f78d13dd49c16288e33b4d5c8e2d20f194172b2d0da47fb37592fe368e682c4080fbf0a01b23107744b9d3abafc9d3bae40b8d42bc01442ed2ffe21b91f5641b221c1c11af5dbfb186f4edff015122273d32574c521e156e809423ac001afda372998fff8f02f99a4698ce0083a593161bf818a3af5601e03ff6b6f0d17f9669b21581ef8775a648e09885dbe3b3dd2d8dec27495d05ba5e32d965a98a478aa56c1074c944c093c75845eecce61609274ec7d78d0c811842523ff85567fa46963816e3bc4d985da9198a95c1f63bc62779c2b43c9d7aab9e74a36b310f2b304dd0b12710f4febca7a652330364e77dff2855a08c785003cf23545ac8ad0036b2c64befd28c4aa9d8b0bc8cfa72fc4a780805e28b15079516c2f1722ac6728dc0db3f7200b34e8bf307d7e3241ab4d690bd2bdf136de75276162fe5c2178ad3ec4c3296cd7ce0a5d47bd3f780fb49804d8ab37d596b7e6cfb40fef5a351dffac1d15f6dbae8a56eb1a89ebe324a4bbb307aa3ed138dd02fc8b72e7e31ca75269a3dd72a481931dcc33623970a5fa89789bb29bf3234e3c6a383101fcf8e70c26a0cb4d4f0ba00a1f87908122cc591e448f7b9a08e83e6127997350803d11354ee01aff09276eb49af724de6252d638c9ac9f9be10f624bdafcd90797eb2ccc79e490fd48c8cfa2a3c1dd3a09de2c84d5e0ab5c4ad4db095b49c90e6b6dfe77a54e4d5b43f868729f508e5daf71cae7ac11a2afc35d237be733200575dfe0309f3474bb35944ddd579e2f91ed58d62fb99d159950124bcb12ed6878a6ed66d432a7a63b33e68888b2188132165864937f55f388cde0f7ad376eac701c42608a293613b76e1cb886cc30eac0212124b9bcd722503c40dd386c42a0ddfa565a6ac6d281c0c9a9d41da26efe220d3bb86b94e9c577b219b2f82b1ed2870fba4fb88fa8b63613225f5debea36467011e48997209622d7a2708d0a10eeb620bdad3acd80524f93cbc8e8c5186308fe62bb4686590fa043f13b14e595d2b8df46d0d0df8e82e66556e43cb2394ab24253ba5ed86888d78670024bcbfc280709165d8e8165ebbdac68bfbfa2b1474ac8b7b33282e0882982951ff70d12d2d2abe2be2d4d4a3de1344640fd580a0c127aa26475cad6fcf530dda734632fd8b773048d6fd2a3103244b2ebc2726dd5ccea21e5d2d185cc322930bb2c294c366c95f4b9e2e2a06611f5185cdac8c29e07e0d0f53340043d1f65edcaa890ebfbf8912163d0d5c0e1c8ec3f0ba8674b1bde970c84bd5c9670a68b976f9e8f5929594afeeff07cc212945048eb1ab969b2174a50b8eac2e461203d64d9336f846061d339e2d105f82934922b144a91db115f627845d253e9b6de9d130cb65b5f07ece911c809ef70334fb695bbcc24416ffaf310dd0435046252e070a8e615accacb39d2121109432295ab608aa8df1b31020a015c9de2e8386353375d8005b6c88bd4aa66eb5c2e9ebb26ad1e50d68587014386d4a0f21febce4e8cd30222e9845ddd369432d8b6d966783a4beabd32593e55a898b3496633f7ae470fa2365db19251f790458b839164afdb2fc7ebee1f35965097fc645f9fd453d11363d7140de974a281e5516ecabc2875ec6fcd3a9bb46c7fa467a6fa012841d515a242508e37494103d5020f361b88366aefcb88149c464fc88679db08cce16c1127028118f5953089ab451be3016d8cf60c87786f8d7081ef61fd47161543bafe7e2b24f8c5bbfb645bd48baa56480b6deacf9e4b9f0c432ce938cadaba32cf7799b555093bf65788c2bbb774b04f8e7c67a26fa3fa77f0cd5bcb8b4dedae244ac99003fe36ac3310ceed8e756334144e6445222f52cc596761346c728454bf14c0848bcd5609fff82647830c6904c2483a920fe4e0e92fb56af181417eeeb3b8b817c4edfccbd4fedebac185843068477c8a099c84a2499644a5cded1f7b58c8ebd811a46bf53d6e7cd3af84c057b15b72c07e2d9bfdda2c64611644cda9ad9534309ede8146e56fdfce1993731440fa23bc382b9ac39cbf565eebcda93e272b39892829b9156a596caa45e5bc1e917950abc16a32b060627cdbbb6301bfc484f981ab0d119c78b03a56b1688f0a18bd6143cc2929705f6a284ab7f56c88b7cf06abcd1b013ea0fbfae5f101f3f3870eb86f733df0846d206fea24c9f028f9be1bdfc46c3bd6675e8d1b17fb0f620fe07144439fb906f86b8d753d8b429e777100500540011c0ccd26d0071e9e01d99254cac2266c02c067494e296a312ef584464793de54c6fa1d21d480bc3a00a785dd8dbdb64f43d37591837cf2397604789c88ff0b7e5a8387c3c69c3f20af99bd46600cc0a9c3b9e8e4380fd3ac96452319d6effcfd2290146bb75669871e56e4e9ad1ccc6579e17ebfe1279dcf654701606056f8da7c14826902304af64569623160c3240d8cb45c62a460bd69da576a66d03a688e0e453daa0e65fc20b719e9f19a5a669c41637e34db7ffe658954209eb00645b163abf6ceaa65677eed1aa3d4090ddafa969442222a8b299c0410a7fd59ccd5ae1721b69fcc6a9d19aa0f9b9eacf24ddbf80d3f49a5fbb3ccb263a466cd237a2d40ca5514d09fd9c435a779ac9d06580b4e754181eec6e4e50e5ec5731549946faa233efcfe495de2be9b73d8ae583e1d8abb3b16f5bfafd108fef466a939868a5d928f2dd2464bc203085f96a3f627ff0f4804fe49e9023979b5722fa72ec949f60301d4af016768e2a18db7d55ae90f60359dc7542074c7c01bda84cdb15359db40216d88811432c205401dc8d571243ae2975b5ed2bc3963f36f0bc4fa15d5cc191acea6b1bbce7b1037dd313bc94c288a5d70aae31ef9b121aef803706b7f3df2c15ee359d667533b240163843bed1bf797f23aae99defc52e57e3e886e11a0523b6c6066fce8812e81231534b33cd353b8df26de3909bcdea5230e28c9996cacbf653c7d98765707c53b0cc78a0f38d9750ee608b4f0bd04da37b3d42aade982f62497290844f1479f4770b3341202097f514ad5592b722e079855e1b9dab9de0550aa1c63b3cb2d1ac68af3fc6823f15824efad860e855b189a427be115d57f6f28cbf24691a8b457fdee1dc773d78735bd0a525d1f1cd1ad872a62f3a7171cc26cd02f54692925d267998a80d30d647f580909f48f2eb920a89788f1ff443ca83d63681e168c1ca629844d020d1558aed816272ee96b10cace4795d3a8c5b82f1478aea0dfe89085d41e971cc76847f1aa32cc35ad8349d0a150c44d381f84c3ecf28609b05c99b0c60220245a7041fa902d1fd6c71f23b5e794158ca331fde2642934d6ffd2efc86b1ec4b7e2f1bc579abeebb28d56f5868eb545a0e20486d1e78d42f9b041eff1110b385298798047076be1f93e1cd8e5073015ee7b6bc781ee110ed870047cbff808736c290c27bb1357809432aa40d40b24a55832f1f51254af309d22d7fdab4b28f5dcff79df52f95ad8a336f38c8850533360ec01188ccd42d2071fa1ba2eab59bef9fe835e7cf72e58ac598ed7e5f3c086d6210146ee1e7a45f4bd31bd7c71dc95d6ec3908eef954f293835340171fe01fe141b80d33993b0727819f32d6405a534a78b099aee86bccaedfa6df5c20c32991a0dabb1c57ea77a0152dea83f740f8654eba2c76a87f3fc198147620d340b6b4347ad7b7f5f4dd6d695d023839f399ae78e9f8c80724e675a148c8f8d4bb84665ed494e479def63c774488ac5c8f187e023a1343daae3efd7c3de3700791f09e590c70c832a941448f438c42be3b6d42f74b298f77ef2e159ec5b6090d5ce423df71c06bb9aa20b363a1c4fddc5bd7325a2139f336a521f695a35e7bf060e3c205a5a223415a19a9680a76c1f96f84abcd61f69a4d4949f484bbee082724bcbaf50c6e0ed27b5531ee4ba2a6fc983b0fae7f74e268e81fb521275fa5ae7c5dab610cb2c0d98e5c857b37dd3f43506b02c1cb31f4ee930e8c42a8fed4cce9f32e2970714289641ae65b7b2b36be51383a63fcd4c69273d622f048095e99c087274fd636b3da14d51b537093b91e6f3273472c6658ffdd844af9c096cf34e900336ad08cd3f6c17635f466b4fd1a25bb1237b4bf832035c2fea36e3f335485c116dc4221b4e4d641d6a26e8c930ef9280bd0773110f83b2b5c69c787d2d2a1e20c5b825bf73d630bcbc3f8ab0074a1b6973860c56915def1651d70bcd1d0004ee9d1018fb88352766d86a3d86eb7bc7c12b6863519c6ce8428e0a54c80213dc3c4cea00a5209b72fcd79aabc9b0c9b85422970d9284a62bf26e7ba38e9f4aa64bc03252bb358b7c174fafcd88ae2f84132ea12dc5a5766726d8c0e14324b50093a594ea4220fae54459aa27c9fd89b4b046f957acd332d8f1100adc76e3d29dbe4759f69f9b66b7c4011f23dc1ada38fe861690901c448de43158d45fa09c4ce9120b030d7e8ec5b0617e0cbbd5440538193466aa49b9cccd8ff3d030241a850c4cd14f26a03bed662167aa24a100e989e634ff389b8febafd53c515c58744d5aa6c72e2d82e9f344c6f840cb662a8eff5fd92d1d7488241079c767c5e83544d3caacc52e30160cb8cbd5b621dc916824f14e811a00a793f9d97ee292f3d404c57911ad338603a9bd7df9e2540012523eebb0e5687f3620fe75acac518b75b99c427c0360f286c565bbad2272af3c0d5bb28d74954fc05071f3a85b474e6fe3076f4650aacfe0803fb9d5c52d61b1d82ca826189663168717dcb6d0c5c1d1c50b52884f73237515e6d4ceb058db90301f0029269f095ccf443f6e94d81f22b937d1a62184470e4c8394380a5a92df5996b2564b6e7ea0c533013aab059fa9ff599d09413c0cff3b893bce29b9680aa5b51fbd61714f23401578ba4d5ecddff9fc04ecb711cbce0a1fa7725becb06e13f5b4816a9789f1d0ae1622df5265006fd251ba67ba16f0c21bb056eb40dbac3f5685d27d3e3a4d30067cad94a2419cd84ac85b9480aad3991acd56e2f4541ae7c8b0d65fd14f98675efe4ab23a87dd3c8022cc0b704561fd79ef3bd74d1fafbe8156233cfbded177215cb12a580c53d5a8b4bf6af144fd1f8eb439bdca9964d98be081f97305365186c3b91119fc9614de7c1913ebe3797bc6d610602d5cde0e09097e4697bd668e51f049a422358709c0ec079bfff735d8e89db40e3cd02d887b2062299f1c7eb672397fe0280119e74d5291f314123d8ab924c61238b8e728fb9b157a5aa46482c24b19ca328bf78482627cdff79ec8f7737af32990b6bc52319703d3253bac404a7604ab07ffac050dba28ef928d681e84cb5eb9b70eafa393e06f48fb24047be5bd123d7aec193ae8507a6cba0eab083cbcc12fee2d5bc9cc4c422e0a42a4cf0534999bfc0dbc744daf3af90395fab09cde5f8b20a8cb7621ae9acf35df5512c6506779838bd7c125aa0548f7a613f4d9d8fdc35a6dfe8fc1e38c02fe2d0866c69ab211bd260de3e1e6aba6503886ea1fd62f8a761cf256110ec399613509f001c6147fe8de893e88dab2e018294f89bba12af0fb9e6241c12d8c2a935f35795ddb4064f4e353503e7b81bfb902932effbdd5d66de136bcb1b4022c2d2e6bf357b7e960e20732af3812c3ab3b20468fa302e80e995e8a40daad0ec9053d945ebb18043af0d1c669eab2889f9fbc3b382bbdb45eeacbf39f0be38ae9b3d788d396edd37915da563dcd46b4052caf0bd6b22cc0e4cda6a169d3cd915da6c498184e43bebb339d1c4ca06b0e5231028328fded950734811ac2e71cf0e72af76ddf78d89207571fba3f4e541128014b13016dcb40ab5ebd95827439963142ca9b07cc5758e08b059947416d96b572c5923715ea36615c80da85dbfa381cd3c8647d32cf0874c32c9e4f3b15eade7376e564b7a58c5c13998d1614e6d6d2a05670d51f9058cf8516b23da098c69c407c834ab17a6fc512d463fbf5c268e3080648daef3ff53a59343073791939a52bafd76159593d2be66f6503e562ef5f048cd9f5a973acec5c329ae0ec862ec4dffab4311c4ddbe56440b06d230503cd2d66bdfe749d1b52bb56e8110de207265a4bb98eab6fc89ef577110139fe6232b7d7f30076108c85e2d8d2ba15f2d237bd792163c8f3799136d93a3df183726ba9a552aad294a6f44a29955295a634a5575aa994ae14a528add24aa774a52a45e94de9b995884e6e00ac9353adc99125a4895c71776a0a6349eae48d296d6f5b22f2cd88609402a0e58ef5b47b40b5839aff70a6eb50b3323f7a218ce27c868459ccd6d0081c8cb74fcd38707f713742c53bfc99e82271cbbcc04de4913333e63a683d8549bcd8e1ff7e0819bff158d17a7165f0de241930a899487d286c1c6c96bd996ae4df31376aafd2667fe9ca2d6d9c9d39c859c2d79a605482b76605b1c91276db6aeaf7403be29d44c4936016109bbcd65a88d95a42a952267e79fea3b644366ac66b2b5a0bb4a44d201c5b2196f358c5fa0d60fb8d66f6b0f300c55a8daac191b79e0d4ae3ae3978af1ee960f3f9f5b5bffe3eb08bd27c1ba7fdc5c677c9e93b7e749128c8f3838ebfa5540c22f5f06b73fc3749edff3f99134481f96f51490fa29dfab70ee6ef12f97ffe193c8882daafc8c4b8bff1c6f0bdcc69c0f5a016e04b4784200a36bfd62143106ddadee063f72ad5ec7ba24e0c9bf505cd4d4282a8f44917d9fcd75841972e28cc8474ec20aacd7f20f1c120dab3d7aa378b17b323e0d01b3d288123544310a9e55fe6a52c88d4cd0925da8653fdfd45e1b7ef29cd491f4582c893f82beacf06d19299576ba198ef3e88c03ea2dd5e81c364045132d5a7c93da84e040ca25dffd38ef5573487341e661005f559882050106d859df3c4c150894c101d4fb7d87fcdb89685362419f8050ee2a57216a18ea4396ad744dd19449859b24add8df65e6aad67f6594f0f222f87fb28b400e04d10c105bf3b88bc497a8d8c46f5ee470447992e2742f31473065190adb3a0a8866ac6a122778843c8ebccf32431d4db50881616026b101d8f5d443584a8c00daa1e22eb778ddbf764aee5eca11325fbaf9b2a47df8636fa12e943a10b21f11ee96d392b9e72a63ae090ddf896f7346df9f21fb745af3ba9f42254f529a9f8d3903b79d31576b957d27dcee21c1e1d75c5d7bbfb079b46bef4edaeb7bca769cb97ffb82d7add49a517a1aa4f49c59f86dcc99baeb0cbbd92ee7316e7f0e8a82bbededd3fc086a80e6278da14e45d500369ef384a61a2ad32d48e4667dbeeb68fe30b093207326c2524cd8826bbfa2694fdd5f05a0c6f3ec25d5d8564c7132435f4668f95bfccec21f306b33c153095bb58e6dd8dee3c18fe230837567d2db487e0062765cdd374d324bfb7b47c886cc109cba32fa69f14b33c37af477b4ae5a2aa7cc01446b1d00f1a2329c7dbb830312f63b4a8b4126aa85e35aa845bdf6255c01c6bd5e9552136cef4a2d2a19574ced87141a3d36cfb5b0e6fa4da59ff5c0ef75926c08ed12114bcc52d6a43f63b6f91ed60a64f6c43579bbb3258d9cc61056f10aa0222600ec649896e7999db1b921925137af762c09651ce5445189018df19ef663c52c4249c3b53aa0606544e355cfd5f4047adb25e4202fbeee9df70c84c91059038985a220948727ff9f2abd921047007c8080ccdbd1b7523c7924e0b40453f220b90823a13402ab95b3f3a2a0e2306e0401a0721d68e0012f12f8a819ca90048e8707ed4725009209bf7f063c6a191ff33c0e320c932eaff2af89162a054a2dd7fac8b3b4912bbb49cffcdad031186fe1a3388a257b3ff57fcf5f8a09790fe22d50e7b0fd8cd54feeb7490751feffddffc175d21cb69b2fd6b30df677bec884053feb5b350454f42ff97d97722037ad3fcb4a222ef8346fcfdc1b070892cfccd84f9e2e9737939f081305108328f3fba3fa47ada8fd182d63bc933fa99635244af88c47e9c16389950b6110d212f0dca871a2cd1adf415294ba35fec368e68e35b5ac681afc70638df5967716f0c63fe57060db741e3a9fcacff4b2adf7c637d0811ad494e066c228b2c1f12096ed517d0241ed48cc3cfe0df39425fdb9e61a4c1480a3c4f0cf5da7b37f8ab953a50f933cb90d92f5d9f169160ba1afeb21d9f89d2286aeb2f3b4f00c344dfb603106b53c26d71e8de9a0e3c21e88cd2ad8c74ec2731558f6e73fd04d07a8883f11c4f735d174495d84ab5d75ad88dde57961d8aa041e6b6446652fb8ce13d3b2b4a69a83822c40b27934dd53b7b7ad9f510289ed1ee0a4ed81341f5f282378d99c403887c45549e5ec681b4b8822ffa89d63cbcd0a7c56cf100c29e08aa177df943f1e1c5248356e5051d6cf4a2c465d30b0208d1ab43b0c9a7d91f5dd7e331e48ac77d2ce480ad21826a5948b37040fc25d9ec91eafd607e6ddc10d2c10210a9dc874c9eb688c6368b119232488b97e1ce93669347072eed96066f0e13d225b8060d303ae182918bda55ad55e9795a23bb693461f3d5b812fceb7b8679efa961695fd4face87f6862685697d3e8f7d83eee93bd6eb93fbe5b5d4982f7ef70922f84d5c9998c579dece02db6840f8f298c8ad747572e8435b7f5b2626308ab640a6944a820d8026e19847a7f6a19672a611103dfd219c9a5630c36218b85f2a84464da1a66c69df13b65b0a44420f45c832f0756b490388d83a96b40e3b5abc8fc5ff03a637a065a73d94c8c86c715e3afcaaee1fa265f52a2fa11d21bf621a7359e3cdfce88128afd57aa8c8c8e850574a10ddb4ff41135234f4b8170074e44081df69131278fe2ab3cfbcf8c4e0f0a45e22bf3da5705de7ac3031810dde011c77415522524cc8684a1b8527220d1cef917b639f326edf49f4adef6af425d76860d115b0dd2274ed1b323c53a5603bc74d123fa733192a8cae791dd01fac0feaa627a76d52b7b8735023b63f616af0b3fbbe04b562e20f60cca28a7c85896aa0c5f86181ec6e09fa082ac4c2f9487a5164a8b59a97dba99221309380131698be45c2f9707657ca954f698ae0c233027ae2f677512710bcaa146bcad17f592f274b5e3344b9c052e488c3d7b54b1d2966a16d3178a70c90cba80a7a9e5f9542eb9c19d0beb771e101e7d7df0b333d00ca00b19065c703152d06ae318b8b68689b669f27f0925d70000dc5ae1d4fc7d137eb2b80d3403646b0d52b8fb5cccb7413284e46a7a235f646561e903d5a0b388a59badf1d1218d53bc5171aefbb1a6158411fdbc90e2e022f0f1368bf7b9cfe46918788860b4e53a78310c2339af78111fa27b228b24d9f4a59f8f2f3014dc0290a43c259aa6f5c7c7c04ab0df65583f150095ee5c6f75e829869024d31be7d47e2a20484ff29a7b0249179eaf84ea59d845d04afd013d031b6a1820bc4d3e2cfc9e5dc15e92a98a72ebece95fa440dd804b5b4b5e8b3f76011cc39f17fa17e4f35dcd85dfc50df16fa6190b92483fee39d5a18d660e90bf28c03212b8d6402f01fd462d287f4a0668e93a8a6641aacf9caf20ccfc8acc00bf1e8b234b48d43576df7c8e31936559fbe7a2c39104cfe80d89265b72a79b0cf836d9dabdf7de33f329e481eddde7aa80733becae19107460bd78272f1c5af59500551a3bdcaa1b0f2ed507686f1bd8c804c85d1ed8e909e5ec2ff352421a15c420e6d7354b7f02b8c3a006ba0a6e69c4f9260dee9be0c8c0eac0027bf49779fc4e9d301639f64f01cc0358faf265d89d04119f3ec7e8ffef183f764df4692d3c08d20234ca94dc21dfac48af85bb59e9b83d6a5d56f836c937e00b2a1a21a6bb8475a6cf91e127fb540a9159430fb146b24fa426c28966d6aec4e29649f95f208b5a7185b535939a5900d2b9513ca96a26ca6c8768652b6ac205f880d45b24d8ddd3995ecb24c81dd1995ecb34c89d50985ac24bb27dca60ac835842c756429adb22a1c2a06d767c8d090d62798c0ba700bbd41033ddcbffbb9b1d2ddd77db8b772ec87bdb95b09ec937ddc59b9ecc75edc5a39f6c3bedcac1cf6cb3eeeac54f6610f6ead0cfb606f6e560afbad77b8b3526e56c4ad95dc60bccb8d95b86f25f7accc4de61d6e5ac91d2b736f056e8ad68b676b1c1b564d03fc51d5d8b1e372aba55def6fadbaeeaa235bb7b612d18a06e86aaf5e6abfdaf0479709b1852d369e57e56ad34b378a3e772ac43732cacc476041e9aa17b744066e9222a75e8d029a0b6cd3ab9b423f373548d5ab53855942015086a31fdefe1dd7d3e3c46879e15bb263cffdeaae6e7b7563ece5aa2aa9da758b70672840d7bdfa9987ddc98787abc19426a584c623bb52a7f50a3286a2236a7941babc30251c377ee8c5f97622235fbcc7cb5b9db20de40c208924ebd657e37a484261cc51e6ef435caa11fa8642a2366a98d2a8f44fdc9696c2d24080fc5304df47803acb6fd6cde1f1deaf625edc42dfb09279b3562150e68ddc9dc4e3758fddab5c87b2518529e66c1114e7fc8c9ee822f43830483436ceccc7a067bb03d4722cbba5db674b0a2f173ab86af90b05bcb670bd284a9e961c8b692848b5da17ce7ef241f2c2ea707d43beb1eaa491dc8b86d17dc4955ac4608ca8f9c2bdf7c1d4120b41feac7237be44d1afe940eab1f08c377e56afd464e7f641cf5f8fabb74a903daa04a41a4c4f841fb99a95607bb1c33be2df973739d6b009ac24fd62ce9ee010b6bb71c28f3909659d809cf5ccc17cd9a1a258c83d06ef6caa1b954d62f455d80fd4c2ea7f01a00f814e2e8c17c56bb0713931dd5a89cbfc9137641916e4c5c2635d574352584a3fdd6374cc580466c171b08cb29268e2e42134079c6317ccb8c2bc35b4bf1c46ed75ad03172c424c3584c96593be93888ae6459962bf430c0ca184c4373d4ec4547e9944338cef3831491b57ca0bc03bf8cefc599e9ca1864306e046d39d4d601921cabedde83649730cf1ec6fc60b729db31403d08b01fa05600d744988eb6649c0fcc1127f550a8c968075d47a6e5dbefe4007662c93e85934fb1e31497b40069fc267f13d810dba0198cef82ccacea131cdfdf5448eb44bfcfdc630a11172dddfe8c0d0a4a937b19f320feb2a9b4c54a73543acec86e4fecc7567f402cc2c8f0141163064416c470c06f48c290480e04a2b6e79abf0c93c17aa9d29d7e877da539d9886e20b28c856b4700079b1003d4c71f271e168c9c79509e4a444863ac2e300daf0072e80c7e7930c25d51074480c96351694c442572e99b515b8b43046a9158f0a5333f30604599a93451ad05ac031f3acf88e3db879c39da49c6ec68a22a864c8ec672415b10f0e8e2c426d5b2b49ca92725f1f88eac07968e0f490ca69509b4e003bf333883a91672a1c2029c5ac9321df5b2227704d2a01a6cdcf29e294b5a93a0c94d293da3bdb246787cad32d02f2b91e34dd69aa7e60702f2b32798f7240cf146f3be300886e739d5f724a921303c22d9306ad4d13fae5ad3a33ebc69b6ada9c5bfa17343881364e2c805e6a0f2830c9507ec46cc0b67d1952a6cc1d3e7888cafecce37680b1f7a583c4ee2f0722eb1f1bc0bc3ae6936b054de0d5091e79a2fdb986cdb56de2f8cd7287c87a6fefcd815716740d07f4bc66edc92584b9ea53ec5a8cc0a3cc09bff815d535e654e66aafc78e095e911995e43ffa804977e44f181a5499a29c7ace3d252a1fc47c8f58a9d1a034446c1b0f4d1c9ca2f2b15f47e20b75b58b2b602e0e28fd67e4b74cd60d6bbea728128dc2eb3be050a9377f8f2a3f1fe2527355a5bf2c76c96348d96465fb2e8ba3677b4ef115c6f66a28339dfc3f99f5726324e6c11413ba4d17b514195a1f5713049d12c8fe2a73cf9cfefaf3fb391528b2f0e1ea726dcabc3d0adae3de1c2d9b77d608c1bf9f7133b00208f71a162c97fb37c824f5627bfe847138b26444d345297f637b2a7ec3bcadaa181f0b69cccdded093d9bf446ac872c8e1bada757bc0bec1fbf1121d0b5e0265cbca501bfcf8999bda5395e64b981b043aad87f75bb33485bbf4ac291161da3cdbea5f868da2f16b66e9e13cebca39ca0a4597bd0b975b9e389d6f880f92ff04d627c3297e61b055bd18f5d7df3cdbc709880bb56465d5b7d2bad82d7bd8af5a9f40f2ef7372df6c6782c40e4a099193c21cfb7ae4221667167f27f9304f3a2bbf9bbb561b687b7ee9d918200168b9fd37615e06bf1fe342936212854ed0e739d940c9f428d760c102b2a8e63590a4ea557ad1a31828daaa8f34b3fbccedf9b3170c67091fe2b48b32933335468d1aaccf9ed0cdad9fc886db1baacf95df1f73b160a4272ff2f19645f7bedf1d9872828dd858816aa79a712290d07903f90ef0e3c7e0fa9e47f267e5b49e9751797d35a8f7a09ef850408835a7d5a03a849a3e2f1ec5f73d0e05d83c93dae3efa7e7aa1a71a5b3c09d06c55a9bc6d4492ba5fe3cd1193921b9e2b2dd132a01d6b4cf63c4a115829f0bf398253ba57ac85f2010442e6563f114868fa661d7921b063773e511213ce062bcdb8960702179a5296479cc155da0dc1684c4c71d2a23cc4daec7f25054daa78c175ac75c0a0a3da66fbdb244f1d175e85dc42ad7ef376865582f04e4f25c3b0b319d2e3ec5c47313b8824cebc8351b19aeb5268dc9bb3c277bd2e61a09981f3cbe46de4a1d4e1148b3711912c480015ed6a77f5da7b5a614833ff70258521610209fe8fa06203449909e00965fa6b11be497ac33bf238e4c4433e4e70b67d8e265129094a058843381505f7e20f766ff2bded63252a42edc633af74788ac10045dafc8c28ce0379ffde30f1326d36e5fd751e6bdb537a50375385e876c43f7e9b30e2983f0132d1d5230cb4fe71c5266c75fa939a49355247ce750e1f99f45bcb7a09065a9468f45b7a542c9e5487b8596218f997c1b56f326331bbf7d24880bb478c1f328a54f8b04da217e7b8a5bdd9f8eca856bf39b0b9bdc34c4ea161bf3ba4a3416482173fd96493d9a98c7228b1e4261a62172c3cce0cdc6564d1e484537db92edebb47e0fdb20ef1ccfa777be405d416dcde9c40a4a31b2933680ab4e9a9b2d1be3a899cd0ffea25b92127032a1e9d50cef46493cbfe2440562451b901037f109099e6d88b4261b96a4b3ad73d4cab2892cd111b6ae4777e3e660862ebea5663b24511876ebe80a298a5ccba853f493933bd3bb59a46fb773513b8b97f182bcd4e00e81bbbe2e12af97cec5224756523d42c85b4d9f746d06f02cd6c7fee79c9f7435d32d41a5c222ef43351cb24488c0b7dec0d149e9767162a990df68e289efcfd75e6b680224b8f453bc19c2535516a80bff4a661010a355dcd094320b868d40aa722c7f5ad3d19efbcffe129eb6b507757e0c1fa83aad407ac2c33d5e82670788f4eb0a8df96deea54dac7a9fef8389f0af103de3ed3cdbb4c3d38a6430b534bc5272afe4ff8f79a2faa0e14763883dbce65ad2fdad6a8b12d759ea7b2a009a485e59500b0e120b0ab1b6a9e980d175cdc33e30188595b21cb01aa6c78859e102d98006b7433c1fe95a75d5e03af58f6f87912dfc9f4bae59bff36736481fabedae077f2f43705fe801040037b823caf537db8bc628b1512f46583282c92b6dccd8bc8c2849c0e96fbb80a616e8abc5c397dee0794c29f3178155d26eb61c5925fb4ebee46a8acb4a0bd410e9a91b50f2c2706ad72cf9351906194716e9567bc81541423aa829e3b225cca3ee8b71292a42612d601dc5a7dc17ba349081bff1e6df820ce12c1c0eedbefd79c05a67a5e6aa990d8ef4c70adf1933939cb622c12cf34a4773cf783a8616fdb8f90e19c190d3a6f2b0f3d2d79862905b4e4d1369f624fb1fb38f60dc387c1a56049b93dcadf2244d784fe63ec1051f0e45ed571b7812ddac0aac50fb65f501ee5663aff452cec4f01dbf9f9d2e2932aefe73d49cd4f754836e4a7e91f60a00f20405ff0b10eb8b6524e689d6bedbd5aff0bac21e02b1e848c8ff96430ace9f76ac6604bf0a56fa0cf8638e6288d3727e6e13c58cae0fbc1ab1ce330941042c0aa87860b9d00a3f405f9e52634477e207b37d11410d4d18ebe301b735abea59fdbd7ae04fc3b8bd2aa94f72dc71a0656e03a417503f0853284752049b32143d8d8fef789b0a24cfe106f0e40ea818d4815ad0a51b104a16e599dd9124f81d570470075fa79c19486cdd640a5a30cfba60b8b2ccffdc41e5860d0474006dbb9ba35f48f05db0dda5a9ed4290e294c16c1897f4962f11a69e9c80e169c7c7fe5e192a01a180a3b416527c130011b0b2c7859ebceaa2d4bc8e6181ccbc25370b63085f52703eb0502c6061568d300ff8b4c70e46fed9fa1c54c80c677105ccd83c8ac565fd1e74af3ee965cb9e53a4016f9a4e8fd4c7a54353dcda63f713af149e256e3dfaaad469c63a0a58795f44ee54b234a00abc96948d1246ac7a2f697e473951cd830408b280a507ba8fabc0fc40b6b946905307d0e3018712e2129e28611d971a74357e0fb0490a084629bf4268b488df9edbd2b352ea2c07cb52e253c62d55a9650caa6bbf9b8b6b4b653468616092839b8f5e086be06b1c8b6dd0846d44166e5e55e9e0180da7db9d94c855272ad238c4597d7389e990c03275e610362477466c3ecf401b047336f61e7b02d11207f4e920c9a55e7f616e8a474a51389bf9f8d781ae03f5d3811d67c733e538b0afa982a03745cb685fd6fb2cdf4a9f48ee9878858701e0bb2ee3edf66038e854d44abc1d44992e900ac6dbc67a239b2904342644248e760d3c6cbcf7954e8f0d25d6f869deb2a611d7b8b3843fa630dadb1c184862f0881b0d0b7b3d469551f4127c233de961ff17e0608c3209980879e5a09883f06e35811c747ebf00b67e0c32dec7b65df8171bce5e52f619cc1d373bbd3774e4013fcda377fe15607515f8ac9544f81f7a52ad25eda0c726855298e626efa6bf42aed25c2a0c7668bdc082de1c2a325a6423a0ebf5ce693754581c1e58ed48ba116d0b68c63a6410b39eadd0b1f907c5cd461edca5f41812214f3100c0bb6527c149b7f03b147008b02569409e3c3c184efbbf8c3b1d0c5c14c5e250fcac72b3ba33ca48f957e5ce45fdc2a92b4864e1b3dccdd0d57c762e83992ea5ef88275b1b4c46882d0e3226a640da132cd51da004a0667607638774a64654829cc64ea2f1c8ebac6f7de0ad1dd9402cdd83883ca69d65d596f9710344dcbf3f7d964723c05ca37512884e98bd16fba0e7c20881b09b341894f9077603cd7393d387c851fa56aa0e42e49e3c7acad967f270c7d613eda55cdf6d6bfbe24e510fab2318540c1f157f38ecf22833b13ab9bb1278136e080639caa7ac5ee7d3d503cf23bf4ebd3b2be17b1f696540619b23df8026fc2a315806b178c5d1bebec2e162dba4a8c9f9125dc0269d1b5b6abcc3e902f82d6c91fca498f8bfc82392eed16f54c6ee1560079a27f9a179138e8b857a1dd8d4f875a43980a45a58983023c9d8abb807b168742472e54e03039903f96546f1bb710ab6e4c24955212ecd5ded05522eef1ad8fe84972f8a738a878598808ab6760218fa3772c0c69e20288971ede9e562050de3b09026d59b6cf77fd64ce687bdc396124b63836bc9dcb3c542fc5934b80c90b954a71c47dea7e55decb096f0db658e67d51f559ae7bb1b48540485b8161ceb28dfe403e85bf5888b4636f60138aceabd6a5ee77acd3f8462675d87c3bdf9ce23405cbec618e113a79dc4d678241452c35d52ef239ccfa6767bffdb12739ce1f90ae366fbb0fe201ed6c36cb610c69cda8edeb739f4f6d6a67b406016c93bb0830df922c242d46ffdd5a253677f608829781e5745756770aecfee3388f3e8a9c2843f5e9618a1f31c0cbdb1f4b2539dd0ac53d8277052fadf24b0f41a9cb9b1341639c779dd00e6a33472bc011ca134d97b7f1d406ff80a2733474f0313776b2de779ea78ee280a5b522586485004d6f9d659ab1e0e2c68f9153ed2cad76d276d46a6bed149e49553db8b7f628ac09b8fcc2c92eb580aad8ca00904e260218cc4095ed28e4ca56934d69d6ba0851938e723b36cada03b0b45432e0519e53195fd7a0a7a0ba7481e172b5efd3e9a60cce4050699d55b44fa3d6eb608c64cd1ad8cdaf493c12ecac98b21e446c56784b74b97128c62f8eb80192f8f6d52f8a2481521e63a9c2c298778ff5702dc41775b40237cb86bf444271e68bc60dbb1ead2cb2a5fb00253b8de5dd94b6a22ce89866c1a1c857a79abe9d0a281422d70587afd65bda63e08f7575d16036ea27179679013d462abadd6beb848cd9d6a288d01a38d759234500dae295df1f026c10156b99c90053ef08bc557a440d5d513e5271489b1376259284b40659e40318ee0ff0394dfdad931c5f6e91f9a1b7abb698fe23f1233d40eb911f238451adecc2575c547b097d5a7232d7380bf2cf52d10dfda80dd9b46a6e4982422f14612eced42c1fe686020e62c9ca1e80b1c38cd190b78c29aca0a66476ef110f13f4b6e463e762640b3e78f431d0d4aa27798281e115a93c8455468f6cc6a096bbaa22b337c67a1749360feaae045ce0b2cbb73c3963048a0c6270431aa3c72c74dff9db5658b8637abe7792c7d13c4d87be70ad26305b4701f0b32d87ad2972b1d69b8fa92599499f0e5a96825bb4da13f1c07008edb44c0d5b1569025eb6d9f59cfb590c197468727b3c105c09bfdb5f1dde6931ed75d140919a2b6fe7da23d4a57cfa3cc421d501d3a3ff64fda043e4662217f55da35fc427db60c3bb03b53938e251268cdbdb6882dfaa828c078c80762673dd216516941543b0dd4f1eca90b3856bf59d563cde9d32a00d1876fb88bbfe13b362774ebb263482f5633a272653f37d8672aa8f17e6b277780adeab2ceb4ff2fa8d0f5b4e7dc15e82f0c090df320de3ca6e8f200943765078846638027271437dcc5195704e87ae611943dd1b50e0edb4bca4ac071ab6b714631c289a06314f5a09d6ec8cc4e0a9706a2004a545e941367e6b8ed22dc9d856f0ac8230c98d8efeb18371f5dbd1aad849fa0b6e147d6f0b4ccb2420ee0d5906fd86639c2c8d222b3a3fec758d50d4ff2b8b331b04b74ce58c9de6c2bfa33d2da94dd573fd7ac03ecde02f69a3dad15281541e5f5dc4eadf998091c13cb0a2502b189b921d7c26adeb3239f3b634754fd5bb15116a6973d6ce6ef26f68000b7c3ad2183ddcbaf5c38493e1168376834efa2743619e14042771746f4ac3dc002cb74dcb1b111227793de00c30fb49b0a551395fb2dba3a7923efb9c71894e6920f5bef8644dc4cce2d7b1c114bade4eb50f842097b9d156933a5fc66790a4f0139e907010748e251dabc7b7d4a9e49eb5eaeeb660bad27bd74da1b4709e1cec5988d9d1fc3a044fe0a3476118e9c223df7a380046b1756321de87f0b7faa4e0e60c7adb6fe101bf96ac431663cc85d2947f0961e6d342f1a262478aa46f2100449c7a1989e0a1d1ca26ae7fb24e46ca738d5dd3e6ae053871a5c46d4ce1e479db55d2cc8f598bbbc97676656620d3a88baa9c4510f7bff2a457b28b0d4bc1ee15100ca39c1622ba51a4cd0d3b33872a013f8d6489418202fb442db528b4220d80dae64759deb39794c9d9567f1da9291230a7a3e23701b0df40b9120f3c3ce54f14c0c4b77c688d2c7628507afd9923590e1058d6e84e0219ade155238c826885146151be243826e808dee776e6a793984000e763697a4c5fdfff0705598d7ed933c970856720c4a9f790316c7eba011d01349c1cb26ac4f8bbcf8f1ab52b4bc1007f156cdb51d1db5b0917ff7818e09d60c92225290e702d97f25b0923778dac8e008e88252d888ece76927b1e9bb94e23828879b6e2d43e7a29a215aee68e5605e19ba18ac3e636b8b9ab81d3432944979955923e440a32d4ce42dc54427f00350d6546639481618ece98cc8bab02593d9411b76a0509efd1d26da011db98500e3d1f3c7b71c8d8378ce60eb2d3114cf5e00df437dae6aa16465dbdb1e71d6491a9f3fb57559e72f806da93ba715498a96fe385782031cb8684276ce528ffe5a34fbed7af93bd1c4a3711e7394532645b2162694700c764a1e526885618b34f3903d499c0393d486bcdbea11424db71f1c17d2eb0f7347f8190c44f260108ab33d62b80e510b0bfe1bc8f3aaf9dcd2a063b6a7263098e9d7528e3a3913685efdfcf2d1220315804fa63bef2d3f8fec3e48820124959248bb8edf8e8a245e13bb269d249774d4679f93a2d0579704ec47e6c58e8022e38cf81aa4f2b095a0168a26150ed1baa733c82e64086bc71fec74aa8ae122565f0e7b0a62bf43d59792fd30cf7ec67dcd5e714f7d0dcdbefff1e5dfef16d607d06bb73a2fb852fd826c8e3cd9b24df2c7d895247d38cd7917715c07e1b3f4be00b6645710af3220c875d1c8879498cf9dcaca69732b869e99090a226fc47fbbdf96a57d39bd922a6ebe209d354ecd13fb5b5dc2da16e7e4a8ae17a0cac236cbccd871bac5cb88e747be1b5af6abcc64eae63c735393b8ecc4edc73939ed4283f18fe25d4df36d9ce953efca89ee84e64f9f240a15a8dd4201bc423ebaf45182d72a131d23c7264e85d49aeeb5978ca568060bcbb0106c7d034629d6c1b7e4e4aa200a0510a43a1bd512d9ee3a9da25ad35c448ae63ca6e73e8a31b2f3806c92193e495c0c9118e748e0704c9985fa66dc8910aac5526c659b7ae97b61f83436390379841903e46880a155eb99f0f2eaf883d868aafd69f267c78497e1f88b78fd57930e02568e8280d33c3985a2c08fe7642cf2f15b5860d25606e8aea47ac7e193975736a4d1a738bc1d19172b5071f341a50b989dfc7a52dcf1b65f415c479471c80d9cc1dc02c6c839e48169753a9f200fc7c2150ea5c184275d978c4d37029aeb55482ba427853646ad427668eefcfb34ad2d0c6664f20a1017a2d747664ff2b96742944eee655ed3e6f62ca03db0959db14934ba6cf1a2b459dcc28aba00663cf11617999f00ec13dcdc14d784fc2d745f919e721f11a0d8e2e37a5f4a7f6a845c3115c514df815bb8f804b059007261d24abb41b0f9a775a7e8953631ccf02b0b111e98039dff592647f1255b9d9948ee1864a94001a4e4ca146bc21450b99b9a491959e3a370606976406ac3940080205744440d3ae7339294fcdcda966b9deeeac1ac2588967842662f663975213d019780cb69d51903013de9eb02f55ea901786c648cc86e46fbeab2b0d5edaa64ab2c654ee4e70159e7d2ff1275103ea3ad3bf284eed14cfa73be5b8496bf75a1ab6198d0b63dd981d17c4d616f4d4789d9aa083bd5c9d61cdc0f143307cca787e73224dde069005d3d120ee7cded38ec3069d9cea01e60914c9bc81831631bd1fe59841d13a6e6f273d7f56681e48fa48576f56cee314766e2cec1523cae8a2a6317a36835df7b82c304c46720a49c5288ecf87e4d94939b2dafe3916b59e28831b8852dfaf9eb9b8deef17bace1ed823eb6037cae7319a4d7ce99918de70890f4fefcfc2774046dfe9008637b5620150086368c4329bc20ed2836be30f2b1acc43cd5b19d0b4a36ab0113b312897f87e09a3d18bda557f43883e8b3c555859757853e2be69b2c9b4ee316e70dd8cbb2b115559aac95039f58ab390cc6689bc8019abceeff2b05a4d388a635f5f60fd9b82636a18059a9cec9eb01419f0c7c19054b8df1904cf009462583d3c38c253b5a329a9293297c57ef0e0803b1d3d67d5065a8ee3c0a91d620763a328641918b52eb1483b6d1f5f70dccefdb821f9806eb946b607391b97efdf66377ffbee9c80b76cf2fced498c9f35b798aacfd18fff3a2a8b09842dbf35a5a16e01bf30a937b9be3e78279fc65b29997ef4aec2d87b31b24f6add28db69fd1145f997434231df521291a8bbb7e90dab2252f92612abd1bc3ac88eadcd7a5d3d3b185d0167f34821401637f843af9d765e262e25560e392007cc41ec0618a0edb1b70110be047697a906741d3be5f3ff76c456d48cfb63780a0dd709d57381f5a39c3a351c1c4e24428708071a64a3b38a6ce3dff6d4e2c2980171d12a376115cb623f45edd5cb5833b7d03c3b1e1669341cc497d4c0f8f30cfb328df587d94a30ee19900ff707244e43920473485302244f8810c66a0b8a4cee8cf5337306df3a1f4962be8cb0f3524736897751d17679e12d3780f73d806b41a8661951f8ab561dc3402aecc28f279cfe6232239abe20c4894a21909d8e4d01811aa25031b28e55ce8bf30a0d9cb81a3451328d6d63858f4beb6b0fcda0ae0e05786ef95855b244134ad87f33bb07ec0f3b3c8278fd8fcb6c0da469ea8782fb9b79ce928f0c0a4fa025752289299f0004c0072d0a8c74056e479e6ecc379a18d5b3b6f3d59488ab26a8e77781faa0c868829b3d710b88cf2d4bbdd0b37950a811c69e12e686b9b26e05330362f8c496306f0727cb71108e06a87c416a5f2c6f30c3ce80b1032a95391de69b748e2083889f38cfb5eda1ae499393323e60e04d7fb19cc75629629ce4dd2add8142ba16f7ed1721c251122e9275102393552b4235659a1f69733bc1048c9a63a819d0aeec14a8c6b2bba0aaf39a138a90efcbce2248288c08502f4e875ba97c91ec148e8e5339064673b27a055175f7c6cc0169a271559d97642b22dd31f9c1c36fbc070ea3251814980245782117aa37189b239d3f9207a16e674ef20736db283fe737db087531035026d5f8b838cf5584a0fd0661430f8016258177c86606248df2e76a8dc81f5b51faba1a4e91fbc125004c6592033e1472c4301f659bd6de84048e9cf615e47e8cd42dac8cfa732ba4e3ae64941907014bd78948b6abff77535bbe6873c2c5a5b538ee136ca4aba09d2544a7ff7def6dd407fcd1072fdbe86a00ac0584d22b6b8a6f056aa60f33684711b7b99db78b8516ca8f25f1d36e0fdd8063a4b8aab8a0a0d4dbc64904daa39ba54bc79abf0053bd4b5776122f4bd44c53fd5b65489d24e1fa387450e669b5a0be05fa76d097683f61c03e1f3b6a0f384a958c9f49f50b3d76d8314776890e05c53085a6d331baf41488def63288896e83113d89f301dfd029d5f9d0e955d070557e69e0e5eb6aec7964dc983c300330b61b1654952207fa041c5360dafeff89f1c1d1182734a1a41d00d4a049c6f153c7886ffa417f1f43b30e7bfebe773def3a2b172479bb1d916d0401e103880a6cf0a26cc38595ca16a9b037bdc995137bc5895f3903dc5e9c31dab64aecf721dd195f5aace2ea8a8624384895a0a06781f674341a6ca6ff0f36703e67aa243504d14c074321e23e7dce90270c0ebf337657071c9334a7f3c0e2d713ee46ca5e59c61917ffe1bb898565a6487088e89302ebd3199520d0096bff7ab23a508252ceaeaf20ef1b4bbadea69eb72b8161aa86b0d3c86c146463cee3a5b0c79f720fc35cccd90130b12d49bda83a3bad8758c4b01514db8108f9322c66cd74e14e0e2fdb7c08af65a6956fa6e40e949114aa35c7ac768e250626dd1717314f0e14a682218f87b97a1220c8b5212b9ad0559ba8773c1989f66b7933ab9f55086d61ce96ec0ce8422e18de764a0902e06a0a7f59f11fb613bd4535fd4460fae71f5651bdd637e84359d0663f3025dfd7f74753cc04946937a11848fe5f057d04a23c12b50b5d99ac0984f5e2399b3f9c9c10907e1795ee64899a8d4337f721cc61957a7d5c48796019026735ea2e1d760a498e20b09d775f90a9c41051f482d0fbdee50c63f4209f14452cd8e1b59ac6f0c8b1aeae1f8ed2001994f2073eb7c126c7419677447a924f38a2375fce4cff1b8f823be0d78e94f37c6c57c5cda87e0f71e9bb991760d0ee4fb23fe1172a3ac3760952da83b448e2dbdcc857fea6d785032da3e1a50634bded279803a2229cd2c8af4ef33c3e5739994298b2e99e4e5066b4349f9d8ff757ab328acd842a4dc9529cd896df54c2d22e97fc67a9e5fb1d880725173e8be4048cb8f5cf593d761ef5ed8fb9c742bd9a0ff0de84942643212f933dcd979df97df55ae4ff9cf1b3bcf75c9363097632900219749d80546c35e1ebf5c00b7ccd000a1e1647af94ef05c185d896b916e2dfc1b3dfd0e8049410cea9f8ba145b9aea37ec1cc45b36fb88713c75301a5df2c5c53df0a1cf1a09b8bb8fe5557e90f86d98390bec6598d46ac5e3a19ced0c8ae3fa11252d9cb90fadbad4fc50e792eccb9c1896b9eb40e9f3c1c5485fea475ca6696503802cf43a15e1a629877faf6c7e97feca563364c8ebf830fea6bb6a7904247492231e965d8582f1bd3127540cb3d0548de6026a85a282bcc9de5802991d78a1b3b026a74432640d2bf08820546239dddf4689c6fb9b576f4c17eef015c4b9faa1de7becaade87389e102f4c1b38fedfbf81d56cb73ca51cea15ac37835b17755cf43096b25fc04cca3a1bb56019e84112dfdd5afd45163351012f54b94f139400666b5b02e517636576cfe34124bcb6922b5809ad582e6fba8e1bdc85e8c8e1eb946f3387370118c1f253bf7437a4a9262f2f7c830fcdd6549b82d78f1f96c1108d7882cfb872ea19c0d5969d898df1e3b4bc3d9990e28d522e5107ff001766bb221e0f634ab5a1b02a410835df1645e6375e0c87a69a846b88884e28c21dec3f1c7088719b6f5bea0ab5db126c5f00ab0701a4e642bf2452f465d6cf926ba34294c28f3ea29aa3006a5685c6ae531b602d7305b8b0325bb12b25a2748e080cdc78a71713a314badeeef6021f4b6f0875fb91a54cb3d054504c2314e5dc425a4249c19ce6000afb840046d885bb28f5a1af80c661647509bd3aaa3c5effc09b1f6fbf7ffce16dc1a52867d90497a84749c3f9d608fc676f309487f3eea7e5a332d7b631a1544f2c1fcb436850f380b127822ee72a7c40fd199ea682a654253f869317d97636f111f35f17c3517cec6874b1952dc168db5764e8b59032860a4b22b74d264d3192099549b70d8c5984aa695dfac6bc77ccb6e5a0e47d5dc6971be592b918ad254f60292693d379527892bd9dc5c85ba797c2f03503524d82e83799b016b6320f81689cf4e34103299e4d7dc1612ed3f885857b14b4eb5c9a33eb1bcffe5038ae245c115480d343e9b602cff2daafb6a2a1bab580d281daa9006a1ca59ea02e87434456b0f1f5588ca4e4f64dfbe1f12aa884f831601ee0d236d9ffb17694a27a43840e4e3ca97b22e34b414719a48ac08798ce763a900961d31c30f6d8694d47a0393f81ef0065f7e591455da8815b4c0835bf56d9214750f718e3aa92b7fa0a59b6a0e2afdfa168fb55a1893537bcc6089b66d0f5d9981ae73b1a775108a8edb40ecd9b9d54c3f8a7a3b1a3cfc2a9ab4765a771bf0955648fa66b926f050e5f80600d1728644147b418216cc3e9fe687911969e63f2b85e7ab44527118fb62507d0652bf754f82eb5bd47a9e572f035ebd1f01cefafd812d704de7206dfece6d44eae2a3aeddbe7b5950521e409a42e7ef1f96ea910515c8ba1cc88976ac816c191722c03b53964318c2bcb3a61129a720b16a10907a94811840aedcb513eb8d6a233cf99293e5d5eabad7aa1226f10e661ec997aca55e2eadddab4da61b7b12d1da513415fa2dfb4e603da1024d7dece08b13527a1bcbd79679a945035dd942d68d2ea019f54de43a073bac043a3b8662c0209c285f6575f684435e30c38a44fc5b2e0bb4ed32a3127d33808053e63379772c4ff0e4ead9d3b404da6ef4dac257aebd471968453d8186fa4139e3c57d1ea83dfe3388d23c41515b76f7a2636d5bf8799df0e98abd14d6b32eb3766df734aa127ca66cf77f75bd7122ac6cce3856d0c63f59122e53a056cb72466e296b5649b036969f460821b624cfddca0a6085ebe093ae71368b0eec6e06b6d405ab18c45297708f052ff5b22d7f713a0ee5cffe684e1e40004729d6567fa362bb41c75f7211a877c5ec091920cb62417a61990b16a811d2067a72f113d7b930db5fcf766f05e3c1f91a73bdf1b8da9cc16b640cd13ed2c11a742ae58c903a8c95ac3ec65235866c41ad5247d9fc97c7d200486eeef58c014160c06c5d6df4bf676e284b54f420afb1c2f6584ec8bdfaedd22358f2e0d7e8331974de6ab4e79e79105de81ecae3e261ef8c70293b4497cd478a9ccdff711633c2fd336d6943e967e7761565cad7b0a28b3f42c564a758d0dccc4620673edc633ba731434bd760015efc41defed8c00830929cdd6fce7fe24c29a80976641f60bab8fc9f7df4e201f6c5244a227ba234e31f452590b9bf017b670c5c17d889073c688ccf7ad265e57a118f22a0d8720888638e14fb57308fcbb64fe232ce1aa0d4e15ba18e1c1fa1f78f491dfb440f2875da3dc5b8b637584b2444eeadb26f508950c46995f0bda377e929393930897f046593bb559a535825652aa97d030aaa20d187d7e43403856e3013847a5cec0912bc02edf04a38ac7d0d4ef70a1d1630e55fc2415129fade4c47e10941001bbf962ac21b1e39eb9a68c2e75a396e267b15ca042b37bd9634c8292d0020b17e8470e6f67a134694d8859c97963f1b2508f8d015a15bd1d870a903382ed8c6730ab4c020e25c3c284754ce08d770067b824ce19c827085bf821d25152272074c226981b2a8813e751cd013829b9c9224bd11b113e3e82506946765e02798b99a352c2d9803cee05df40b4f012f400624952075849dad402ee6235a039c10a86f72e173bc83306656d8265230c85b3e5002c126ed3691d64ae680b9b1ac763dd46cb7fbed39bb83f5995526ceec53735a39936e4104687326f485f96286109215d7bcd71c9d6f30bfc7e6a609fb059d87ea32b8c0a166608c46bea7101f95e10a9647ec18414410979fb3296741ed994b131d0596ca54573f21c774449f425cc72a938f8d8c9460491b55e9ac892cc47c07b8e23f50181e77377774c441fb5e85359a107cebb372ce23d10b1f46316f3697958370b3c2faff2b9f1bb10ca115648bbde778fd992994077961e1e66e8a5497b1044a9579de34cc90c1bade9a03e5a9bfc0779aca5a8028a0d53ce5cf4f5e253b6c3ade4c3f4ecb0bd2b5660fab3b115eea0a3e06b564ddb6d37cb503f00f7259182222f6cfd10de8c38054571d232d863789cfa60f29348c3c9cab2121f43c22b810db877560df0b6c751ae800ed1990d327196dc7435286ff7d3f8d2bdf75c259e8a75dd4a27137ae0aaed7eb9ab74fdf9c58386a4935ab85476b5c6d22da326559c0ab621d0d18abc4c3c4eaa9ce011ef223db6f9ffbbfe8a1ed2b12dbae92f53802a4cbaf1c72144ddaefde6af8c22d93f0ea3609fda6da5de04d2660b49cb698f57efa83ff2992a79de76d6298d555bb7957fefd13257fa18b0677e7153df1d444ec57b8bd8b3d1f4d40938615275306ede222b5520e41a819cd4ae323662c1649a5ffca0aa0e1374404a14b66977d962ed315f805959bedc39b61a697514281345624d4c8333c1571fb39f73caa045a58f2378d5c754cca03b9ed5c189132ca0b4d33abb3a9d70b6db38c2febb51d60dee8881222d040c6a957944da8195b12076e2c20c47f68bdb1901734f116ea07306c87f5f5ac0f99b9e895af67a9054033a79f043903a523ef8f45297c498ca8f952d087ecf92cf7d61bf921c5da16334b6a0b6844760453cf7db18aeff98259c4688dd31b6e56cee99398a33dc4c32616771088cea63da383129a5fe9eb72683f4d40c887128ff64e01c07c58475527aa018a2dc90fdf2fb5ad91047f4b38a79936cd54f991438e7c142e24777c017f8f151a5888d5762d9d3218acbf3df206d310ff37dea37a077395f1b7692b3c250e97ae1c43d462c40f03f98e8beb1e910c717cdce032ac3eeb4f529afcd68f44044deb9a32c1f7c5ba2e52e2c974dd268742a89cdc28e47060f5bbfbb0262df3420ec1324815ab18d82373d669e5ab697a6e5e72a4cdc052134deaca25a5bc4cb69ede3a5912354f0b411b461e105f229d72600c6183c506e6b4345f5c0534a996ea76bad10928c6de65a837230c669766e6bbbcf1ac50ba62e7cd2a548f23a37e4ba3a8a76fde009c6c5cd95b2db8969a6d16b00314c9ee6c20aa9b41021b3b56c8c90b4416bd69c2665f4c8cd96ad971a2df29f9eccf943933b0e8d3303cc3c9204d8c9a74986e7097ba0b6d8e9cbdc5614090c4cbfe08407fd8358bca723b8607f835d8fdcfff0eb5426e838e1f4b52971d56f07d9d9ae9798e433956def8b20248927377743f00d5d22d89b1c5581da43f1b5239b5145e0009afecfe5157fe03384f9e72f14b3a23efed4a63e167ea33780263c044d896c4bd22af94bb32259879290e6ceb1803357c3b90a05c2f2f5dbbeafe1f384e0f296d9336b172e48ab8c0708a262c27ed7394549d45599d93c1fd3b28d1be0770800d2beda2ece65cfa23343287568f9f4f70ddf7a70a67b253ea5c0d7a6b49bb7fca049b4bdfc2931171d02c5306f69be225c263530e5480594f0c5ecede13b5549641a3795541a522718bd2da12087e732e08ca3fd433e2a1cc02d49613951552b432ef6e718988ff1c90e8a5ec817d24e98105a229c69d7ce6394c276a341973dc961a5140d48ee335f58c663939fe179eb5d791403345a2ae56608618a32ebdafeba462a4dea0523d090acff8b7a8e9eec299245e2ebaf5b0d242e5ccbc02ebe6b22a34ce2cd4509f7eda46761fe7776f725394784b132efbd587aad9ec13868c2bfcc1d9450c25cdfc822c086c1a09355bc4ac565e01c0b2df4a822951fcb2d55cef43363a98dcd200153c2a16460d04f78b91a7d29f38cf9dae2ff369b80cd5517e425939df4ea3bf42f738d1600b7aa033d00125be557d8c6fd76dec5dbe0e5bbda597222087799fbb5df0e0f0b9e27c059dd27180f9c4104d68b4d90a8e862116fbfa45fd2cdfddc2ae09da86f51a041cbb6d6b42265976065f8c23402472fd923762d18190215d6b670d21317af635a6451239f19d24926874fbe2cc41d7b6c323d99af45a961f63a2438bbf5df05d21909f92459bec35fcf791e22f0460309d835597e2ba48bd5305020fb93fe5368164a8aeebe3099d83d9d03be6388a216df0cab8b222340ec513bf7cc1a27b868524c08f680e0e450665799d6c7684046d7bd5b8902e5f98ae321464a4a697401cc22a29f303d6e97b31974215a02dcc8c643e3313c90090e856d2961c25eb64bfd8f1f0006f1b3d693cb899a162520c60f8abbdd63bf2b21a834939cbbf027a75afd860e8de1c2592a5f453fc3a5cac3573c672532249cabc257da507f0a80cc2b24594d249873cb6a6cd9eabd76384e5b1c82105c1286e0992b0bec8336439ca02dc1b6d051c4de526c8e48f918d2efb9d56c9d9cb4ce7d4dd0d1b7cdee8c068118fbec889be8055a9057720c36520af453ca49a318eb08cb150817fa5a1d4298944f31d974471da6e7ca13bb37abbb3a08dc8994385306f781f1cb01b10ca5dc5cea2b343232cf970a4ee99aaa3fcb5643736bc8a3b34290872f31dc473a9c9eac31c87af20d6e71b2142ca577366ecbfc39fea6d450d971cd25998857cba9e59ae034d49b2f4213259976ddbb89b69a3164524fec3ff3f58905048cffc8f3f1527518d2add127c74f82d6c6e1907231bbec662ff8436ff50278472e164cd7d48251a6be05db95fdcda9a1d7321f46cd8db549e22d830ba7e24f3a4de74791597f68b38d8d4980a482c9827d009dbed5c95c67f0e7107e30a213739c5108a9e5cdf3c3089937bd418f4c3d67c482a0712c8d2332724d44a964b081012d3486e669354a0556992c3b842aed9c90113b0201ee1021ec6216cc08038800d581843c383766260166e800bbb19e295e5558c43d461491aa121f8c2c9667af3f51d5399b6efae32ae0556d9e4160cd6459f92c2ac60a3657332ebe082aefead63014bd95757c3d724a81bc00f66fe6fe51f9cebb59f3b6338a3aa091106be54e74af27d59944478e54898556725187a80e05a292d30c82447b9bebea127290c3c3777e168c9b20b95ed79f3451ea7024dff5723c3ba33ed20e29042ced342452a659b006e7a808931cf034be30981874bb615b5df28e1dc812a093a5337953a5d0886a5d27c6abb86d70498b0b42c1c187a371d32ae646481a2cf0ad4f3d358828b4e96d14457c9196f31d6c02a3535ce87d75565494c68a0517741269328c1b27ec402d475a6d15d9d80d4ceb84367c25b5492def889f733647c3ec92ee6fb6d68b1ca752f1bfa17b353b4be383bb43687a775d08a70d6af9a79e95b5a404466a3b88dca60fd9af1b04e2796ab68517f186748a01745c66c23db4ac9aef592e23507995d09644a12a53613278d081e10d71d181e3285f8143dc366a00a77c8cd2a3155593e16be0e745d9074a5c59ed7308e81f02b048806d2c210c350f7406f4dea75e11586e8ff19e2a47795d73c6c3bca60f7b9ae4c3db6a792bf1a2544bf734488d3bc444723459570d4d3b9dd5422fbdcefc7a2c421eecf28734f3196f41b89095ddf73a75c448aa392493c35af64c5d20228dbb45ce35bca17781c7c8620873e6a2cb212405e2f756905691c20018c44169b037cb6d1cc1ebe191cb528a3543874e096d7f2f5d6c7aea05cea0a405d3c687119008219baa6534a227f45af7ec4e90245b1b535290be0026ed5f3039b167ef3bdf9e3e554996e9d764db27c741fe65a88bc2e4488c123473ada883c733d962c3b04e66d7a9ddde021103016373ab032e3c0277068305709ba3c74472a6ac938ee63d37d73eceb76b9d75ec863db2d3e48c536721324b14fde8b93ff671c17872372bfb25bdf140da2e4aa29c1d3d8be01124218068b336339676a4516ac0a1469486649aa3821473c45ec1dbde4a990094ea2110c99ab1461373da5e2468a160ca4ad3a152144a04237861a2e772138ac9c0c999fd12bfc5feea02150815a004f7b6c5074ac672d985dfea69bd873ec20566ce45cf8ef86e31a33c1a7df84ae4d533aeb0a0b62c485b2446276f60e04c4797af7a696b54dabc51e9fc4938eaf98f0e9bfa6031f8ec893ba48218398d86b631e0474e25388fb8b8ee21528668428589f7da6852a2d7775093d71812ead9f8822909f1d1c71bff3f10b342a7b74f1d70303912a83f626b677fb2db907b6acc9dce78c8128de8866716f3b100bd3d18e749e3edb9e33032d440adcd70f46eabca259bfe992d725b6a71bf582458f85df33d0b2d306c56dd7936552dd85d603a16298482ae1200ea32a04c10269b39439a48108f24137808a6690fe7e516673e33ad28c4940b801cad174728a63020cfe48e5f6a1a4974e8570252882d422aa0da30622bf0e584104a1f4f4294f474af5a7ab3f9b3cc045ac888a1f14907ded21574383c8aad2d52cb04c6f33428398ce5b3f324338d6bf3a6d0d7bc9cd147273fceab9142f540534880e245f1f0ddf42e4f186062127ad1c0323f37d3c1268505075e33dade2a80043830ea59f111c63883abcd9de74a78c1ae9a3d27808491e4b7833eabccd83412372188b7c85ee3d37dcd0e4e4f2e54a98194eee1013fd2acd1c0e0db2866cfd90fbc8a1415df4555c0efa55047ff60960cc9dc1fd3f9a4c709df257a76c0819e7de2ba141d8782cc7214bc6a7c70cc9e4d8d5d8a8677ea1c01479ee8074f113bcf9dc19d510299f8877b636f11d4db75b33eeec846468d0ec513da1225c44173e26a59b24fd2a200514bf2b763a795efb5e1e32840619e5878dc063cf621aeae4c22410c84717e4cbdc3d1b2ff5b4c9e1a815880d28340855d0454b9e082820839506775e6810ad1c2dd2e317c1de3c5e8896a9689c15688c3c17cb4d83e08120f25893ca28867e0868104e0dd66aa04165a814ab186f8873360ef834c831a83f09de03c8726f3f32646f3316699ee4aed49ba28690ff759ad0a5a376083d7283c4d019e2f782702965cea3e6d0fb388a8c1851b46b5e0905044a855a4271388a53be84ce7351c460139a7f26ce60cd9cad78997896a0d0045fb09f10f5ed8509f02a154c7a887d96d012e676f25937ead5315bdf85141bc0221aef0375f0e1e26a7f1f9c8f468d66de0ee6dbd894451f0e1f4f26725e04d2a7ce655dd11dfd7792620c9f68dc50d5673c0bab1ad6677396c48991a409eb8aa2a2d9246ad5841318e0872bd47899f3a0313283a2cd095501d2436f1a8b35824095829c099e1b4655c400dbd50e05350cb03235d9d553bc473f44b7bbc6429e60fc0ca99af9ab1f0b5774bf828ba9098e8e12cdc6c1c9bcaa268bc14a62c2433b78a627e8252b54c0855cf2ab846b61224320375879be251eb63573ef5ada986d311fe5b57db3cfe51b44bbf152255f383484a8738097c22ddd0d78bc131293c6dd4941cd41d972703c0f49158d372fc0fa2f2891b94ac743594113f22121651102f0e818d4616132bbe4eaa1bba70afa318e32ebe323d81ef056fe6ba08c9550e504b51f540a3b6ae032da89afacf57dba28444ede50f8bea97cde7c5389f8635ead6b280b297ef380d47d4dcb9a9e371bad7daa04d18298b8795a778aed2c12914645cba1418888a40090bda5bd4d1d29538701e73b9807a3849d022b27cf56ffbb6deab32602456eb43511901d0571d1c4602a3566b267581c43754c2511322dd90d4c06719588673e632745c4e45564651fb9a251120be7817d458840fc68639115e8cce2d2a92f4a4a0c80079de3b00a3d8ea0bc4af39a619a17f2d3b6f4c16b3d170c1d1a1747549009207cf292f686765eeac6d43a7c4fea6800e83325a05a3010dc08032a09ca615a605cd5a403e8df0a884db5e1b6a261b60e740008a0be5991a84401124512917809a0352751b280be3c7bcea3508717ee9cb5724f7f5b6698b605a4ac75b2f2aade2194754a342bd7f0e2070319b184c8bdb7947b4b29a54c29c90406d505ec0551405285245398c2149248a14715a840852a506148134e68c2094d34d1c4114d347144134750ea61e15ccc96ef2e9b5041d29d7591b92b26bb321eee7b7e1731dc18775f7a4e720dc610ee82dce55c80dcbb731930c1c4136e6e6e6e6e6e6e6e6e6e6e6e9860828923478e1c3972e4c89123478e1c39c204134c30c144432a7952146c9e94144d54d25a6525a594927ae933aad69f52b5d3537ab1374561f3a4a4484a29a5a452a46e68fff6a6a8ec862d4d3fd1365129a57b8e774a524a2929c7a092a42fa594d2744ae9cbeabbbe6449fa0aa9949224a524eb4f0a4eaa922c6b454d586badb5d65a6badb5d65a29873ca98a9aa6ec29a19456544a8a56d79357bb9392727a29a7977392d3aaa47c4a528e41634029ad28a5945694529c4e8723c92b5e9e54432a65f69448549224499224296320a99452522929a53fdf849a21ada34b184ed124e59ca669a295c2e6494991aca497d5444dd334d13a4d524a29a594524a29a59495be94a4a7e0a5974b43c9bf3aed184c537aeaa78be14b9f8d344bb520f99423d91e9d8f6f551d0e957e92f2294952495fbbb20826a7be41094f689e96122fb726a3d93106e8337d863f89505b6390a2efdb106af61782f8b9992908298410ba6e2cddc3894195fb39f0d3eef9fd8d1f426a4173fe9c3cdb0ece757f45a47e0a4cc26ccd026a17ce39e79c73eeeece9dbb73ce39e79c733a2637c5416d2c986d36eab34d8b9fb8edca2a40c40d2dd3ef999f05397b437b3b44104fdc068439b9b6e676381d4e2c315204c80fef4012244384f0e81d2de3c412468200693c33b261430a75834ae6f6ebb8cd3751c645478eeb0e0935a339ee123773391a3a7f2e3cf2503b403f8b5a6239da690e16d11cb79f1ab93fda98d9bd5b2d3615fff6dcdc4b179d0bf14db388ab1fb489fa4ddda0360acc96923dd1a74825e24d08f5a4b8215ec30bac1f8da4341e724386d0b8216dc91257e3422aa9ce091c483ae046396e7b97a4436e48bb254b1e8d4b695dc54103977d207c257a88ae3f9af868de8fe7a388112915316251271af2a897a72858510889f857466a266f4fd9919748632bd98b4fcd1098228436437f1643cea655da05a1d1d05e29a166b57241f562752f421ae31afa68a4b404d3d03f3f7397ba41f54cd2bc2538c62bc234b4d34c7689dbff82708c67e4c3d00d69333822ee199193cd3e6e5b1aa99450e1cbe877f595e14e0886d2c50e313448b008870f223dca11a42f0c1e0d465fd8307f37c4438e456b7abd5fa25efed1a0a0600a0add35552ea899bbfeaa277ac1a63d9a9bb96b8320b725719b123f94dc5925a1b2f52248a8dc44cd2835e2354ef9c69f1bf122b75f8a34d6cfd6c9516b61b6942235a3149bb3bfc8850f5fc807804cf7374931eed249c58f71d7b9fb4fee195f7efad90d1e0de1dcb720f84e3c0483de4b207ef88f2576783deba231cacf0826936965849a359bea7790d29bcdf419765b7b7f125a7127dbc9bfd6aef8d6f5d23eb68f1f69dc98e1281a376eb3cd7381dafb3f93363159f1662b4fa61d5b6ef5f172caa586c6aa7665f4b5f6c7af3a42ad5c925b3d76ab9fceb16bed2a70a395d18d35d6cdddfd5dd2b7ab3907b528b5c76efcd7ac2b660819ad8b087afe95534f1f5eef41f12128daf642f46d5688be415440e652213846def8b3d92ce5b668e13f1b29ef02e1502ffcea5e464b97bcb21e6ebc3217b7b16b6eceb51437e19982d964a88d0816105d221081e4b646040b8c6e7bd73e8729739b6b6e5f7edfdd5a2da1f2f76d35d7b5f66e6059d6fbf3d7c9759a0e46cba4fb2a1ad49aa6e29eb29994f39df4211eb98a6b1e15f1f9ead609698c723f1b49146e0efe96a85940b0f95b16524fca166a914834820ac3ff97ad1bd2a6eff01dee358d39d7e1284ee73bae3ed79f4d35d5545b2128e6cfe0fbbbc3b8f4cec653d87c3a1d159f14289c7bb78ea6cde09c8e510e93772a3e9f0285ede79bb91a4703990b0d46574e88875578f84f1aec8bfec995c1703d5608be2634f24effcebb4ee7f2249dce77ac27b9f11b67d3bc82dfe15c8ea43d49b3b0751ceb486ea250abdfbc8ce6ab5a4fd2583312fc2cb525d735207a4a940baaa35c50fbf2bb9f6de2aeb21bb7c78af715cf178c534b881a0ceb09d4071e5af1a97585cd7deff1f48de77198eff1d09c04b9ef578c3c54655ce5fd8a91cc8ad18ad17d7dd3dedc960d4e95b1fe357fb52d46b8cc7dfe6e455b8ce018cc6c6d3c1dd7b2f174cc7fdf42d2551e8bd458646b2cf4f9e39ccf97bb9df5cd9df0dd9c8de47b7e8b111c4334e5cfa9b6e8b83e2c5834f69cfd702ad6b261fdf4a2adce8f3a28bd71f9e324d916232268e69a51d2be29b9426ef3e06a28ffeeeeee4e3bebcd3c344b4d9bd3af98f85738adfd0ad0b7c6c2e29bd36cf6b9315c577953a0d61a83c271388da2fa24384616d32fe850d9179c90db2c2cc8aeb0b61f45f51c6f18ced76f5ffb396d41d5f34f12fce61186b7bafbd9bfc9f0892beaa5bd53940e6c3d0c1600f605cbc282ac8378ea3ae356c306d9ac2de1929826259c8ac3f1a640d69acdcda6e6340fae8663b41bd23829a67749d8cc09b9d363970a8e5ce96f30fe28bebd12ba70db67c1b517d7b56faed9cc868b77deaee6542826eb6a565c9d57e1f914170aebca62b8667858f12c869bad74be35510cdf4422d7388dc3dc507cf53ec4f0fff9b4aff8339367ea4f3cd577389d197f9783ebee709b99d88c93e850cfc3a35ca86a6513b835098e51b9b8edb3b64411cdc2d4b7deadaf5fd56a75a835c7834b8263b00c035aa8b51e1a8ce884dcf6cddf6dc68dbe58e63a5f38aef7d596b82e79cbc27577b7990d17b77e5555ceccffd22e5b10e7617eaa95537db5aa97e115b5ae31af7df1cde67dceed0d829f90f04a792c18a509d37fbab3dc5b7387ed8af10bad4b826970ce229993a556c7ae7a758d3133b7955ec1bce182dfa020f882e20d8dbd7727448cfcd66618227e3f2808be41c42b838897ea00728b0356db0cfcf05d305b9bc1dd49fd52f7b394daf38faf6d6527741cebf06f49eeb478e88163f04fb3b26a700c6e14f542df7d0fff9e871a3628e5d2784e7677e75e923cfe4b9eb58c76f7318befce3d3e3b5b836970df62dcb96fefbe3d0f3db024b5247c3bdf3ab606d3e0fc1d77ecde619bb1e49c532e930ed8bc292001cefaa91ba6733cf4c031dc7bd60c25cbc962b2189bb524b79fa5679b35969285694f9f9f7e33fdf6ceb7f1a53d502ff4a5efab3de79abe5ef3a9cb7a7955f46ab7d5e0188dfaccbd37778abecb1adfd367d2b7c9c2481007f7d4d6a8df5f8363b48c3e556d07053526c6a7af2eeb728d55ed299b35d18b3f0cb5d3bf06c7709b41c0d690323a95836731fe4eddf0dc9559755b8369e8b7fcdd871a2dc9f5e670a7c76c29520f9bbbe236257a00b9d369eb10023a86fc4cc261be7b962421e4bff7ec61cccf60b8ef7d6cf17a16c219eedfdea1860b29f8144e08dfc34020bebf458f0582a215101e92f3ab35d9ca328da374f8fb7ada4c465929a9ad31176833f764348d9ff31130e7b434102044fa9c2e1b1e8d6671cefd646d688c0602a2502504bca5d198fbe7a25f7fe1c5773a285df4e77d99f297f17aa5593297e4f2f7d7d052651b01d11fe3c49210101278cddd3f660bd55d8cf703d158ef506da1b6eb2defabe1c56d76d06a8b09ef39cdacbe6faa0a897b0d05e9294a87293da55eac6f6f249372913c269c9ff346926e2408dfb78e7337fc60c7c2cc7f0ffba49174bd0de174f3a1201f7ea49592a497a0f44f82947a792f3df82736fb5413f878a25edec3307ce9b96aa97d503acd949294d59b9bf794dc2cba1b8ee11eda265d5927b9597c6c1d929bc5c08f8ce4c68fe92bb185919ef34fca2639e9f9a575df374c43fc8e658932c99975d66941d4cff70fc75aef55ffa6b5de513ad06eb3a7f4110ad25bd5b7c891400a3a9564a38de5bd24bd6b2cf4df8581318fca01c24b51e96a0bd5a6c22f497c25e9254992e27bef41e91fcff8100a4cc3c49424afd65ac3f8df451be6dc361451982d45b23b7792c6182915638dfedcdd638c14151fbbff74afb250dbcd1cfd34182db32efa4cd1e7fa1e45c97f6957ca7795e155af76554edfba6e5c78c5f8ad6c0da641464f944a1efc8f217d5c21849036c64254774d345e997be79e9dad945458a899bcdca46952233a709b122798b9190c4f510e9f73d39d77779230a98cc9ddb6652028a82c729e9303155fda295a508c263ba8f7f8a0389c5953bc4c41e9cce677b2149f99f088334e4a3d6553c43a27caa53349f9d1664e52547c6a4629677439292945cfac3f296a3ef591f3114548c553f2af784953ca9fa69593c9abb83e7f7255931d5933b9d6d5ae87e3e15094c763419caf3fbd1db28382cc068b3ad45b9d87d40bf530d566fc28f9b95cabd437b11635ada7c3a9aa897a2961aaa72f9da41587b66691928c52caf8f23db2bada4f97d4a15ce2d75a6bfd583f7e8d2f51d4736a4559d357d78d4bd1093b728c51c6a453646136cbf42ea9354bff6ceaef2549a7cbd5f751fa4849708a4f3cf184134cdd109f78e2892ef428e20f207da3f1ba9f229d5c7a14f10798ffae1ed356801e8006fd8f9ad12fb5749b5993831a74958ec625dbf5c257bbcccd1d0616733367299767301d2371ce512eee25f95ecd7bf9f22e237121433af47648d285bc1f6f87871e907e3e6eb33fff7095241d7a4024fb7e3891c6faa3f5218df5d71f6eb32aad0b792e2448103cca9e0eaefbd12c401a0ed2bbab35461be32bce1c615a24a8520b51298158451a3aacac04e91ef7cce322a8d97f4a873eeec5e39146cc18beecb3cd00d1d6a2a4b3d921f89fdb190c6aadb5d83de8c0cd9d9ece807f6324f5f12588b23860d4b19e311f44515710540eac83fbe81607f84d8647908cd005b430cf7591252d25e33699ce4f6b349a257b1be6cf5f400ff3e79c4a6e3b42e6b624b729e123c79d4c49eb62acc1b95f7bf2dd956d7ee47894f021f3ea9d2cf31160c3e77e7c7e585c9e38e8cab6359626bd0df3694c8e3fdfd352269402fcc90e080fa1a0df3c74f213c57572792eafdebbcef7d5b779348bbfdfe88b3f5e2ffddc8e2fd744c0b681105cd827b8b0550fadb80a9411f64f7fcdf23d45b09849b90eff49f7b1b918c2ddce0761dd8eadefd5776cd60f3321744978463ed7421a094a64755ac69a405b6df691a92eda302fc265b78d94dbb5ea87ff43cb999a61f0573cc435ad02ff3f9f0aa1e581baf259cbf0b8f0330e12f23c84dd011dd94af36896aeef23a43612f0b31a6ed63234ae02217ccfb583abc0ef170df32ff28346c80e22adc88d5c5d2f1af3294916086ae64432e0719590818e0b9f9180df10b27706e443e8b6077ecebb71c72dd95ac87a5e698c6d0f4c49caf9a6840c805cc7419640a25920cc38c7850f81b0b2eae9e189570cdf8a6780f087eef07df090c7c2efa159fce10e1e82d2c6c66f540d801f485d98b992841fb9c9c8cf3c190dfde02a12aefc205f669fe9124bce29b601444cb378e8853b24524a293fe31c3e34269f2f07f4d09894ff5eb894527af1f257628a431b921f705cf936e4c7d863bea7e53fb6d22c71dad62a2c65ca952eae7cdee1847766c9d1629b394786524af9376e8cf23d7cd695c08f80ccf0b987efde83d26d3aa37cdf80fba094b65518e1755f0e15d410109e0f8b4fc15cd0c658baf8dfcb8b31ce0925694ead77c0329cd6c69c36e6409056113573d72585cfb9cd7c5fa960c55330174d25a0dd8fb4b9389d72911ec2a7969a149d3cd9dec125c3255aa3b581da6f65abb1c940f5161d12da6baf3d2b03354bb98f5d38e8eeb30f6cf0b94a0377718edd4ab33f619a168e2a29db28fdf473c59449b6a5bc22f2a3b45edc25addce24b56922fdb3f315bb3408b03b5a7e42da32505f075f0dd4187f04949511445bd44491fffc67d5745bdf98d72bf6cd03b815cf9327ca28d456b0335fb4c764500c2922bbd7cc94d1611357392fefbefb9a233822a7d4b2f491b998fd0be99cc9ed6dfb8f0cafe421fe073ff6b31c29d7970425b88f4b7e800cd9f2deaf76f7e6abb9b29bf45bdb4d0b4a85dfe161d5d512e1c29959e73753ef65ba23843de8e258a56c496e83ee74d019ebe85869a4c137d29b3961d93fca9c5088e71f234726c8b8e4bff64ba974c2553fa2c9f5a90a32a4ae9377f9c74484dbd9c3c5327fdb1d90745fc93b61d1454e9853e4cb419d378b2621421f05e594bd9898874cc2cbd2910a38f9b491f3351dadf42c331e877cb8e9b49316bd171b397246dd171a56ff223fd284d1f5f92b2d61839e6fbf9dd2459fa2d34959ed07868858d3ca1b972854d269d9a9a9a9a1acecccccccc4c75918b5c34801722e0194953777c7cf8305ac232353535353535333533333333333cc4c6880889e5aa9a9a9a9a1a6a6666666666e6f653021420043e34242ce346a3d16806fb9a0fd3f00ef6c13f3808ada9a9a9a9995ec09859912f6a86d8ec70231f30ecfff391486a1b7d6cea14dde8b991bb917323189493a8314070fbdf8e95ae2fba9b8d8dac36fb5c9bfd7d3617bb9235a4f28c4182960f346f6c48e56f1b03d0b621813b9d50260140980968b735e62e842dcaf85ae5db283ec27c406d3e6842c99d964a77c7185d62b42ced0d5176848e07ee4fb2ee9e491b5d0fa20d0c7364c9fa464fc63ff5e56499e5f359b7f80cc6cf2694cf2ad0671d9689432a9fad8031bfcdf9838704f04ee553c8f02b04305f86f92a327a3afab9527eba54fc7c8e88c6f2b129b6858d1665b2208b83062a3e037f009489360825756a06dfe925221a4b8af52d3e7318889fb90da07ce63c00facc8540c5678e04d79de0e326be376a9da69f2ceba50c774cbd408ac60dc6e162fdb3f8782a952952bac6ac9f103617110505e541afc3e751a6b7acf930d3d7f77cc4777af779eb678c1405e99b29f19372518c4e6b4e69413f9f291d3e3fcdcb4793cda648290628e532a9197c639a734e13c7345d2b8d4df6456bf341576b18cad51a162f21176b71b586cdcff5a261f309307fb2fe73bd686c7e0d8d4d39df7d1371389ceb009dd39c2f512ed3b4de5dcb6615b82957c637c5422162fed0e25b6bd1e2ad0bc5ae3466d9178db9e6e837258e9871998b226da459b227baeedd0f2d56c8889a25e59da731e95b5c323d54be5e317c3f9646cad5ee0f1e9229dcafb0fe51a162a7cfc50db3bec5e51a363f5e92366c3ee8aacdd3b0f92ad7a7a9cc9f939bb35c2c1a1dcd2245facd52cd20e16d9ad694f38539ea66645a303c532e00781b3806cabb4f408331bd7b1c1a4b0b158f0c1ee9f17c0b1429838a65bd4789e1a700fce84e3fad8a6118f66288e1b100b468f1d6a3705a0b394d930eff93b5acbf50527e461ecdd2e2dd471f1d5279f7d295a05cbee4477710246a9e8572f1fd9f19307117f8c169c0adc0044ec659c0c9a0bc94b5a27cf6b90108c0a3e07819191c4548cb7ef6463afc4f3b04cfb0fe7af727ff9c68162983e38d3ca51dca5bef7e76502c75446d71b912da6408a908e03389e561a1614e00f66da1a9b87716043432400dcca8d827ae7323a6a15da1cde03e73a3f9eeff4e18a0ee041cd27aea889abdb030d65f0fe34b1a732526b43d254b50be67d0588acf7a880c0524304a99f12e8afd01e59db4e255fc9ca8e8f3441f14fb464f0402bd7ca23782e1edf5e27adbf2c20b6ff496340b0ceffe39212f8bca0286cbe128a231f74ebd911c0d19f24600b04ee68dda1099c78525ee8d9e92b70407fff9f585ac82aeccadb8328f0ae764dcc5414faeece3de7309115f78c885ec70ec0c628b6e41427be75417a548455252d984eaedea1557f16f33786cca91ea98a8fddc2f98fc18f6ff7136a81e95bff9f5e03b296bbbcdc6caed7fecf2ba4889b2a738bed1ed88fce8eeeee607610014d366e08f4083d111e0d6ce3e16d0d80e3c94580734e6a26baab3a35af6d118cf80060f6a86d5829ab525cca3886699be35cbc441d3cf39ae83acc38766a136684cb354efdeebca9da2d48707f2dfa1dd1da695f7ce1feb900d4bdc248900ff902e305b4ae63f9cb68e890087ac95dbb025687c01967beaabe9ae1d748ab29ec6bacdd02fd0b8cd64ad9f7f2c6b352b0e2184f046d4389cb86e99478d7910ffe93d2ac3df5730ecfff3a93693143bee1aea06954bb9406bb1a0ba7f1285f03b0890c626db588905a8f1dd3315ab950bbfa270388901126270a185711f5bc6f0ca60a6a3b451314e245e1a8918ee208d79a3acd066f0a64fa8ee9f731312fed006bea0f83092bdc1bd12ca853ea1f2c307d2403a48b348db4a1a43a243bde4c5ca0a86fd03a9d5ff8d44b57f5ced2e105b7abb08f8d23a6351e7701dde3caebf6f3f98d131e3c5a38cb2139964e771d949cd23d7c3b26c8f2822d55fd42c6cf36ca443aeb988045c4a776f4b7ae4a3e9de1e7a4793dcd0e8508f3a8663746b79f7cfb53da28450658faefb0f18424089aa7b970f01be1c222a7f164351548a48c5e6f9d9a1be23a549fd549bc9682d931d1e1f52462ace3fb96ad6f2a2d24e4fc9e770389c20379e58e92ef5263b24db1aa332cf0f8ef4140a0ef529ac6706377e9d23a8e4edf6944109d50c0000144314000028100c07c4628148301a0807de3714000d90a6526c589ba84990c2904186180388010200000000006044a09220008333aea2f94acbad547f4ff44499a8e529fed56a0decfcebd4d08ee32ba130f7114556f7c90187d7fff8bfce2776aa0da5776c8ae4207ba29901b475cf72c003e223734a38768b89179cd95318b709d9a2b8ab48959e6109c21c3cda873e9afd574afe5513efdf477ce837cd43679948dcaf7f2fb8e96f28c82109e966c4477cec64c5e1f1498aa61366ad805b65c39de71a75bd05bc25cd0aebf7c49f988992139e79184b239b447e16e600a91c2655bcf96c0b117f4fd230aeb995778668ba59dc9fc0d6004acd381930d0f3d5e9aadea371f356e7ce638552f40a604167013c58ebe5abc512b0a06dcaf63768eefd8709c3bd3aba25e52fb51a8c778c09cd3fd29ce1dcb2fd40f25dafcd994d9a3ddad40dcc7cfd33b1133b51d89b7def6d9582d4dcb1cd46c7c9c62d64f4d538306d99fdf649259cf4e4c02f81fc4efd71c768716de75a13c71f660a3872dbc0e3b139465a5b3811a1999cb58c70333a0e460cc591b99c758321f4496ae8f35ff0cc7852882ddc990d5e5aa8b03f5239626ea33444c0175295d574c535a492fed009f8662e533eab30093767d93ece17cc44e9b9d62c9fdaca7122f6d94922263f6ad330da76d7c5b3189b364716cfdfb8ee452464748587cf858a61d84c1df372dc4e9a9cec6e4a5a55c2c9383e500e8fbd8fcf1dcee641e046916f7660c85feb51768e313a4c9d334560574f66f29db57bc5dbd93ef6ce4e2c43b76d42684fcb7874aec75891958827ba04d9f15d99957b0086473706485f392757699b1682c8b3e2b335752925fe49d49ce508272dfac60b44201cfffeb708d559fcdc8bd791cac53df876021690dda7945095f79c2daa06c5342fd91bce127d2f9cbc83e7d156b78bae157237832ea046df55306ed9dc2525fc91f724cbcaf3a219df19a46480f1c8f9c04f042c8b1892b3aacf74acce6fcefae7f721065b5cb38bdeb3d693964f1e8130d40b0cf5bc1b68da1eec3e5628502d1d480d4c4a40f8af8e792d7096c86d1826870d95931f398488481ec0d98965658fdf727fc3599c862fa253d271f63a8560933641a3a5d78b53ce0aced643d21f00909118d6411552f51397e36205ea688007a9f956f761d1bbbcbdb3807370700a033a9c65af41d4d93cec444dc6c079f4b0a0c12f12c84c0ec019726657398af543b525dab04a21a2abe735d99b1223209ed2b8f63fd57e282e974479bff2cb67abdb6a299eb9dd390faf08eea9be68812786aa3523edd1a26b7b7a5524739dfe73839dd698a8bee80877b9ed0787be0f3fda6a0984cf80c858be780a3419861551a8498942c0a194a069c8d63d75b84e43a8d14a98d93a46161f144367c0fe82f3fa5475a8804263f7443cc50e9b319399979b9eab71d4ff637f3a74365bd8b23adf0575a196ce0fea3d59f6385ae46a8bac70368a43da1d211dc2fe441cc4cda0304923fc6e4c9bf18986716481ccaf964393b76e0796aa8d9c4bac406c0ec574dd1a576724186b79c09015ccc9d2ca26fc470ff87f85f8acc50c948496fff54369a48e59a68948af58220c0122a949d44c245ee162d452d1955accff33ef3bba3021b3fb149f9305cca064c071764a4e610f31171ab353fc429d9e09bcc8403b33433511929a97782e45fd42fdfdc46674f275a81c5261ec74ee1c0a09a2916690dfc40d7879f4a0c7d8b46edfb322d21b66c3529634383e100119d9b0ec484fc3ec588470d107a277f65d7517065a904460e1317743044d157694b91c8f5f06ee9242ea89c3575ab0f1f3e013fbd9b08211c4439a723806f5ff14dfc2722110c8c42c6afc89a29828eba3d347a22d509884b29d070cdf64004758d2c6ea58880965840d620b89cf508471819e58b8111e0c4b7b1af1f916bda5a1d26134e7c65bc1d778d3a16a10bef8b060e5df9a708f88ff2def22be22e97bd66258f4fcb7063e6677fe5bc592f7744f73d5961ffb2901ed713ff5a6ab777df0f8b70e8f411ee803dfdc5bf39d66b8140a97ce6164704f5bba5200194b348e69017fd91adb3da77487d0754742b125ab69b3bd35510871768c087b022374301f0c23532758e33ae7b9a1bab8fb073814ef1a2d0843bfd08bd5250ed3d90f1e815a7291668867501e47d1a134e5bf13cb5281105454a309d9871eda1e846eec61b39f594f182d89aac7e17acc73341d5698848f8aba00cc8d56cabf61f55232247f3d057d5b37f160610d56db5c47621f52e84970d058a60fb5150ecadd2d88da39f6a02118380755003c7510591a3fd96c250b38fcaa2ee955ece958242e199413ce41f4e21d60119665f1c8c685a0cf4c7294bc22db377a8f12d48e9d74c267e8a17a7b926be822c039a8c8c8fdb7350c2a24d27a0e12eb4e7264273e3b8715df244c9f0d70e81a0075810e62bf5729cdb9b21af8c68d5ddcd46e97b51229ce095af77607123f5b6ad742f58f44129a42125ee4e450f56650a491e2de5e02b49352b550851df15d973983fa1b71340bd9fb8c91902c63fa59d5d2d00b8482390a397e9252f3446d41831816eac11d5d8580c40cbafaf2976a06090500ebebf81c05831b0fb3d67a5bbffcde00d91af3e07693f08513fa00643e5c8fd85e990bdaa727dc09acbd36a754a95c3210ae4fa9b3473845539786bb8f08a230fc88658b445851b809f7b416d2b8565484f51489ee287d4bf749c2389ddc4e70be05911dc1d481e2494589016fb17170a5431420c0efec4ba11828224d06cd1a4a80ae9ca42d158631a8048ce85ba99bcd449e06144c0cc5499814b8336b812cac9f2cfc10a0890793277695fce859b87773f8d9b06a849d4e2773a36998910136f1bae2c2faa082e5333e121ee427f912bec4f28301f517ace6ba8d706fd6b6c227e546a56322e557325a78151cf48be8764d836a51e1e5b078a9b28b1536e13b03e414ab8cb6088270e362bb1c2a144405875d43e06f339d51b1e33e97a180c8032b9c0156b30c7cbfd77c28286edf4784070fcabcf45050d75ce4d15437426f589d953ae497aaf2eb4b5089ed932808119eb35b0327b8919cd0aba8446cb040b8c8d04576e9d701fa1840255b0ce2ffbb21624ada601316430a56211049a220d6d80270e7c03de695cde02d7d7c93e01267a2a0e8e1e803a8950701db9b5f4f3d92551d18286247daabab38e3cc691923ca6f1ae9fabed2894f29f208910a26e3cbb2b2a1461ab799fc008a82e414fc4d275d3fc9136aa347e1c7741eef4afc5dcf1af5740f2e4f3114853034d94f6e67d201cd851e91f39282945e515ebd7f2a292874ddfe69454e9c6621181719f1deed4ae019e39472ec7446a72deb127c70e1cbbf88476ace1aff4593cf366cd17626d625372360411571e6ce1116645f9142c84da3b539b7d209079e4f04198c7d756cb4aba60889cc3f6f8ae3abc3f7e0503ad1ab770ed7327c8bf9f2b86d9a821d6c7c12ce95ad734830ee2a4e30d3a0bb8a0e2cfc2a9cea519246e896909ff2fe34fb219950fd8ae804de66d53ec1a29f77828171ee8c21f92c921686cc3dbf64334d4e70080a23eba66fd95a7fcdfd8194b248467c4fe1ddab8fe3ee320bbb9dcfb393d436ff4235477c0aa2288b7ad9364dc3bb17c49282c244a8efbbed788856866ebccc9badf5ec4c7c8107adb5bdf8826fdc1eb13acb0e49306231f71cd78eb9d480278f759a73e2c429749cf3637fbf788e8adaaef3533ac9a93e577d23f8eeb70d229170c3f056f05bc213d663fd1731be5d7e0a377dd1343c668a52e38ab7d178895c6207690e12a125608c1b40001dfff44a84773829006c33e4483b770c1eb87d8ed2928088c10024fc2a54423ace9da27a7b665a38d93d5ccefce6484379ed11d0fae8c4ebe870d98f5f26a6c12f953e9334b56ed5d5419dc815e1337c93b7ca92136174a056b45bbc6a3558b771e605b9e5f135eafa23f580c40033f2c123a192153a54cf74df39f882ab163e82060321bd304f204b75d3cb1dd353b9576b8735358db5590b31e0681c92f8176d7259ed40059055188ebfc6602505143264b9a615ec45c8283f7b055f82108b8b660f721d66ad8f03b760b02f30753de677f2d097bbc61cee64da0d7a0ec375e27c04e8df0ee288c6e45bc03703af940c2c2cfde45aee45a9b8a4be060aedbc097f293c6df35e92574eb2b0831320be476c6937141791fc79f709b8d3e0b6121b044e617fc5c84f1149db810fe0fa7e7cd3a80ce79441fc3e2008817532cdf61b46da524212dafc95c1ba86b9340f5e50e0cb16e3cb3c8c987b46a2780382a8968c3c061f9d40082622cec3aa8171f85be8c1fdc4303ea3fab41e8e593a02bc77e685f86b00452cee656933479839df34120b5aa485c9c1f6db9925367c5ce8ac6e80fe248ed494e046b0636199cf3e2793353fd7d6a246023f2fd70b12c39bf24304232b36bea1b26b38ebf7b9b34d20e38fb95cfad931908e16c5560a0a6f2a2300b8113cf4fa59d2cd35dac9a4d8d9ed82cae97a2a5f9ed2386576bf47436e2c10a6e5a1237e345962e78833ffbc46ee5278fa3cbc545cd07bcbb195c487dcd98b6d437c83125c4b588d6da3d61cafe77fd0476e89414ca952ba776ab3eccfb293b29b2227a281a19ad839243e2f1f161c882a56c8ead0cc02448070a21f71124dd51ccfff20007b37436d2d49215ffd2c63321e913ce22dd40f3a1b229409d35f60dad7b4ef0c71624ab7409de16716fafd8d00398442c01cc578b275c47c5d741555ba51cf73819a1ca22bec14b740fca7eaba5d19a98da3fc8a6ef7e02ab7aca94120ce46e276648a2b57eb620bcacb7cdc35107c14f9443d082455db9e53e1be6c15bfdf14a8576de1365548a30a44d76c9b0847d459686e9313c5c961da0b63ad4cd471bb55b265c7fe7f2cadec019c0bfc1201e35f6f7ed6ba94f0363d9490f33b9804a65d1d8e356837af725ff84212bfb793eb45aa3c6bca868e2fdf7b1f5734d55cd0c7ad63943ed1b9d908f394f66bb81bb9c1897b50463b1f2fdcad7dd2ebd6e71c3249dba905aa25d58220237edc3a111b2da593acb325a301276e4253d6c73d4ff86ea819e3f715414ce9f7360308635a3ba44ca1310df51ffc6bf56c0aa0ad36cb31eaf1788a4c935101f596b10f431c1fe9f0f8ce8043ba12b250923bbfecb39735ace0b8aec12513ff8caa2fa82c1e5209fbea3839b56123a21300129d403097200c720f2dcf2ee62c4a415c8e121950d9d445dd0d36a8a239d3f02ee654686ca93bb5fb8076905badf8612662a8c9075f219439ea0db6866dde4a9390fa5824a7f95676061f5a303eab01bee75bb91d5f0d988559238bd976caacf827b4815741ef04a5ff2c6b1397e4f448b7a8b5c8eefe083ec0f800d9646066037fc0f3246e7b71de2bb1d9c7db7219035a50db83e6f76a5e325da2cc5c206868a08a9a9632de0c75981d1468dc370f8fe5473abea38bb71ed3e581699fd0c2313358f9dea7bbffa187989363a21fa2cf03be630be41bbdb81f044206c91488375aa530847815ad956e4299b298ce03b7fb7bed9b4217b25f0e4a5da9b0d2c2158348e5a9b4611a4047009c461e392d82981f9abd0fa0ab83671e9e117f3af7f8f2f5f9588ecd2e7c4a30ecdf1db5ae1f602bf2ae594f5d14ad3ac437c4cbfe7a1785e2c832da9da2ca4422693c5fd0bdb98e81d55abce672e0ec7d031d57cf59b36750e54b13f04af5b0c716375097fb764eba298b9d365737a291b36ec210b270fbabbb24c12b82919075f484c9f78c84657b967ceb972e11a60225df015d119ebb5638ef9e31e03108c4786159241f47299f1abe2263d7cd8dc2208c0a8d6f5c0c6b0e2ad217b89cae7069a8a3b6a369d5e0eb48b23c26db67ea92825fe0c704d181fb16fb32f4e0a6de359f409888d8087036d99d0c7f4804b6e35a62073332266613f0ea14ed2a341ed6fdb7f715e127b8596b26e7b15ef6822a60b9feba9e7c03498ba92f3c8cc8f699ee270a1b3f41d7c43fc9c13a6d2124d30771394bb7f89f6dbb8b029c6704b5f10f89f8acb56fcfbb25f45302e0188e153259fd42b4417522476332485cfc0bcb06888f0f7a2dae1fd91c4a231d20383ece020402a82d69a5c25a93f66c0da1e23e01d454b34e3f8117d70443e6685309bc14a634f93bc2959d7ce2974c49e696fa7e3a4b2a1985c8f15dd5d39451307e7c5158359f7c188d6e8237cd15e9e05af38eaad5366862e71e78c8a042e06795a8f2eb75933658d53734131cda3c2a03d61567081db858fcebe7fb405050b02d4b1937118ce927003e9721e00ef4887bc87e529f2efcb93f8079ad5218f377a40fb4371df859999d87ee47e078acc9cb78df83321f45990e2b7e03e0791426f8c0180996b68f615c773ac3c545c334ea3060520a5be11d0686544086ec366fe5bb7ec88d5a1f76d171d09b4fb658d26b11caf41384dea44da68c6af142ea9d7300d062b4bae11649b80f3dd1439bf419e91f361eda1cfdd58c0c68e580e4dd507d1170794f3fc1bd5ee0f86501cd20c4dae49d5a32fb29905259996f6857ae0672df5c3d593a46c522faf89f02794bb72a6854d17a36717af0922da81d231ba8e82c83e84b959eb166a7108c40d1ca5b98dc36a31d8a94833fe32ccc212e1780e8d927b4fb13546603231ac98d456a3a5c78333aa8bb8412f4f1cff89c7459af1d1222099ededf7793c2129de7a43452461772e9acd8a810bac90861c51d08c82a648659f71382bbd5f8e141225687a93466daa4cddea301e82c38ea97d8e3f9a36121b5a169530be1e019d3fd0416d5861c5e5ecdfa7f084377c1cc5feef0c57e92835b55c092f06906b94d2092e2833b38e99aa8c6e2a2598210a2ad31ddbfec22b6b96276ff4ba2bfa7fee117953b0dcdf913d87348348545ac35018c7bc685497a33f95bfd0ab8a457ca292eaf4531afb01c059f06c2d58b5856c76a2d2ce6c7142d9b5d3376fd30cd98c3784d6f6b5d3386bfabcc6f02b4177735bf4a6a9859609aafde6117bb5c70b2fdab0b0324e5acd0e7aaa4f5528686014b6ca6e6eb62227f22133502d261087ee017e69a04c11ab536f0aebf5b9508a34d384d6ad08e94e322f0a60e16f4e3a6044dac26dc1ad05c1221904ab40cb21240c467213e8f7f6fb9658e543d9f790137f600197d9a4fb279df5985a2bd42dc21492c8ea9bdf61e01f246ba2413040ee2112ccef879af48dc2cdaee23dac4b3841cee3a14e28a940f67e6ee83bd9dee9e8f3522b2de17d1f807813a321ea6530b8cb4c54461970e8beb8c8d9b38a92dfd582a523a479864cf43cabd6fa6ec8f440c286175cebb93f42cbaeca2857639b71cd8df1c78e32292340045512328bf148db619d754c300488c067a54299493583be16e161a87154a4fdc47a42d1f7e216714444e50f0afd8be398efe21b1c29f12d99372eac04e35ae653d7e560f41e6f2cd51b8d650f78f1c34864fe3ef7fb60fc12ac4143648c0441c078bd703d0c371c877f256f7b7c5b1f94c3687fa5a3a1736a12e13d6e4d558fe213a791a12da86feca102b43d33047b02814a5d18d018b4eef192ae5b56371608e475086af08cd27a53b36b8a06b18e5189172328967faea839b3ab10ad5ef150a978d2154af5c8fecfd4283f85a4d9acb8db7c21fab6fd1e8fb87dc117d3aefe3e8b29099d1f79cd070235fbe2ed2b771c94e0943fb5644d5f6dce96aea79662e11013cbb5d50c2d36f6fa2a84c6a1304f3164b9f099e57b6e583474cae40c465f561ae2830ac1708bea0b4eb12bb57375cf4f605d8533bfff7c23e10327185a603b31550ce49a90c032c01755e01e69de002f8dbaca4c881651e6b130d8ee4be4ea74348d32b2e884a2b1d6e0b0f91c3c443c7fe5e2856335a1ac0915dbf5a8b88e53c02316603a06362929b1c22d917428d6d91ca49c14c050b4141c8b531790185c4dd5758e5638b98a3365553bb422fe13695e37aaeb6e5befb2438562f6e79c7569b80c5f670d8f2f108e129718cb94c7e32f1f6e58568dc44a7f1c1b08f19753f4fc6f1fdca0e21431eeef26ded408019cb2d67884b0e9c01d1e1d7af1e8c6e713ebb54e085fb801227462e53d2d68e2448850893a6b7437cbdce687bb4544eeaaadd63ff175fd6ac280f12b25f6b12878fcda87cdbd4a2503dc4616c02b8548bb629439e7452bd590a177809e7a9d4fb4425b1eae7c655dff456df28a9bc89d4485438d8141f1ad7b9c4015b1bcbf6e461a8bdfae55289a601d9e8878c802bc464bd72d960d172d199dcac3438d4517fe163c01c11670517664ced1922301ae429742e5ff3eb6ee854c0e17e45b7d469d0ca9134bbe5ac9a60ad000546f8c4c0edf3953c15c4564d6103fef41533052b6ba4a192c32b2a0b41068d05790f0e27247bfaa83d39ebde71ac4b34a23cca500d08ad3d9c5d8c07792ac03ba6f3562e4ffe469e0853e4ea28faed4127359f7faa9cd4d06446a23d10b2749ef80d27c38b0dde0f6e36a0a2a4542f5cc29535ae2e24759788d2abb27a80975fad80679edfaf563dddcc20a8306314738d24b3299fa1338a6d6ad7a94c71757d7511d9954387c5bd880a34019311af782204d5130b2423710bc712339178de0f5f801183cb4c95a428e5378e265ccc1243d7a60874f9b85eed7caba761cfed13625d035f718cbdd9bf5d743fb3d87b3aba82771638ac825b34c4706b9d114700d8de090bf5f2cb8615b355217f48501a9ec96aad68f4c6e7a9aeaede0311e973c33682638102436816f34ed021d10d99e199b3b8f857005f8730cb4a1bae7c5fd5926c76bbd32f9e7650a706f006afd6f04d6a4d8fd0cc90806b2ffbdc23a8e695854b2cc7624d3ba20d6ee1132d5444f13f48b0bb35b5809267fe16e6c75f13da72e40bb0f17d53b8d0b2cfca53b07939c0b07e36ad1560d93800873a40807a64a50b6df3996657f03a3857fe5650528b71938383f7372ae6b4c2f29f8c5242401ab18515b95306f6975e50071ebd21e2a3c207d4afea62eecac2a2086cb6fe9fdb880e47a168118cb103d59950fe1459033862716a114a7a372166c91d077c97e220522082013f9194c34c663729bee629b285367d6e98b85e0ca0c26a40ec88178769ab1f0670d68612d923d6a8ecc747c500bd75b304953b8ab20a1a9c73711e4e0625280cce579b9614e5cabbf3bcdefdc1b4c8d784cb63b1eefd81ee7766fffed751845e7cbef3cb09bccc59a01f0da25274f0abe3f33ee881e0f5610d9acfe085d4f772dfc73699a2ca5f0285b07c8775c9d576e459cc8663d19d598f6f8dfeea4f2ea3a2f9fcb8b95959a59ed29ebded56af758d348c2303f566a1348c42733f113ac0f8949edc25a5d50b7acc7b5cb6a373fbcc6c419b825c12c9dfa637f2d12b61547ca83ef82d898f4c0f5faa02697a175d6c9850db273246fa6afaa01186b84339e49b0ee58457a5167f0df84325614f3dccb78492fded8cbc5462cafa84db2f86791b2a5eec6898f391226b203d9ffd06989efcf9327de4f9a8d94a8c626477c777219c94a1df55dd5ba3bea8601088ffb4f89b503c7b40866595d4cf201e950a046f47574e1b7d11c287ffa9e6a8009688309ec110bf4ae95a9e5d93962e5ad7d08878a3718bf3b0d7775f5718ac0abebb17033805f57a2f31bca8115a9097dffe510b0502142fd3e6825ef669737908ba8e2528aafaa607f51a4014a71be4ae6c2d5eaedaff2ff39bfc21a5e06ace6b4547bcefa83df1f2b098c6e6defd4207eddd1de3469165f46ba5bc47f9e5b0fd98c87507c90a751ad9805bcc8f7abb078c81c758545488b62f0d5b00d86f4d819391af48cba1bd1b11e168e3bcf5bb41e1ed9997be9eebbc940d894f18b4b295c282ac2fccea65f045f817d834bcbe2b674aa827744945b14483459c8477a8c27e96acef827dd0fbb7f98caf3f6667ef366bc0a3f3fee916d744a1b2f49c37519ca5a3cef8690f18453f31912a6df75acb90c0a4b43d2edce5687e61c9590857b12c1b42029e0351b8c7baad8ba4e20e3e06524c9cb069e9dd0ae5b99f83fd5386b0909ded53ef44a2aef64afa8e27e2b73e4f87c0a89657932252a6069c7d17c1026490952628a67276015175a75a01311ec3c866a25e0b7dafccda38cbd6bc2fa09ecdd8d6f1e13277719971b65620186ec586dd064600dffeea7edef10e3dc017c96e8507e11cc47080063a6099005a960719acfa34cd73c10dc32257b76b0dd46bd76e46b2fb7d31d4930e0221e4ee63ba21366502f64a607d27dfdc556397ecd8acbaca96a82568d04d38fba0322c6d79e4a7d300d579ea8f816f7e1886b6d99e7c51453bbfdf27f334bb6ac3fc3aab8237bf3ddbd9c49f252a88e905abda70cd4ec0378c8cb48c0b74589423e7671a88d956d198ec3571312a7b372217af74cbded46ce0446a8407ea66792305b73eb2aa87ab6dd5d2e217b76d1be808ff1e31ef6fa226868c52f1e1459bd106cba4d8b02948faab8cf7d9d58faca65cbc657e5123ac235958b24717f6573d61c723e9e65b8e5c686c70f227511aa42876a570183af588b7e5135505307c55990e36bd5ee8f049a07b544ee483d1c1d682be80466dd1d71b65e70f241ea4e95f0dea3613f2619b099362f4e3772e44b7be2231e29acb961d0d7bbd604fe7cbf5be06a72e7ec19a25a5b47ac060a7c0ddceef421b2227cff13a0a1b3fd2df88a0c005e116127c2ce6e2294e7568078fae3a38469a0e9a44783cc72ca8f89b3118840f34f88c60206810ea7464b5bf33b30e4f385bfc29795371cd53d0da598a2192f09bc9dec88e8a552dbdc3daa92c148e6053192e4ea6012a5ef123537db3b369848f473ad33ea81dd88733f120c4ab5019f9e3b04ee79059c05bdc390df67bd312d01b55f95aa40303551c171e365662f05dc7b9e63e92b5ac72fd44cbd325881b6c930ad2735e912302b199b1c67b5d789f107bc58c2d98e225fe13f27f1403570e847fd2a98f94f95f8521a60dff09fcaf022303ad8895e06866f0e19b2f12a4fd599534b93fd959ed383d73123d213c29e2455f4a3ae42064a8261c121d38f00f6ae6b58ee984903c9654db5bd72484a70a38b06dcb7bdbb084986bc41d416c1e96cfe44719f25c2db3f2ced7fefcff45744060b7c66d7ced31ecbdebe31839d51a4178d8ceda6a3702192031a24cb3b7b4007ded0edd440d0c2658cd7e4203c2c35f78fef68dbeb0172cd19167868d83b7d6e8481b0790967d9a312a9fb333cfe5fab1b5aeedfe06479cffada79adb5f02e23232e26f033de5f2db7aefecb7fd45731fb222e995a3dd1a2de22df0078f80c50bf4b0dff44d237fb774fd082cf044817e4d989a2a4e587c154f38d419c585b2acae3756be6846dd51413a680608adeb827f044bc399a5dcae5047da21c4daf1bb5d9030cbd599a60f6cea557f909f082061c4357ae1f7fcb42d7ef0c88b931d06484a74d8c655e183882e6d79d9a4c86561e0adeebb721ce4b6c37a79cab021e293e50b8ea3042e4420303f9241fa63cd7987cdab28ffa233c834cd32369c70bf60ad01004329862e621090daf40dfaad21948487e86be43f35f5d49a000de4cf16a7b6c7dba329f4b9f2cc6b725b6c9b391ffd3c1da29e5b412148c7eec6fc74e282303c1aaf7307740a7a8cb68965b4a1ff19029582560d6d6cf7e2657d8a79a922ad763316ebfb7eaf4bbedf10bd18d1a4ddbca6871682f8e39ddc37e8d30f11fb14e4dbaa1584698a19c62bb4128232b283b83251e0b4daa6848a127d578e5fcda00546ed9ac08520f7b51284b4142789b4f29b8082a038beeab4b908a2346df0a9e21fdc5cf12db33a81365942d5fa61238b41bae7fd52173e816cdd3b004af3c906ae48afee222f180fc0d815a5363fab28423b76d4971d08cf08b0f243785e063d3d5cc3b0b410aa208f14b9f392d1237b52b86bd61b10a20bf5f81207bc17637a880e25f6137fbb7541948354b5f03ec127347026478b57f7057d078d39f9743860cb3fdd92233325caa7d639b5bacc3da08cf90d288deb817df9cabfa5a6aba4fa6111eeea48eea3ae1b14087d400ec764323fdb7f4baf66f8d5339a9f15d6d009677a160116db8ef2856896d775170bad5952f31fb620608f76c375ad4f271062e67d4737512bb93d6a360f939cf16d6d876a855a3f9fb67d7464b2ac5255375e50f56c120370dae06c24b66b6d3b75d64a8128e0e377f7b0335f0228b73a003f4d7214299880654283f74bf7721e245f5fc0154f30ed1ed96096523ef37417c30542cc6d84fb60d8e7ef44da5ec10b9781788ae24e20e9edc73a444d9cc9acd1952fde4275cc3d08f25243a2b29188e618ddd84d9b0f47b899538cc608f4547e40803e1e5933fa5a78602acba840298e9f2abd216c928c9575f106b5bf9042bf7c6e6975508e900f584df383958321f7048269f8de70db7ec0473c8af8a662a1f144393ebce27468db8df66e4b4c178232bb83e090579cb8ae84326c5663237c892205a1f784836d9a273a1aa62c64458c2222b403f4fe6dc8fe30c811ec06c3d69dff9a6f6949e56ba06412dfa664402e633669563e20ba3dea362ccc032fb2cd5e6f1bddfecb7b8f885cfc136b8c85f95126843e4ac5b0ed00ba1ed10e5060676879093f3ee57c21bb0064e0d2aec3553c29b11800053dbba2b2e1466e05d09ad1907a9d588557497635df944a4192ac3479f562ef44a10ec00b345f3a7a1b4bacef885dbc37294038e5ec50ebfbadcbaecd7841ad93d4d4e0fe5f0a684a240e169f78049228d975c79eb201a5f9ccea94a1f21fb8989e50e45549518cb1b0d239e7672553227f6ba8ae9efe60d02eba0708f203b75612c3a19220b9856b3b050b500ee1a67c9e52487d021bb5037c00918227359e94e1de64296b7d686079b5d02ae4168a98644676602b1387d042d9aaeaf9d154cbefb844a28ded0d6ec87ea6fc97bb26255768a627beea34b87637b2cf520863b572fe666e65d386c5901800dd272b6d0cf973e25b5c5810689ac139b69c02bd5ab7251672bd001589dc8e0b9452f894121024cdfdd8714f13928713ebed2daf6fdd63f7c74113e2cf691db9614f9a192ca7ee7a93f7c704032ddaf8b5646cc376a33d1570505e58a69c6f3448581105ba07671795f0c0ba88ccaf92d73b46d40de0398456f417aabf8a059125ffe43647e777d10b575161aaf115570812065abb097984e8575d2fc9bc88da0ab29d295f6f00a8c5c96957e5f0e537b9814b6ca9ec80ae32d7a6d33b8f5421ee7d427e578977bc7b5c29881ec4812d6239c4eaaa4b31ef1879fffceeeed8de3e22cef7a83ac44f2fe9d1c2613a73e913e6f712b90364927b50edd0c43118733b2a58a6410e9bb9246a9f40904f6e49834eeed20741ee5e767a127449a074f53c1494655888150ed1777e0a32c7f4153820dde82daccea473434a02a690f0696ec45fcc0e098028dc707d32b30ba451060582b8c2eb1203d27172096ef7247f1c33bb1ed4c22cb7c373be236ae689effffb1309f8270da6df34661e7589f3ae96a8f0506d7a9686296deccaf34f1cea8be34550b53b74ddce0cd4b889b80313a288fa07cceccaa06e40f3988f5f2015163e75f7e67ec52b8eb608f94abf7a833eb2210ee3be02b5973224128ecfcad242fc4e51dc8938cfd2452b8b520f9ea6b508c5c0da7508a13a925c49f52495b9234a601a9ab0e06859e9e96706b7d758879547cfdc34393b8248b2e6eadaa77840f26470cc8edcf04ea3cc70b1dd4067d8fd7bb8b5cf466de41bcfa51a5c97ebad6cac3bcef04c1bd304933494d0841be61739a4476534878d29a2a1aa926dbba8bf782dcebf733d956fbcdf7242da1274ea9b7e6fa91fb88285c35e0517b854776d368902972a0cce6ffeaecfa3e97e7b8c7ced054856838bcaeae9cc766f831a0fe26ff4641f1a67e026ca72ea105f0d444dbe0d2779ade275214259d4a39c697862e308fe01306a87d12754a97e25e2b91e53c6b71c02a0c5b4cc6a2068a790a18257721c57565985402041f93fced48bb3d4fd6adc2688aa1646249407eb40c2b554b3a33b73059013cd48056108645f81f8a04e8fc6bf347e5d9de1b8483a8503db140c7798d4fb3240becef75083bf03f822d457862492ec0875acb1faa4805ca47e5dc7057a66ace656688a1ade4cf66e044938f72b24a8990892045de82883f916c3fea91a314d1355ea40df85cc8c9144f3986699df62d62d8e049a01471b896b23f822d399a825c047248e7d64e32a80583f2e0b201c4417fcf55ca63009b33c53b9ba143cce9cc1a8b4bf4ec65493554d02bf64917003ef094906f06b3b8a1af1106fed03e38a16fd99ee99baaefc6bd1c451ac2930bbe99bf9cd18c4317eb1d3e20838f388287fde4d9a807dac6d1bd2e480209337e0096733bc9d09cceb390c9e240efd0bc51ed52e4262dface06832b626000d3041b91af93634f2f3088e429ba9ee15f2459b300965abe42419944d354ff5f51089caa5c5c26e439283a9a9a8186030fd9b2e15fd81feea32ae3da9e6b934f8dea80798c073ceaab5fe31e8b5e0e4b469cbb7527aa8d613d0bb943875231bc19b41ec771e3d8ceeb3abfabb83b166547a007ff400d982381f63e6c83bb2ad88ce3bdfc39d5b17b31e21b2c72b1e227e381b85cab8929895209853f3ddb28d9b2a6ebf2c858aca5308a9eb1f289e5ea09d0865ad9881ac204f42eae996d05dda6e89919136a935de6a83416f4d232ea39536cc0dc0794ad58090e6d14e21cb9ecba335956df9c8882434982679742d3a1cdea3a12998231094c763591c3562ccd8d38fed70de5c9fd9ec93c0fa3ac1ab3d260308f0c2c84884d660c48d2e600c2966b208f2ad9479b794791813f901a9e3c616be8ad803ea209dd994c619186c202a638de5c2397676d19214b70610a28001bb275358928000101055eadd62744858d1307ebcf821684fe728897e88dc76f61195f703613df60a2ffc45fb304fe06a32daa5a5feea048ba04e6b58281b214792cd9c7902113556f22c9526e6e3ec608affbf18185660d572328ec3c7462bd75c2a895994a95ed1ca9063e956dcfdb8512886a318054662a72e3dcd1fb468dfa19bf89a050abf6063573ed7fe4b1812e59a647b84456d09a9cb6c9e40ff3bf7fe097d9143ccf74637aee346733667c043c93af629f4ec859554688999f3f7c4628bc12761a12fd7bb6dacd062ae560f4bcccc9523f2a6943ec0cae60762e7bb8092975fb9a309017c5b26ee67c6c7768b68d2609cf37e83290a8382e1403a807198244d622e5062fbf32f8c218d9c163f233b56622aa147a20f31bc8bf5b7bded8ae26bb9680d55ec7e3d19a280d2183b130604a68aca8b9e62cddb9db3371efa1a5e848a11f74607cbe9f6bb5a9c8b141a7bacdb627ba1f81fa3cc8108c4c697f71565c738819baea5dedbd338dc5aade56838b1247ee26344c602e3bdf23eac7fd49649fd838e38e8258da39a443b6149b1024845f4153766bed1adc571950ac65928ed297fdd59c3807587d43406509e0ef3b8852bddd38fc8ea59220e6a11dcbc18cc93bdde5640ab3e7ddd32ccc33327ea85a19c57efdd8e1075e1b54847a69ae386d94350f71f2cf2c2451d6cc92d46448f89d3787f918196e675013afa8e7e556158928185901ea0a98ee499ada1749ae87e3afe45b174810175cad184fbd781b68d4efaac73610b9e27d7be25a23b2d6e1ec69aee963625d6ba6d4e106d25be4af5aa05c3b07a20bcb5ceaa4d9271b4c6bddfbf9752a040193a8d550064a998e974f509279694d3fe21fde91101d29810f4c82cbca55cc5748eee7301051a8f0aed67eae60f0d2d40e9366b3ce64659dc69f2cc65b0f5bff2ad820c9571742f52b484630532623eac135cd57dc29108333f9d036a79810c8f59488190eb1b63af671b41222fd8bd929c8be5800bff51dbf202df1bdc6d66aaba86a759b52ca0284301acd3ac7b88b5b9c94748579403619d2ba93c9c4ad42247b6a05f51b926f56db528786cf21b6ab778d8e08fc8aea3c3426e1694a812f598a76589e8e2711e5f9929ba459a5aa03e88e38dccfef898def651853c3ee284f9b41f1e524ccdb0aed98488ecfb0149670ebda1b8039059811f32bad5e5d88488d53a36217aeec0e04ea5358ffc3f4203c524397a0ee70a6d7418ed97fa32c4bd5c4f8bcb1065f2b28340b54e661197248591a328f8ad542e1a6b702d6746e29d1f78f3fa669022ab649538980a29c26178fb828eeec06490a3bd03d1a5cd79e97a5d1b5571808a8eac3defe110d4eeefad4c429ba13b110488de7b8d0959036028217e5975c89b184e195e7fac319c455e61ebc81971bd8783ed0f871366f890a8157fbfea46634e5e4d709acafe5a7ec60ac7e80315497f9b7dd806cc29719668cc252816192e71c491db86f48784e37c4a879b45f62834dcc5524a26efc81f4715ffdf800e1b77a37e1bee2c26682296aedb627826630256aa056b5d6f266f3ba86058ae45441f81287486db413fba83ec60bcca68bd783ffd69bb842d9c195ba312ad09c6c7c482da9bef7ede2dc76542a489c95c77e80877ab695781097c2508238808ff3330c23870aa69c6153a6e09837bdd801f39ed5a7d4a9666d67533bdc47b5547a09fb0232ae3b0862a45432943cc51ac32c5c660e264f4f1d6b3a74319b5e55867816288eb4c240f9ca41d700f0a65442e4a00d718343177145dfddb0f96d8e151f1c0d3ae99d2252c3dbd1af6e2ef1d456505f7ba5f716d53ce34934d7d02c456555f651504eddd1870a237134580b2aed0755a9255b73490115527739f0e2e535fb73a0c6ba2bbaf03f798b6937ad79a9bfa4dd5e8718f94c0430657b3fc73baf1403f615ff185edfe585cf39c29514d896c976aee982afb342913621f71ff95ab809ca9eda0abda80df6642eb89fef8c8b2ab435fe708a650fd0c38d906b1456e73646ca69be64d033cb589364431e21506c7cc6f2d34d3ab183109ed79c13dc5b06dbd79dfb5ef9cfdc2077f14da6262fe44cb33680a3983d46d5c7cf92013379c77a3b8fb37244e2e07da6b6512186cd881c1d4ce80d1454670e990f979545d60462ac9b273fed8d95a5686c94c886bb20df217de25b982f6ae2f0c2046a061b1a6dd7a161cf4fe5ab9a9c66909a5a2bb5f9d0f16c541cb5be04997f3da01291cf965a22f6adc4f3be1e175c322b456234f3affe393ad608cebea25cdfc983067e35d8f7038c101997c756c87d22954405c964c5ac2d3159a44ad191d710c1dd623bfb07eb27027de11fe71ad83262db0a3ad9824c5c53effe4bc153a46b5120e2a018da8d9a4bb8adb3907027152903fc10f9b1189501444a095de187e49e6a129bd0c407bf31a5654ecdaf5b1be52a72dff7c921ebf07ed9ebada48ba1ac7ced5bed395afab4bc0e48ecb69a152e231d22f17f8e29b79f2138cf94a62d7dbb562fffba833d5c6de153add8bc04e1e37ed1f6da2e082318f11aa88caeb159ac0449aa945d10628045bdc2f5945f47a873fc02a2b8e7082c3a93f23fd6c53f5493100185bfa149b51c55163fbfd428bd65baa8145b45f041d055781bcbe7f930ef768c558dd3659a3112a3be8655c2d15cfce50f14ac4369fab91df89993e3b9b83e398554832b030760ee8b4f0385fba261fceaea6e061e8b1cb4af290507743dbeea93ad5e7317f96b142c417f12ace44882c1f1026e0bcba274bad684e363ab510c74dccf7c56c96c91a47a5d47b7bdf62ef931ec8733f7c0b383c6416db4d916d8f22f57facd8d428152bd1c56a14e928ac1b9bdb6b6ba8c442377597eba6281ef97d31aac37d30dd1aac7cf1ae4bfa1ca263ebe30dd525c96de2e36fdfa38dda2c09510356d33ad73eec954a73b742de8684304b8afb7528f8a6a47a7cc16a279e316814ef483a6ca71499f0c138009fa231b67161b7d00cfa2f28aeb9eade3aaf7975e0a724e8b15b3c7ce1051f85918b99ac9c537313ae5912d1d7df49b25114b4491ba21dee073e0572a5a47416f785e7b21c34e248fd2aec6212debeaad00e392574c38271c75a196cf6cac05e8e02403173e2a9d00c93d67932c9d3965592c390134d890e81dbf931d2e683e979924612af2cf68106c3d583583e7258a4209e407f6e6d5e4e82e76024da5a55fb458812fd5d598a11dcd000d7ca3e975709afd8023f4c7525d65355626e1d93eb89a5c925dd75fe37b165007c4f66989db2e3a21c22521353259d6a4b1b4d26ce0f2664f7e61947fcb6bdf04880b5d5f5336f89bd8be90ba1a10b89f3b4aafc50f516e7be1d8d183101115526f8ba0816f893bbda43764f1900919e05f88fe55a6a7e6f64fc10f3e8a430b778843defd05b961bf42c9891e653fd530e0806bc67ac2e94eb1d5d228e9c5dbbb48d99e8832ece8b4a6b37081738252f961a8f0e237d44d4efc503ba3dc20c19ee776dc1664502a075c809c1973fe50b0d308cfd9f9bbfc27b8f4c6784528d5bbebe37963e4ccce56611ebed8392baf7318d84a68cae6e6c440368aec05db98b2e3f44b85e6bf4841f3bcfdf88e6066443c44347b21fd83d0d92a30ee0fdb9043b4051228d1972acd851831eed5e32f8f90e650cffdf34b0f23bd7c4365c22b4813e26ad4ad46793bf73314c79171b01468cfccbbbdc93b9527a8658b5055e27852f905338a233fc841f3252cbbbf054e294f9d02e5d2ad9a466c50cab5d7733ae3d230bcfe55344f39573f6986ae0c7ef52b3d59941cf94447164de77440c520256e24eaf68226b13f1141cc4e1ca2149a213b31db19f999cc201696281e5cd20dc093de36f2e77cce30a983f0e17838c24c508c180d1b9594ed12632f03a46433353411e32b80338512c209c0cc4a7c55367194659eac8ab6565d47ed6edd0a7fe10cf8831969e152680c0d4dd01ec1b453889f6cbb9771ed37b0e01711364ada1613e940eeeab14c9306a0340ed793f031fb5fdbc2bd9cab2dcc6a9968f28c79b40a5938fa5f238a8ca14db55ad90dc8d74e9bdc3edb8a93265b0f73e58310a8bbb2e4bdd66e9c97105a3a94b968e221efd33715c00ce27e272e5333779bb2a01255f2468a64399f10a8672c6a272078cbde8266d04f081d7274e6f5b2b52f725f060bcb9f249f723d05bc84d8f49a3f8c888db691a457144ab4cf80bd33ab2058d0353164ce200ea01d8c1f77af6f75f01ec98e8f794b78dc00ea1c4edd8b488214eea50f8c05023283bb12277612157ee8bdd53eb76afbe5a36dfb2f320876288a455b8c76205876aa861eb1b2d26fa7612b91f2311380bc30701b9d081e7394a3d59ed9c8fc063ceba7a116d0a5797acbc883b16881f216bf80aa5ea080d987d4a05bca301d5ad4453485dd442e82dfc307c0a08a0c3a02eb86a01bc422b659aa78db1f8369dd27ac1aa107f96013a91c3f6a06032e5f3a401cdbd214cf77e9355303d45af3c03ae1b640f4b1b33f8edc0ec9bc59741a72128ecefd8f3bd0bba5434d754d8d5249e867d38304cd9b8a4e7b0afed5654fdd69ad66d00f2d5418fd1e805c2c341fde95211d717907004441d90611a1b730a2f8e2dc9b7c90833cf3299fcf24f8d5e30937918d56082840a37b5dbc1c744638ca5ea670af9b61798e735bac9082b4ea41471fc2a0ddc569f0c8aacf4ee0d9b075a7afd6cfd089ffdb8c8b0ca200b0c181695e0590968e0488145d13c815e8a50e15b04225eac488786f3afa79513793a4d9cd6f52520142546b0a5a34d551e767b5b7d93b39dbf904fa522d965a98e92cbf868cf3e825d60956f7101a7877c1c6e1a58e26a95845c66addd40c104e70f9ecf542124eadddf5814206a9a14d44ecf1a8557434a230fa2f1f4f0306c01f2ab1a709f331b76f8e9c0ea4851f60d50cc436af994e9c09aa96bd8189b54c221cee4724f5c95ebec787644f824b5d11c5bf494d0c046ace143de538195ce425436b797749b10254c2383cc4ce471b87aeaecc37faaae3da7c9b18f5021ab9e8f98b624b02f1f8d297a5dea3af1fb66cfd76b46e99d18d05f39297a09badeefc5e7c26fa55e76b3d71f7bf1a2eab58277bf28f6a9055f08c23f8e5fd2bd5e43aff11afe8fcf80f4ba94df9310b9bf51e15b7c2f9cfb7bcbe965067b61b85729edb583bdf05c6f63f722887a4f4869102c67b00f16e57fa3f96c21f840b1d9d470424bc28d311b14d2299916dfc631b9ef9630ac0b306061115fb6cea116f5de04aba98941d8fea356e89a1e6985f9a0f30fd136279499ba6e4163e1cd5ee06f420ccb61d3a1810c563ecca1207cd3e2f1c0f72ce6006d1fc43cef0b091b08a5ad87bba649a9840414772a5820c47f900e0e5cebcfd48c854afd345c7d912053f5e0d51fa994b12a6bea7a279a65fd462ed6dd97f959334c1dc5a2a88367b94529464a42d400927b58e4f8afe5ef945e6ae646c628a4427abebb64f3d10cddbcce08bcc1e8c65ceafde959a05ea2dc3b428cc8310563478e2d1c33424cc15891630bc78e103b31763af566542f97ea2599df658e2d103b626ce15891630bc4881853182b624cc198916327c78e80de10d58b74bd39ae7703f4c28c248ae14b46ae89e85fb599f6fcf2e59198f8d68ab1b96fbb21343b598a82f488dff30fdf9e53dcc5b59aa7ffe5b6ad48fcf88630f19d6422115a4ded81527c51395711902609b759b96d9b71479f49a473b2d715eb78d60b8ea166a763a7737289299e5be4951b7137d437a4c565bd7bc4a51ceb8ce3369c43ab760edbd987f920638d79b08643aff3f796135385eb362912ee6df04a3c03bd6e73bbef6d737169baf429cce6c33f083c53e4ba4d83831be0b9ad75bf6d81c691fa42b641c0c1cdaafb5b792abb95aeb80d8169f156fb1c29b8b1f3cf99c5a0d470930ec0ed2140a669cd2e356ffbed76eece81e9b5593abeb1a2727574ab1f7b45646cd335d221a2e3c74c753c71f44c69b8e9cebaefb434c6c5308cc95017221147ccf1166e651d251beb5c0b81622ccb586664d2f504f7dacce39a0dd1ed9f527230463c00c42d8a6ee43642967e5f103aaa779a536f22719beb90b16a62e53f0fd816c2d06ba8ffff072fcbbfb2f3578d9b8270c8f88ecf1a9db1e3d0ec82d842c98f713b67991c1012f1a484d8fe22d98b808c93a618377cff07b921f6afa25e871361fe49e82c5cc1568afc502ee3a428e76ee7ce7397db0571507d18e46662c99a992d10db476e98d1a595b1890d7eebd039d1147479b58609816d74d36185f7c69d25e516662f327931505d5de136763066a0941bbac9487d343d78c6691b67da45b799d7511b5b8cdd3bc9f8a81d18dac1f851db655f1402a4f6ca3755f004c509f4a8777c8358eadc0b94e637f11eb85b4f2a53e576e0f75579771b742741d4c27ceedb6ec0fd46eb2a372b85abc7ea1565cab736e2a368c3e0980fbbafab21d57b43951b5eb764ddc42ef1c02af4556e8ec0d0024df346a1daf58f901c976183c9df2a4bd85439e6a319bf3ee4e3b29102e7dbd0a27fe446635b420ae42fe7abdc78c6b30b88f769c7aa2ab772aca560b6c5aeb9859b864252b577b7188eda75344c75be8d1c12af1b200ebb65955b72af06cc3b8b58083dbccacd4a5727217c11e55e4ff9ea6229c49a9483a93b73161c96ba49a814e3e0cf6c7513d70df589b9b991aa6def5b44ef454d5274b433eaba3cf0881cafa00dbe491323286960f5000b4e73aec8572a7c7880825186ad39257989b4571dcb8450f23c8e808fbf43adfbb93806680d1575cc8c11a342d75cd0ed7a84909c2c16b852b9bd0c8184af7b8afa285a12051fb001da35e31b9829faa4e7ebbe0017d4932d4d7dae9226d07da60148adb19e13b5ab3916beec6bec33453dfe3ed50099a2aa2ef2be900c005e2b4eb3ef1ce38e6bdf284bbdc318384ecfe7634e2f1f45bfb1c5363d61f7e2e95d1fb4361320200073633ec38990e637178db275cdeccc5247658d2577ac4a990174e380993d7d8598484b74ec70084c94798125407f8c38f642e755737d712dd135eaeff30c12824de61275c8b58c18a855d9bb95df2a76cb23beccc18deec112e2a162b24552ff23d47d7743797e77009f47d0f9cc33a3569dedca84a5a2d40bf57e43dcd9e37d5b0d15cd8e54237c759252dd3255324e44686eb87b592c5912105f45d0a1fb4aa07b9a05c1a8127147dfcfc202ed89aad27bc4553f5d0a67a1b7516cbb621b2a1e7b40e808916daae8c68ae9f242aa1e2d7c7323e8d4e64a247a17df1e045220dcd030bd87806b2f326533d36281162b2a909d82a3eb3ddb9fcbf44886ab2f4e1e22e584e4bf5c2787ef7c84f180156bbb284c53811d075df6da2b3091347b6803843bac8846f0463b618ae67732457ef221c1bc8a78282ac3ab326fa0d4f54808116ee715f5bb1a9e310887074c3912f3b552c2058ac19013d2cffeff298565aac0ad4250cd9ec650c556b39744ac4d46633ce853a1075aa8a0ae0fe86e213cc652f88d1b0b5e64636564772f8369591f2efccaf283feb7386a3be542b944eacc557436a507530794471a0a213763b2334a842c2625c6c5a3639869b1c0a49bf83b7750b1202b36a114b8ffecc16ae632a832e4571777b479c88e6b6cbdd1a5792701e37d3f50a91ab10da7256ea695b27ee74766889a556e3b4754ed8df883091361b5788c76d6da2a51e3bb2d71e447c8d03a8e8a1cd73ee70e2f75652f314af0ae98c5abcb05fb57ade3b17259e65bca107805045814262c510da82538b336fdf01d1668891012de9566584ecc00c8183fab4e84583d795ceb7edfa01215c39c7e45cd9937e475725d172e22b31ecc76f2ae655cb6bdfd2426da81d1a947b39652dd6c883564813511a8fe2f4fd51afa9130053657b57eda9eb2b612994862a1385ee5fc287940f49c1d1f0d5b5d1acbf4a1ed7bb9ba33a48c239e8b9afa6f9e2c4623949cf9ddac169fc384b109604b2b64d25dc8a90319cb1369c7d72a87c61e7cc97c13901c4028025cb623e3d72580b26ca78de40982d25bf210ed1949e0f65af17992a757ac1c85f7a346b843d954715199c4546ce6050aa5d98820124fb5b847a88cef69617b9db1de2cd27cf106e2a6a5fc462b6eef2ebb7b3538f373e0a0fcf9ff4080bb6f028233a61e0970609355113070d008c09f20faa3a7f497460ab72030cf39c1113e2012e8ea48e68947b92e551f8592dfa05b47c4becb84f0ab8a43efac1d98b4e985a85594876a5cee620a3e919767fe05b51624a39752654941c1c29889d5931791c0b0c7080d1d8bd38bfa5578cd31285898707c1dc46889e139607c946fa8bb8f68cb98938923b08318de5dc506d34efc95f708a92ca387ea3552f024d6ef5bf466721dfbfc64feef61104902b74d9636f0efc29e885ce0874bee67d1467915c73475fca789c4824b1a5f615da81935e557e192e9971cd0e6fbf2c4985f636e9a3aa93eb4306b544666c93e41475d376fcaddbc9127e5f250ffd85e18d7dfaa82e3973ca42b0f1cc8d924db3ab2886dae2e56751af5a8256d08b702a4e288d9079eeba1308e3289ca48e1174c6027e119d7a4fc6d691bbe1c238f59d00cae209cd94a293302afb164aaddf5ee17429054ac29143fb0e2c545380e2ecde1c126293cc176da38ae50a9da4e84d9e72ecc09f82c6b9745afe949da072667e4f77e026ae83c88e9891b77554d5c28476b2e1ab34e523ae1a1d221a66607db951cb73b97f614ef8519c81d1bcaed3fd60a8f26d9f01b2c81aed25906219ca2e9fe0337083aa94e2947df097361a2d54a1a9c0f85e2ea9fb174d8223eb281e4dfaa0a1d76d63d3066ceb827f092547cb564a1a542913c97762056659ee98b457f23bc66fe1686da927c9858444bec9279e7e0e1fdad92f31739eeb90dc0a278a5399625aa4f2693680566d1699ae9db2bf8e4028c527cd61bac84de1f7cf86a5dfd807c2ee7b0ab8507b6454a1ce1dd75ea64a6d1296e5cc50da53258442afa8fd60c576890856d040d24b8bc791f8681b86303508111c8c3b68ee1e654df638bc12b6530d0f40619ced8b7928c531e5a7fdc388188a55f629d9c35d565c6fe8b22ad0178c1e5f913bfaa8f148bef31bc131e6fa07a797a91267cb8b2da287620179f576cc7ce8a35aa55d8558c347f0371b06c8cab2c02cac8f6163858fcd8b609e8873225d0182ef0c8e3c90059dfc8b1510086af699f8ff1d1b5a2873447f20bbb2b7c976a331ca685a9fee63f479cc1434224fead5a24797e467061e2ac786d8387233e2198dd4526c38c2adc080a20d607bf4b0b8a61a51f8e716b4f41d00f7b7f75b6214d77cff46867cd58e7933deb89d5ddc7eccc2a8eb26b3e0cc8cff5398d06000e079a5dcc3f43376d260aa2784d3e487c11e36715a1251e89df3b62295ac394e081f98fa509ce666cdafef4ea66766d4597b108c2930ee2b923a9b8b30c3efd9e3c0b0783ef0a09464a1a09e9da0dcdf828088bcf941f565dec2e92116b8464a347ef385f2554262f403deb0185540e1eb356453408dc21f3b6234151578daca2ddab3d061e9558397d44fb032132c383a3cc1d5d45b92c86fbdaa79222a8950f672d90f1660b1621823526cb15879b0b2b7307f89dd117885d66a6c6a48ef3dd1057ee2044f4230bc89dd14598f718821bc687cf01405f3aa368b01fb76393e29bc23927f1b8f695f29ccb2269ee59f976ff202f31afe6498415394643d70569c7b5d135fd87013d0b17823a56cc46c47117e9b8fffc6a3d072069a68e1091167b30a7554f8e3408089b40f6694578a4b25df236ade565134d6e14bc69d1d1e7ee50ccc656114318f698c9622e3f965fa3652e89d52795e6827731b1a196c0363d489617eec9bd244290b1696c1d07f3898a75fe6746473b22e530bc9e00bfd03a35916995658a67dccbeef4d8e87eff43fda983e6debb60fd7660b811d1ed9cd13a73635c0ce8a5b5c1e87bf08e9372910302a384b3cbf6274c48ac04ffe617c5c541822c311a49642872992e87608868570e7263b590d334142e7dffa94c24bf75b804ef851631294fe65b9eb3b2691442db4229cec2bf546278f044ae470eb400ed18fe2352abc0601f190fc5b47c8f63f8ecf9a646ae27beda4d6b157a655d8456bbe7f9c051dd05ff8e2bd2bae493aaedbe208887749ebedb66ab6d04c8d9d3f6e249489c9d25338b6936789dd189298893c4daa62befd511aef7564b6e24744458495402f58bb833853e82e2b9ea7425f8ae58353de91832c735b2cf2c58e4532d46383d9d3aad6ca0040b5bf3e00c4a7f8c1090d9d5bc6cbbbd1f7ffe3849f3c2510954cc2195a1c944eec7e8893b50f6857bf2b6503b27acefe8b3c1412abb3cec5fbebcc5376a2579e7e057030f94ce65470fd23b7052db29014e840b11cec2296893282cd70988becfaefc6466464e07275b6a984735a2724f50a4b1f07f8c6d40000fdad4e359882f604c1e04972eff183443941b5a055ee843dc4904c738b0566fcd724e2c22c3f0462dd23c27affce65bb3cf2b65e5f4e4d6ac1f65dd6bd2c00120e81d4c1d8f60c634d85bec8f356e16a38c0998aa0f7f1b036614e33f3af120848e6e14dcaf2d6a5dad306fbb0ac5c7507b274bd8a276da9a3da290848f541daf330c491a546ecd9ac062300b071c54606cd0be359b44bf116ef7727bc716b5b38c84efa0dc182f0ef88ef674c529cbf70e2e5be22b57f3175875e3a8551848ee0b26662e66363084c2163ef032bd9261fa96a2be14f5be41ae8f03d914eea5ffad597e33ccbce81b0f81d53bc98b871053ecb8d8eb293f3b27fad743da04d4e27cac35836c356ef3aa9bbf641e4e6f4dd3e6890b0962615b516c165d7d53db60dcd626dab91ab6689ab0d9b1355664d6def50c5df31b590dde7a1fa137827933b3b539f0ad3b3f0f79b2cfd66bf22fedf7000add9e1f50c3d00d5b68b8dde43e24d2fe26ed6590450347775e3c28b0e6ce2dcb2a7c6710bc17f7a56a141339a3ac8336ae4996cb8b5225b933f550de36c67343f27e0af3b4af15838ab010017e404c5a5e785154a64c46aa01afe618810d49f7b7d09574b20618c05b1021d5021e4d32eed685a541a2af199c2f0abc772a7151a03ad59ad16cb7d096cc10b3013c03035a83d2875720988d750e5160a5643bbc1c21c5e585632fc74e0033c7f3738044d3104854e18765447bb35ad8f05544f909e0605345159074bde0a14e76827d22e64f971b9e1dd16fd950fd4d2a2e9745db768d2424222111112272cb88082f0862087bd4b1181483b8cd6a5a5efd60549a5a178b70d0b26d9a961f46755d16960eb0c4dcd29595e7c04ac7a52a7b880aa87c6ae36e8a57a8ac5eb56fcccee26291cc8a8bc11988c19cfeb2085de31d75deb1064098f63b02fb68c538f4ba48e32fa8a7f8c11482aa0e77353015bfdd6ff75a203c431735017ebc1faffcf54802fc7664d7fcf1761d3d551bdf09f0eb651cce3f5ec689c14ccb5aef7e3a62f0b7a37ba0bf5d10b9e2b0e5050067429996b3d851f6387f28f02194dd9c7b6437a33add7b00b0578b97cbe5224b69b96d635996db46922ebe21b043add67d7825b0811e2200011e767880031a20cbc9663194023bfb649ecc9381b2cf375b5959a962adb4dc28b79c2a2553696229ecb5f996ed12bbfa631bc5ae7e950d825dfd29bbc5aefe970dc3277ac5ae566157f31a39ec4efe4ca5f1dd07595ad247c68d53a89119c9de5b84d639e723b5a0db63b1989bdef6cce7768c096c0dfc0576105fee91d7580be2525e58643331643a4d6b77bc9e8c36431505f142333f663142662a0dfa44b9674174c875d1218d0ee9139526d56f50ee184aad568bedf047a88645455927bbba17969cb293556a2ab6ac83977979879a7b2b2b2a44c020ac685ddf079ff4525e58603e7ce283952b5a41159546f360c35a2d2a0daddb782bb803964501bb154cc06e5d8180dd2a3a80010a8023c06e0173b7fdeae57e751de337145c2d7763f32dd49ef0a769f96d763824140243a2ece67a425fe87371a6a55029540aadfa88888b951796172a0a5c04f135b68d17d921efb26d7817dbf52f7be653f68baf6dfd2a1bc6db36f9b21df3b10df313d83720a476fd18825a606be083d8a10c0676d813aa3c36e47981c34c6a01f1498b0f62639991e7fd4ed3425c1b5614dc306016b2a21ab60c19d9ee59f03d59153260ade6baffab558f812161e817333146549ab2b7802f3998cf072e5ab480511acdb20514b6eee20293f22d001b377c6e07c00600d420638f6f0a200037d8b0010035c8d83f9a214164e0c6e607179934646b2fa3cea8bc8c4dc61f9549c7dde2e5e2bf06ac62934fc3c3ae4dd41ae3975f62ae4ec8bcbcbcf494ef00d860e373fb06d88b7fd8fe51167b995fb25f037f5df665f0f7d9df9cfde7e3f75e4803b21d6c268659bec8902123c66cc37cf804a66fb952cb58a188612b8b5006ad3e3f6ec3784aa1d3e737519fdf6401d6fdfcfce68e199f0fd8972b7eb4ba5687e6d6631b2788a2504717eac21d35dc85bbd166865a8118b1cd0004c868701a336868e12d204b76f3d340811dd393d3b4dc0d34a83368a0a109799b5ac3f5e5d7685069f4bb1ea5a19911e25ad93d3f3cec871f7e7fed5f1bf6aaa9ac32f861c35e3036305ee010170662979615ad60ad861a40fe45877afeda5649d9ad2bb29bff65b78a5876eb07c6add86a660d359868014b5a2d2076c126b3fdea1440516b905ffe893a5392904c6093a8ac69b2ecf77a7e00a834b5de0afe7ed8aea6e50780fad9fd99b4dcd9069ad4c68c74dc58b65b5664377f6cb7829cb7804e04e0c4c44609cd1d1199c96236b80d550a65e30462c3c6c61d86fb7e17e9e2660f211fbfece6ffcd581c6f0e935802c5b277a5ebd93c31bf2c1f4747b18b93d764bbf98cdeefd863759977e41e0b64333262c08089e932371c0d355ebcb8703941c9b5a0a1b2c232a3a5564363abd93a3fe6f83429333262c8f41818182f5e5cb8b4c8a19cf030d81302350d98756a581416852191da130e69da90c62b2aee088ddad39aa6a6a675b56ddbba5c2e57c80cb586a6060d346c2ca3692ec661d66630a8866586ed8623f9d72c3915695a50d3f289e84454b306a194adb255b6ca56d92afb8d888c466a0814029d279aaaa3119113455395e453947b569f572c168bc51a09aaa1b24bba222123ca3efbd9e71f7dda75dd1315b31a1a1a352c336a6c34b5d597aa2a69656fb80d129cf91a5389e7ffa769419a964d4c5e65b3456c3068b73ff375c5bbce39e731d210698ad1290b78253fcd3ea5ec13109fe49fd918a7bca72a3af0c70f994c2693c94e442adf89482b2132e3e15149a4952da52abbb42be9d8a11ad7af562ffb9a212420c90af5493b4de3019926cbae56ffbaf95f35766948d3f2d3b04db3e483957cb0c71857823dc6f193ee56db3319eef5932e08aa0f4f510dcb0cdbaed9a12e085cb46a9a1dc6f8aad26893a6a626954aa552aa19680dcd38b369667ec6386332d382724283a3eb2ff79b7bf9278fa25440df58f333342a3456562c884e27d372c957b35556645afeda5601b34bd29176baecfeb0afea47cfff238b2c4ad364d975157a4b256785483a2d3253674aad4d155005ecf951e6eea9f994dd43f3b54da2b14f3e359ca4936999e6479f5e3faf3ef6dfd1878997ddfc33b64947a6e5f7d1cfa7d925df2e01adf0936e89f055f4cbd41ae6971f53674afe9adcac79db26e9b29b7f66ef4c999a2f8146a3cf5fb35953d378ec0a049a9166f5adb08b44feccc632e3c63d44e1a25ae3849f7033bbe159f20121f697a49b117b2375a634495f8d9047c8a7933aefb29b7fb7d2c5c8069862e5c166dc431cc551ccf956f907ac786a0209bdeceb0ff4e8ebe39b9c02a3ef06281ff1d756d45f1bd73e42604392e43f685fd74dd480045dbf29720a685ff721933be8410fa288620843d023146798b53892a5393ad15425ad6cc9047242a5542b56ab7dc13e25e2d1bd425c6d8bf53b44f3784621a2952a853a816ceeca3f1ecd03319578ac0d79647025a931598b57fe8269299bb1c2909fbc99c8aa9b44dd4794ea23dfc8a7eaa98ef1c90d46521dfbb0cbebd4a16680ea944cd754e7d3443c28d82c92e6d9aa6b0904b5572dd74b64c766242ec949cd96aea4b0165de6c93ed907140c121a22d23e5d16d2e9743a5d1e2202068b5e21aeb6e5f3f97c3e966e2572d3112cd2ed32ef17243444f4fbfd7ebf1f8fc7e3f1789ac8e7f3f97cbeec937d1928ff72d02a180c0683aadfeff7fba5783c1e8f873af97c3e9f0fc40457825f18140a11017f3f1e8fc7e3f1345190a828ebf22ef33e356aa833218fb6b60d2b72d1be5df952092421d2e7ff0dfba16c98cfd07a025e122c0fbb0ead43ebd03a944bff9a3ead0e5dc96e754c76bb6b79b5135b0d5623647582869465c77e65f357d9239fb25ddb16f9da9e3dca969dc04c5e25242ede83c3b75c37c4cc88d9ae87840e1b168415c17e6cd86c08dd8847b661bdf3f3cf8c98ed5024d25e7a9e0f049eff4020afabffa6783d7f8c973c13ba56d7ea3e87cde2b07fb5f2695cab6b75be56e76b75be9ebf0695a6d6dbddef460de5ce0098316da8495b9d7667a3d5b5ba1b4e4c6cb081dba04aa16c3881d860437b636319b3c562bdad0fd0ef39d0f2c070001b42dbf3016c0c3b14b4c0892cf8189a420422fa1b7fa7d019a469f93c9f80dd3348e34e227d06b3fb047c3bc180ddfcb93c8934ee2cd2b460bea1bb130cf8869e278a368153509ea6a12808f00d45cf118aa2a84f0d6e02c3f48c7e908fc6e56cb69cc661191ed14479f2cd66b3a1e8383245726c8d3f5105e8afa4826fd42b541cf5691c47335467f0a8dce9e963ea91c7c4a70f0dac8f847288519124f251e7113a76b19f26eef4c1e7121867bec95799966fe1bf270dfa8a67e5445a8a7af575c117248ea5496af171ca103a0e752ca94753e8f9df949ebff58ebeacf988e8f45dc189d72efddee3bf4996b0d7fe90992c36e2922449be4abf3f64ffa3d905499ab5bea2bd4e89fcc869349a9a438467946eee104576ffc34aa39a432dd51c264b52a2995c90b4b45f5fce64b1d12c8de64c167ba29cc962236e6934cb596934792f8de62a2d67b2d12cad541acd8372c33ac8d644c122f3070dadb75c037750ee5737495e933c4a22fb9349764281b42127970b55ee16cf3c9eca3e1f5607d93db2d72555ba82a5e55ef555bb2a77ca0c49cbd2da843993c54aa3e98ea839561eb2b4dca61e93cd46b3f47a95603076ff839840dc75746922d873ccf79e954673642ccd4aa3990392924909253577c89722bc347211354769346da572eb5e12e13f93c544784cf6a6ecc798ec5191d94ccd418bd17c3e120de4242ab964253401d99a8a7c8252426b714566b218dfdf026b51a5502d279096965299b52e455f480e7514093938418a874e4d74e6118ecf369a40b1ec7d511c45918b98d439d4a41fa285a31f623dcd73631954dc235d64267b943cc958e71d255371effe1d355371c3faaba3672aee90eeea689a8abbeda89a8abbd551522a6e5647d754dcab8eb268291577aaa3fae8e4ae79eae82915374837f5524755a9b8d98eae5271af1d65a5e22675b4958a5bed689b8a3beda82b1537dacf8ebe52718f3a0a4bc56d76f45311e5a9b8c78ec650592a6eddd1592a82928ccc921c692e96196d2c333e4953866ed63a67722433d90514bb10c7552c47a32fc51f99a3d21c69ade6c07d67ca8c3e6c02e3469f3fecc1a34daa3da98f52e4189fa06a8df1c53ff58f3fe26a0f1647517c91c1826d5a8fd98c82dab116cb91598eca721c455d7ea9aa39f446bbb84fa6cab2e10ef66539fba80d433547598e4c722c47a7788aeb0cce52ad316e41e836ea588b5a7cc9eea86b9ecbbd3d40b1ecc566bed54833df5062b2e6016be6db49c73b645ad6a1877c13c51e7610ffc5443a88e7a9cee8208ae20e6adac33e7bf6ac3262c6b7f5919b943aecd043e98a6ef7a1546be05fd7fc65946f25299eda5cc5d0fa8c1da0a90e1ab7f2989f996fb47cab95e4c80caa79240514cbb21645169bed259713472dfb3771c7bc4f2c8b92651a6273d4da2c7fdac768a2a3f31ca1666a6a9cf926c62ea3ce2614c4fd8d31cd54a2d1286b64a08f5fe2957422a9b9067a948ae27a42ad4fa0522a2d8a2b90164531a5e6b44a5573ac4e2028558ab10b08ab340a51a79297af4b1ae5eb92d57a8c5d5261c720a6d625ead104c2ae2557881e4556d358b64452d79097a84735871ea7d03306862c40bbc863709849646753b506aaaa342f18ec2449350d89ceaceac1f8b5ad4afd6c26228bbdda1d92d2aa3d4c369bcd669ca5fa94d5f835574a354bcd46556af629d5aefd48ce5aaf62a55c28cd8343a5607bd44f28fd43a91a28758af9d6d63b50bad743853f3d654235eac69dba445fad78abf58ab5c640078fc76ab130d8d9ed5a6dc947eb689dae7599f82bd67a582effc845452e14b647bedd271fdb24ffda25ffdb7dd7a67d6b9bfc8aef10998a95a28d680989bbc51f7bd4fd50e8132f906d206c522ed72a64b7af58eb612f16f521dbfaadceac1e0d59970b3a15f9567e8f201fd915c2798a3cd462ed15154444d98a0cccc0907d3d5767628fae5a80f9168b384fa6b15a0f53b1c655ca255369685d2c6aa55a2c910c243232eab3367df142052f445ce9cbcbef781959b1cc3d6b3d8e679ea6e55bee696a4fde69da2ef3f4b29b7bd9977d60290a723ceb7ac2224d2b2a29f9de15c16049084fb54e62a9160b65a5a6e06db1d6c69d87ecf61583bd4cc54ab97a42214dcb85442b2e0657ab7fd7b5d96cbd1d2cfbe8485a6e0edbe39079135ad7d716af507b2cf80bfe82bf149566d457564446d02348abacb4e0161696115e143fbdc272c3b79616d8c3cc3d6bed318a9c1015d8a5fcb2843d4b0b0ee36eb7302cc39206fb988a9572fd2075b08be601a45b6e399cfbe1d499302c7b64b7242df7f80bea1145766b807d39df541ab58b411dec8c3a392e16e988449566d4617cdce580250141c8442ef45191f031c911ea68a186bc9017eee8ad541bef5c50d20fd86437bb68b958ed5027d495a4ae8d7b8b2d5e71ebb9fd8b187cb9bdf4a0c38b5a437711fef2e245b8837dc79fb9675fee3148bc22d3604db0eff8a3e77fc91d72a7f579f579cb88c12f481869c8ba1e863a433e7f71f47fbddf1ceb76d74a5aee546bbfe015cfe544d7c38069916ddc637cb71ef60a51b1c655ca95cb7dd8e6b618c429f9c32c06dba892040404cab717d543f57e40ea0c898a9e89b24bc2d90549a952e30eef704a46c6c5a37e5252b3cd66ffb2b37f4d11940fe5a369d91c19f91820101010b8ea3dec849db01376c24e1810d50302c290e479a2a95aea65598e889c289aaa249818912fbb1904e494b94ed3769a9679c8150f70c5d3513b503d5293689aaaa415c8b7fb5fd7755d7714252b324de35455492b3bc2c8901153b3c9c430e9273125304040d89fb01376c24ed8093b513d2010d5232149201010900710105069576ed564977e25a0122f9552e515a9073ec90fb349409ac6629de7799e67ebdf6ce2c96cd30ed8025b600b6c6dfc0404ac91902410080858eed4a49f94c400c190e4c9080353620202026d561212a152c9c64a645c299780401814149a4bba2b1ee0f8a27a3d9becd350df27a1937df249d5345555f55f36ff480fb357247bc593dd0cb4e2b1e2916fef4a072b1d6017a013f05ccff55ccff55c4f1210b0b42301cd50247d8439cf5cb24b57e093fc260f4f3fe12720a0490c4502d2b812d04ca9a2a87175d22fbbba649378e093fc268fc6c5fc6808c7a88000a35a01cb1e18185eb2a20404964f735fbf9addb1bf24a02010f8221bc088d144d9b4549a114de4688aba24b7d8cdd11ccd31ec23298aa2288aa2dee18eb0d76bb5d05047eb3c4d20a7136a94cbe552a988df544aa552f926ea910c7d27adf589c5865a49ac4aa55267c42742976fd25a9b4c5c17444d49fa572b75466489ba2447721445566bcd8aba54772c5b42cf54a561b592608620454d2c40b12c17bdcce4c89cde15e55b8e249f2092e410890579069c42a449f209f84692580801ff00e34a1d9927bfd6a14b9d92889ebfd491452491c669269ce1083d8b5a480f15699c0e1a591d4406356992c17cd3a5d6247906120b732472722c459df58f5c89f0ebf9052896bd239e7cdb61171f19c74ea1c3cf21d77c24caa48edc4ed6c959f37e9aa7356f24227b644feffa4bf68044d0d8cc3a9f80ec91bd2c946f239e3124918091af72749a216bd555630ad5cdcd962c4b96e6e8445595b4b2a6d4dc58c6047242a554ab76d56a9144cee293fc614bfe505573e456576bdcc2d2d54bcee2d4ccb7b66371cc24153c433e422e4c3194636cd7cbd8edcfecf169b6f93cec1b9f53a025b09bff1d77bafe3107be957cb29b83300e023f8c6b0010c639c087710ff0c1b81d703ca8a4e0606bc5903143c3f275019845d1bf7ef1875d6aaf515e9b7430020881bd6e2526242c2d394391739e028793d7e1e1673f11a6cc8dbfd1dec6ecae6f59791bb3b73905eaf94b3ef9560ae525f0ad5402767d1a570e95ba8071e6094a408d3379a5a27c33794a59e09b9944cd757f35c3df154bc35f569743259fe963d2a139d5a96bc7c8e988b258256ba76117d8f75f3b010fdb0a78da46c097ec033cc92ec09b6c033ccb26c0afec01fcc880afed05a4a83c020630c48005b4ba56a7ddb53b3c1d04d86d6f00bbdd816293f1584ccd40321c66d2c9c94ac5778a1263c2a64af114460021b05dbe365ab2414cb68a84656db16431111873e3144a4446f2ff335adf7b8b0c994e00c203e149c234641ae29ca3ecd6db36ab01bb8787c00edb877fc07ebfdc2339046bb1162b16c46127071f5e09b4be16a8d585ec17cffadf2ede96b2c957d9fa5776eb59f6ed4d36ee49368c2fd9b9a79d6c57db82bd64ae964c46027b959890b0b868ed8ad6262a4daadf2076f2215bf228ea0cedd1906579acce903c8a928e7be6c6e6e1a5dbd96512deea72ca8eecf67a59891435f3f0a8a8a83321fb7b3a9ea605a97cbba379a0026b5778bb83d669bcddede0b56d78d8b6f1b40d802fd9353cc9a6e14d768d67d9347e65cfb0ed9aff1d80afed1bdebc313b00660460c3826ed830201bbb6d7576bbdbedce6e69d8ad8eb6d7eeb8a1d4dc70283ac65b4ed27287dc5cddc499c0485e252e5a2e0c864020cee4e15fe14fd33290a6b99ce9d07a025e122c0fbb0ead43ebd03a846b27a44bb9532e50fd2524c5cbb7f7810f74495176ee6bbbc5ff7671c096807e96cd7a93ddf224bbf525fbf6b44d3e6ce3feb561fc0e3b02e50621f50e040dd8ef4b60873210d8214f4fd80b7d3e38cca4567763f3f0e6a6f971cf7cbb65bc6bc790c0c63223eff33c9af63a5e874205e54e3abb1d9e8e5e7677fc0065bb7c6dbbf8dffa6dfb4565c7fcca269f65b7de64df9e64b77cc986f1b4cd7ad8cefd6be3bedca67eb27b70f8bd7b72c0e1cd368716c0bed2ed783d1e1c1439ec8fd9af23bbbcecee5ef6eb5c6c1890cb86f9ce162693c9049a80ba1d5ecb6e75b4bd1db8201fb8c8e55e945cb8b8dab1ec312f5e5cb8b8dab1ccb56811b2651e65bf8879dbee2f2e5c5ced28d3635ebcb87071b5be31644dbeab3325dfd51ab1d31ec53dbdb179f872af2b35d7fd5fad58d605030bd9f9ddfb40b033c912131296b1d378ab8382f52ed419f25da835221a9386ecfe28fbe56bbbe53f666fd6a7907fb25b2bdb7c960de34d76ee4936ee4b768ba7b95ad7d0abcb7ee4db2bcba2e777c1b0a2172fef73c1df5a7fdfe7c2dfe7e2efb77ce4e6adc56e77b8ddeeb4bceceac86e6e69b176cf8d57fd0d1d09f410811c70d8ad4fa5197bab832264533e863a637e0cb546ec278fcaecddc3030fbd11c596fa37c56b876839d0eab4bb76a7dd61b70fd8ad0376eba369f91bb05b08ec36864a33f613deea644e760f0fbf770f0e9fb29ff73a5ed763f2c071e0dfd33414ea6504c54847b49bb02018900904240993c96432994e3b76ad4e6787a7a3378312f3a281d1843a421e2f44e63503362344c64acf0d9d5d76330e344c78aba3d1137383867a437669c0c0b4583458215b3fca6ef1b5edf2bf5bdeb65dfcdead4fd92fafb25ffccabe3dcbc63d6dc378d826ffb5597f636399728ffd64f7dcf8f1c69b306e3d24fe89f7ed4080ddfcb97f6bae16c3e0adee95fde83f5e6cd80d1748f45c5afc13ef03c12ec75dbce523c7dd704b0bb73aecc2aae92de1ef5a3299b3b9e5cc3133a983ead47dfd2619c62e442644bf6c35bdfe2f29bd7e57eb71584fc44986f4fa572d9545bd10a9fd39ad8fbdfe93ca9ad4c7c9d91f7b21704278ceec69af82fdeb6757aca9f442ac1c2767e4679fe33eecf7e310719259f90b81f239edcb5e7fdab35ab1ec6a653295488fc3e23839264f7b9c9c939f3d4e8ec8bb9f43f223b1fd383932fedfbe10b627e224c39a56a6528984521f2765f2238f9343f2229f73f2eee3e4b0bef539a96f61ff42d43e47f6bc642a9148eafa382a2e044ecec9937c8ec98b3c4ece2af5accf51bd9ebf10209f13f23252894422a9ea9aa59061dfa592d615c5be8d955a7d8e49663da11e07f5383932ecd754a5cf21fdea717242fef538285c889c13fe93be09ff19dbc7c9297deb7172549ffa9cd5b35e88f573b21432a817a284e3e490f09ffc34fe133e4e8ecb7f463fc27fc4cf11e13fe6cff8cff9383932fef3969ff92ac67f5ef443fe9cffbce49b9bffbce69ffc9fffbce78fb88bffbca317b9abfd17ff79459c9cd687f09f77fc540817c7c951bfe56c6e091f2787f42d8e93637a16ff41717256fce7e426ff1971b3f429fe63f2f424833a719c5d880ccefa3932ece380709c1cd29bf84f99a37e89ff905c147b38910419960f11e6842440c20f2464569e43e23f23c7fc47e45886e3cce811502c7b4322971b3363971b56afbae6e9d8f5dfe8ae5d6e5012bbdc8094d805889bf686ecfa86746217206e525df393cb4d9862979b51d7372512ab6bbe5af54d5ac22e372793ab6bee0a41b0cb4d79c22e376bd78f6f545df34f61979bacc22e3767c72e3726167601e2a6d53597e91676b9197b8b5d6e541776b94175cddd10eca21fff0bbbe087696eb256c1be210f741cdaa9818fd0140a7598e82106e1e80f95c86e8e026757f7987e3637219dfc70470f08080f7998f859be399680d7dfb057018d0bb399721eeb16581cc4fb11f2b061fb88895427bdcdd94b4ab8bafa3662d81b7bfa366ddf71933bf920bdc477503bcb7bc86e66f59533a0ec24ae437633ac939c002a4740767b376a1ff11fb29b77d03e7a1b54a853eae5dbac38aa3ebecdc826a493be02245ec3aaa7fc86ec3ea99f5c86a98f4edde42f692f45206e5a9de42c641f790effbae6af0884cd4ab340e837599bd01011b0a743e8e79bcee2b83a7f649a5f66298c4006207461471547883d9c48427f6f4274c8df100b4ce8d9020d313addf99fdd73f42e7364feaa3479f92752081a90cf8e247f73784e784007e3a722af0a3ab420be9945fe6e80064690417f3100ebf9c59ecf50093a1e855a0ba8ca62710c514861c50944239c4c2a78fd0c21832e80c1054350c20f8c85148eb042165608821145ececf4a0e7033200c9600959c4e4a0871048b0848f0a2a28d259ac4122104289ae9ec085ba1674ddf5afcc54c74122a0427f4fe2126418d9411142c8c1042596b083d10e100b784cb153869d3144800c425674a0035e3022099211987085154afc7e782ed031055108c690043304a10945aa25f0d0420c7aaca08a20c8e0c73f903a2e8219ace8afc86618acc45f3d0e478e048bfb2a872714610a43183a83ee87c515473021031d50218927c4408e3e828a181101054664b5e3a022783092dac7f4a5a424824290051645f8f8682e8e7b05e74813e10136979fc01a0b528ff9e3c764cc8a3d3c50451745511445f1c4b22755c73d8668a28374dcc30343fd65cd0f88a1bf6effa1448e2254c1e3033a701449e895000a2a080ac11570a0e087124ef498820c51e011061c4448f1430218b620039d1364f0e309ec58400adaf1d658c7411f00fa427fdd1e86bf02820e9228a282074800a44326eb2b3afe1b722f52c8220965500214b258c20713f6e4cae9b03f3828fb60f34d83a193bf7a57db7ae08418e8f039438f0910c8906f6a1c890f0a1e2888b44084840cc957223fe1ce865f80b182d0117e30c41267e0000e1d261cf1829d202420018bfcd8ecf986a530010c7aa884d843949171a1cec70baee8a58e7dbc2088081f28c9d3cd8ebbc77c8d33f929f720c20452f46842d75a466bbdd27110112de8a58e8388e0915923410750b6b16ca8bd10aa3328eccb66dc9b3d7f0d6ebebd6bcf423dff49966fa10e762c8557878ea7e8a13a13453aaa334c2851a6e44bc661991de4188e23cea38e91b00b0afbdaf8e9867f45ec7afe979e35a98383c25088a5015d85d623b0f871b926a1679a4f927b14ba7a884dd32c4d93b4b1e462325bb2e56a94b92ff43e8b4bb37c5bf9245647e38ed5cc44267f6db4d6da1cc7d1dcef775307f3b3b9c52f4955d452983d33f7723e4287c83b42ece55ecef9847198e1c8c3180d51fe86281aa268c8dff4b34dee2219a2e2a3e93a9aa31fa1288aa61fa3d3d19350344d47afaa6fc843b9053429fdf32cb14f1a3fb7d0d193deb4472bb621e512caa24f52b94dee293779588458f2971c7b4e8883cda40daec083b138922449923870540408d1c957c99eb7a8d2d0785b40d3e0d09a7cac317fd5cf36b993a1aa3e4992afb505740f5f7dfc581d555535a9807d6df4887235fdf1dfac7e8d0fe4bb3ef92c693e49e22ab7d11de52f8ebea730b38b9f3ca525cfabbfcd98351f7d524892c85ed6c1e3effae2280c45b27c722d5fb19bd222c21aba92482412f9241202c25dc8eb2fb99248ebab205fe26fb8d3c9d7a85105a412adc44993f92553899fb84deea4f5d57f4138b7c97d25cf2f22ac911c8bfde4ef28a4a19f0c8b18c5104729971f58c59dfe66340ca998a28f742c051535a04289bed2b1145508861c074e4e0fbfdc951aef37ec891f22b1337ac947dffcf24b1b675ac9dfb4fcf349cf6e9c69a34be2363993dc46ebaecd6d428245f96b93357f47dcc62c227451721fc851e46ff89a874584b5cc4fa43cd2e008922585c41fc5512c8f10b568c48f2449922449d2254937dc028c17b9162e2e5e6a369918b85b0b0b2d4565a59b9cb825282430305be7c71c61bc7871e1d22287bbb5b0aca8a4d05c18181871ec23cc89893b32a6c89dcaa884eb9b69729429532200002000000318000020100a8591284a92200d6a4d1f1480125d8e62504a3892c863a12087911845510c43c610430800c620406686684e006ad2e9c549d8ec5b05c6af15089ef6b67337a444b84864de073085ed3fbedf39d03378375dae78aabad0de1a7a2989d3e194bd6dd8aa2a664f992277e345d22f544e1b28bca86b61e4bdacad812de0d450269700b70821ea6597d649812d0dd7e0e34244619f9c3d2e301313dcfdd5bdd70333e5bbe026624d0ee33b099c1b2a9cbcce7604526663642d01684dd6dae18d921c6330ab7d4875c831429e6e8f7d95a0b469dbb0d1dd0835da7b8d0723fe06abe7df623b49ac0edcd2a1d9edd1a27ec548e2d6e49ea65ec4243a3f803745b712852832595a699240b1475f839bbceaed7da89fd3801aa3c8d1d8aefeafddf1c342d2c65cb26928ee642c2e6793baf10c29ba63d3d4a20193de1a64e7285f1847390b21adf9cb411808f74ae875bf08c6cf196a35156a31fe0d5c60eea32ecb90f595243bde1b06a7cf8fa27d1da9eaabf71d6ec39cd688d0c7bfeeccb30560027bcefa1e5c959d8c00bd91eb5e0fb1f05a626bb612f7a8f78369b8d98471d28879a441d9e6e56885342919027383b10039110188012b21df478f4ce13972200f89e4a08955cadf39486fd06025eafcbc5cb74c0d61a3f0f3cbbf0d73a3d1c6c5617626e57d0b3657ca7ad7a30348aae6d914d8a2481083559b783ca86325c13cd1ace24c060d28a5d0e9298ef8556f29b75d83726d7c5b1aa4f08be120a94e608760d890a28f809009a7704aa0889d625de3ff46aa79d6e03673ba8208d58c2718b515daa0645dc8c704592da5224bca3256830aebed0cc42c52f0a30e4b3207bdd6215383b6b62273f0a14a142296aef4fbdba4b397c6d0969100ab57cdb0adc713f62b8d0a7d6d26fde08adad742227707703b8ecde9497838da5c81d1beaeff793ec2deaf5f89655e9cc7b8b47fcf0eeedf455433979ca32016b663a1b1cad54f89aca236f88cbfca133482db67e7f622dc88e164d5d5e85c0b4c097860feba48acff1f2c0c9f002b5768b7511b0009bfed949baefe29ac52142a1a599e1d62ca9af655a114b97e702e253d391b9d11fab32a1e637e41bc722f1f5974d3ab508104808d6edbea76e4190ff574c5e8d781cb189aabf7d05388f42b32a5ba2305cc386393ede138d990fafd840f177a6fa56e562061c56439b87417ed14dc8f2ef1258f6c3c94a776576117471b66ba01a310a94081b3e311a297e20b5f55c776843817ccd541af21c52e8151ca991ff37c4cc9060e8fb964854a39806a3bfa214908ac9d22ebe1d31270ddd5876b550b926ffd9e5198d3f434b6fbce749aab0f77d21e710394fbfd25506b1a8cd29d3ee6a390c7513832c72ed0c1de00ed46c4b6bb0e206ac70eb82166d511326e85d7fb0c76a574bb6d6540038d35ec8b1b0bba30dfe1437c74078cf4e8e5ee54f48cefe2d5cc846fd0c8a0574f6c63c1cbff05b5c539b162048c82df61f3a746dd5c342d92efd2c0a9152bc910255f12b1023616bcc90887eca6d9c8aea42871a28bb1b7e1e142ef098926ce344bc9f9dac71b6d3266684d0d024eb06a379bd2a0a63d0e855f68ff1a424af861654dfb1c034c798abd1311fb1d81b37b205d56665803b86ec03897abc1a0b76182ec782523e96e6f8b4dd8160bb106513207261d8da13bed19a99047068f7ad10092818fa538337b7e3cd49dd312488550b85327a21646d76f5475ee0a40581d6c5e5874886d40773702c35cd22b0b67a43b0114d630c031e07f693dc015299c03f5c34b03ebb7ad57743a1082f441dbef66d81fca0048fa1a8770bb9ab33f17e87c34d58d13156d6fd2bb134e55b0f7edbe87e277527301240f38516af88fb51f480310b5f3bfec56544658137e3880bde01c2127ac3f7756ab67608b2c3643d6aede8110f69bd006a46368b384beca9925427eb68da35a4a0d7629144b5c10a03c4799c1977468bfdb96692cc6734796fdbf5b0cbc4ff78c70739a2b4bee8c3ca096fab90d4d27daff4af86a38d0780f237b47a7507b24b69908a7643f8970529b79d640c29995f519c2c4041daddd0fc923630212234a1990bb21739a916cd0b560d6e31fdede7075b2e297625b91af7b5c7de68824be4dbdb95f6dacad78c5a57f7d11f3e66ad6635e57453d4a046e00c58800ec739eb3be057890f3a6c9d95df8b4c8aec654627dd9119d1b0804aa07217138a0abd80d1efb783054c56fe33c1ce1ca251127f017978307606427a67652465917ca555bbd87873f2cdecd93a8ac722c3fe2beba9f6be6121141645cb3553fb34b82192f5eb2640f8407490ff99ebd0b67ce06532bf3057a4fd4f74dfd6c0f954c180396525f2a230a0862e04455047e5f416c1978f810ccf8b24e0dce4da13eba53543f968c3ee25869056ea60b689fa125ef733685ca313236167541118b6928cc3e5c57776031800132ad33f37dce9cdb2f57200a5a6ff4054f00047276742c01d13609b47e4b86c2dab59020c56b711f0b9b9c474105476200584590a2afd37ecafaf31260cfbaefcaa500c3b6269fd08c12284fa989ac91b4e2000b1b3d87836c345c6b322df58553643d9abe106442de1f8d16e46879e300edb7f412b031d05f900f4c8462df729a3042ff81c97a0c0e7b338a76d182cb37337ba32e29327c1830a388ee20fe972a19c5743181450c2ec764389d966b57f4ec216f04206fffeac711a3efe0576c9c4f3ddfee07597263c253959638db9d72ac9f113348764ccd301923bbfafaf93956ee4c10fa5a22a79f839e0d708c3ac182e56d23ca6e728b850763676e82ee7fe8baf6a42a99f77f98b809231cfe691f23f052b90c50e767d9d554dcae4d2ea394a4205c48eab1f13d5c31f1adc435be0b4506c56a5a5ad40d9d265c4d6611d656c07ff88927dae418f4439cf74eab10328b1bfd2d625ab1ffeecb80a705957b1e55064726821a592a7019a763962b1205d62aa11b868cbe553531e6130aaa4a88f8e32caec884d574e0103bceb584c0a70c83fd67f54f5885c98f096582077578187145ddb78b070d0762bec9e4b2eda7de0151c88753744de448fbdef6ff9f481dea1d65a71f459abdedd222c30e8bf1867ae51083a85b9a6436e3111325941e628673d19d647ee3ba9ceb333212a2ae7b378d098f835d948c2e0f5280d6a69f688570d588af31cf544c705055506409e1f6e025c15b0435cda0bee666474290218228560100a8af418ff3822d39e16121bf30d0fa7ee400bba6159acd2148fbf6bb7a281e191242306f931a6611c17b66021fabd96c213acd5ec21517a743034d0f11e9cc5970fe043ba39063f456f13f851599da595e03e5a6ad7ec7af8f72c9d0a57848d62341207f15458f222c8faada68460588705209a614290f2e04ce224195c52ac41819210fe66246caa90f3b363c7e902722da68d11cc6b7c9fc9ba41c33795cf673691d474dd48f55f5939512e4b5bc1824775c225c66dc9bad415b3e40ea2390eb1b2c297102bf824c4c850187f9b0742071a15d1e0d05d09661b20dd2ddf06465208d2361bbdba769b86d22270eda2a887ae806e00ec801d3e63e78d4ac2e1124e884649dda5b268f952a0488ab822caff6e66d0fec25d02a46601af30c30570812038966a2844bef38565fce134696c3ed628d49da239af7f216921e48437bd346c5993334f9508be70256f8e7791a129ab179fae8ef385987d616f783cd9f1eeeb2f0372ca444e97b5cc55453e9f5c534e2c0c468a9ec4098eb48d0c148837801cda2540432507e702c6d519774a13ae51ed03749c3d24823afaa4102631728f610881cfe11d113ff385065e9e654e3691da51e4eb18a4da0d4b0b86282cc8ac84130bbf3f1414a762538e18eb413298e687304ac31461041f620c9d9bfc410459fe52823008f6218d7b22f29f0c094951ceef2de2c441f2ed5f894c8ec4b784e9932c97d0cf4d7ba9140d81db12baaf21b19c2bf3fe7ba4a30bd3baa0963d30da19d9e28a8a03b965c4f6666446494cb82569b5163895cd5aeed8c138b1a4514c6882572415b47c66ee11b4711c453165af731056b75da2470e254ee2be54482a9b33872047c8174e23a88ccac399d8f8d0e05938d64fb9bffbc1cfd52084b5496577ffa0eb0b20e5c408ef85b912d091142693195f498122fb771ab9b94a19c9bd8292c8148fc62a338a222c835fddc26ade47df811fd9e8f20154c6b8e1cd69d7ebe93ac23724fcac81d392e14fb51080e9dd254810485f1a3a6959269895444b4301e8c898923e04c4f43313312e656bdd12a41d395ad7bd4578f3ce1017f8bf791e3a94bc029dcaa6528b1124cc28c047520776cb09306e4fc31dd474eb9fec8d59d25250d10145c09e8934b17c0265148ab8465b892818f2feab6aec1137df3e48ee7e6cd681b85b3b34bcc41fdf99a62198b6826a77f7e846a74bc4f4e59314a5011bc90c024eae69b86c7116e1d88b320b36ebf27487c6328cefc26aa4a467dadafde27c7a20206e23eb9ba1ca0b37a66319c4684d1bb10e38556784d91f37ec1da201117b6fd9744c717eaf8c246ff5593d34d9e1f52553de56a5d44dac489ac5d53f64e8aadedfcacc24a221dc821c2ee1f404152b6e8dcc8cbad0e6ea07ab0b98bde3ecadb039b5dede598c1635fae593cf672cc8da0772d2178186fe049c13728424f22cd0401214bc3c4797818f15017584a94bb6b69a27b9763361e3bb96642263966f0d8c96106df3ac05e225175712b1ff6b030dc84b91e5f0e20b6c870f99efeba8bd9a47b2d37bb0e252d33739de10852d39175d4b20cc7c4166c4a57de4c67d06966921c50f3101c5c7e58ab2177e59d4738ad3eeb4a04c68741e9679b5bd599e37c14706e00d903b61663b4fa3d35b41679674837976959cd8e7188662c4e41f2c3b236712b1a90155cbc5ba072fe4192630a4844070b55c09328a024a6806208cebd5dfe0488d57eb2f706b442b103c41ec92388230f476e0dbde31efa0826dde51fd5f2a72411dd4f11330a9b7ecea314503735aa9103390fee650920291046911b23f5e125debefdb7e1e3aed4fbe31114620d901ee1efb21296c9377c4674a860b662a4d7f1e0fd1f61a427b90474c9eafc2e8600312342c35a8cf80d50f6d6a9a4bb10e456a63590087384913a2c547cc41c03538803a2b6acfc1f801725fefac01e0313960292aae86929d142ea62e4756ae2643e8be93aacec8e9c298a36fac00c523ba6ca9332994aba8973664e229d96461a74ee64c79020846927e932786eacaf8df9b284adb8bab9d84d4b4baf5769af962899cbf70f3593f4c702a5c0620e12d86014e5eb342b6ee1e030358ba6862613a244c06227e9b63607164a28a1fa9c0615a4799a57f3748f2c4545b1502eef8d510aba62859a81c7c36176db8be960baf90c08ccfa2d8735ed406be4ca67a2d44548deb5621d2e547ef5a2ffe07d8034e55e44441c8a6c3aac39c524eced620085602c215a36a4f6a170ba3588c4745886c05804087f3a501801f6a532fd905685a40bc5ec7abd6f432271d15d32320f83b9efb08b565016375a929747ba1d94c578b1d0513ca384332cb20f6b16a9abf50a42e75a11edcb0ac7bdedc239c8874915b57ce1f48a9fe7580188afcffe3f1cab8a8f0aaeadec5da3409064aface66110a80576278a3d1a842fd9175e6c19c69801258b1510b16e810c573db622bcdf4ea9e9702083105789fd98209e31e682783d842a82b3d6931f36f38be09e5cca62d91cf93f68db1e75a6e53b3e29277745a6c8f58e946f5d728a003c3b1f2bb77210156956d2eda6168c6a2809e272d73802c42832ac41ba9bbf780bba7538ffd07f3d0ae2ea8491337dcd7195b086c3332a1122106b888194d69bcfe541523bd3fc76ef5bf420dd9452c2876c16c8e09dfe31ef2bd59fa7eeed8cc3904f4414a98b4bef00458b9a53e5ce3cf8b39c821f363b608292ae68af43d9c3a6880c9b3e5a4149d795eacdb2ef007c580a7d659f3748c3ca3351afd8c39a35c6344016dccd14013e2c334d70b1a63638ea6f079274572914effe7b9557bc452c4bc9fd35ef0fdc1531d2a5c2498885f4ba376c452cd1dfcbc876858c85460d9f21a49a39e202656a452d0665105d48283ba6900b05f7321a4d64b4a9b03e8894f1256aa2938ee994c121d434e64a2c44d69f16ba105202937590cbfef9d3db61043ff2b2416c1741a55036e84ab4ef7e0de8957130320dec729399dfe98c3cfcf6da981fca87dee389f2a16cd7076c659c52983c2ca48473760a1c2355441a4410afffcfaf18304a0306bb4ba9f795dc93f4345059dd0bd4d22ccda44cd470e5544c8f58c9f9be71fbc1045f7b4d18fab10c227f5f2ed93ffe4289d43bcae0442259aa801978a33d4ba55c52a772ceae8efd9bbb5e09ec273cb05de988d825a482b2a4fe19db64fe6d4949aefaf22116c025306541433e4d87581b8f631ab7b4f509b09952c44b499f84041485482f886dffcf891905cd94c7815a9a63815fb084b852e10599250849a4aa7e0c3b4e660ba2b1b48c7d672a3d91d4988b39b939cf49ed6842812a7c7cb2f381ce4d4e1079074f7b13d4ae9965d5d88d7461732fe30ace855efb56c2a27b62992d8fcf3054eb46b14b50200ace3fae5eff8d4c36e77c59b316be3997c0385d9671fd7aef0b485c94a6b3d11c81a43185a92dab6785b29a7d9b9a8d0c03ddbc7adcd38a21b03a8e79bd0050f69fc34fa6e1f24b0c9108ff50964f5c17030c1d98115f5ef6277464a60e8655dc4c781c8ba46572a435f6732d3b9acbce195cba6a2337ec89c6e41cc74d8249d018c92dc7f4d9754254311dc8ebfe542d2388b8f2d680538ac35850f0cb8497469e13c4bc1b83d5e4fd3301c8f6b63f3eabbf3c614d749317d1eca791bf3ef07011c0f567696652a9660e79f90098ae4a1e0979e1569dd7a4474ef8ee1bd6fcac0c79a92e6cd3a26812f580922f740a6ae8e152d72427ac8a07a457ff44d4ea4a10b8893a34ec00d67b7227421b725a0fd4ee19701831b15b0508edb9286a17564c1c55a9cf95a056e9de8811b7907d3f0d916a2a0a3283c8fc26b605bcd6e677c95825284b2b7d3a3b36504d0248780947e1d9bd7bea5f6c44cd4f76c0536636220525534fd775ec06b2cbc4ea4b6035e6930bb0ace9d0cd55de82ec67a135e073d604c98938bcd0c7821c8dd623b7525810ace7b0ac79c186fac2201254bcb46d88c5c97098b777bca2f73d63241d1f5e0615063932c92e6268485830854ee0c83346b806d534b0d393128218a9e31d73a61ed81a73d8884397c02bfd51ac104018a9ddd8d95fbeb3c2fe463f54b4dcc302b1e658687fbb2073000bae173caab8a54b9e3c2f88374a7380385ea832f9858467a412c3a5f7f75abc7cbe0f67a4692c038bf8669082623df39eb476a393c35d9061300f08be494376aa9294b782cc05f2eabb87765d92b37334233090b21577a93774f02631f226046bcee7104321b510937fb336834238c72474a14b9d62bff48f20643f26bde24513adbcbc1156d14b28780cc78cd68d0d4f04fb53814dd02aadf2e974e5543927cf0ca126209125700e8e0ad02ad454ec002e6e760e78915d0b2b5c13fab8a19a8314610b8bba9e8aa1cb38460ffa325cfe1576012b00236c3557ee4fef0787b9c56cd52d3af6de8c75bd38fc5b29deee65157426a1ea2df810760def9a6de99154d11c0ca10b3c6a1789f6b45cb41f1c2293c81587513e1af5932c0c0a7ad5f1d40122715287f119b1111ea97f19ce34bf51730627f4136cf4adec2e2e0d61bc9e7581004fa592bfd2da979547ed88453e8b12d0f586392d34a6a694e2fd6062d0c8c386a32689bcc61a9bae3f453f4a3b5d5987f3aff94bc2207bba94b613326708930e828b9c6364021e8ac381f00d1f1aeb74c82fb4e2b4328fafed9b9f2af1d76a8760fc1ca9119b549650cebae0f04cc23fe8c165040559fc9eae23108594fc9c97fc14a606a904ec81be65ce90e2d98830f0aa475e73003d1822fd32f7f363d2e540ad86c22ae78d74514be42634e1de3b248a4bffce3083d8091c6eda452df99b41b1589f856819d77165707023708ddc0445a3349243afbba8578ac2dee2d3038ac461ef3e16cb9488374c0fbe0b6946ade312eaf0892ca61fc84d06c2b088d07a892a98be9399b6a4a6904a0b843e009f4371fca4810268fd47a8e7d26350a988b7a49607005072b47b60e254501978bd5bf203e4eff661b54a61d3b17f2eeb0418260ac3ab89c6b2a72f4d7bc5f0b917c5473b87379a7dbd83d97f882b2848e3a5f04add67be0c01d2f43cbdc5b64b016baaa0f683eddc34e3bb4db30bc9faec9c86cc83d67022a1772b82206fda394dd7754508fd7d276c05669eef473b93f741a318004908325228a61bb4519e39829e540c921a826476f39323056d27c6731ed0a8e305c1bf6ccc226374036c5c7bfd9bee1ac2a0314d6bb9bfa9b288f2bc34232ba850290adec55918075c39c1e2e0d59816bf4ed53b1a9897c2fa5108826fee35281fd37776ab458aee8b94c19787b49d3ad818a45e4a795431b013e384aaf3962c71327f2838b89706418552a536c8f1e77be8f49eb41e65a154c9d2e5a479a836048024784cecccd8d68b9e028b34a5ba0a5d6e265b2e71aa86b2e3b6b60de59ae0227b6fb3a9e30156fa5a119a6becad34715ab92f1bf308fc9ab5ecb965a2c8bd8050bc266500ed0a3147865ccb393d90cc29b169705d203e28a9c5856a016ae636b0d8c1a14c723870c6243d9650c70fb9802dbcf39ea653490328b3e278ecde268e48c5e9122cf74f03178fb700b48a001a1c5466d8fa817a81ed437f43d38fa64a3e3993490ef95d721f132391652a03faa2e1c660555657ca449209ce32505d5dcade773b717ffbd34ad3c942b479451f33459ecc9b501f2a699a2e98800517d6ba13a72f0e5d852abb9a20e677304fd60ee2cb1f23f517d5d30e2d574a535d6caf58f1f13bfaf8203f4ee78f030134bccf2d3d25910f95e2425321f7268e8b524e4551936f63f0b4c83f2ca12cd457d4b3664a06187aacde3179a01cbd58b6760dad9136e8fb014a40335580af3997182cdf2167110adafec55e5a37c6eb990460c490b4fdcbb3889e28ecd1629123d360b90e5ac9a272aa1635acc59249b809aa2acb66e7ce836a2a9e4437efc43b13c26bd683d849e5c758502e3c782346d38c4535842a9af2d71a72f8cc1aabec0348c7263d32628c4d887d83534d9fb198ee54c5010955882897904cb8ca6a53f2bd860c8ed9c63afb02d2d984478cd458d62eb7896a35839eae3a3d16c5f494abcac9c37052d20a32e45ec730fa8295398fd26ab4ddb361002060c7b2d1175761fb584d2d946ca901494556951335a2bfc63a134ec7237b425560184d3f555014ea2c53d269b15ffdeb0f013f758cf6e003d1a686e42bc5a09d9974c660a01a5ce74190a706dcd0c27e464eefc7fdce0fb41a46406a8427435ba0572f6d4b6937ed5187137f8ded8fcd10032f57b70c7e25f2d7d8e831676c629a3e55145901a547216905080c6b0151c68862b8df02a67860f13eb99811a975269bf300a8d8051dbdb5507e03d0ba5d908070cb184a4b0660fbd4e12080e76cd522f2b3fed64ac738bb628b36b6787459fbc6caf07dcd9d27b2c30522ebc3503619bb284d48bca2d00bdf45f289b17e2c95302dac51b870ceb5f27b4d0fee6e81880377c6b54d3488df39bcc1c3d92292cbf6ca37450ccde1b4fc8482b5987ab5defb4c30e01f6b87b255b5406167c2ef13c47bbc853728d87dfc2e72364838327ccfe7bd60a3c3006dc0ca99ccf7348df8d5e118ebee7e01a5f37770f8c7ab39fccbed618de6b96d92d173f8c134f95c35eb03472332750dfce49864c8f0f213904bcb31e9e0b1d9e3f3f750412b310ed38167a4fcf9844299520d4a50a0a3a1a0a0ea4bbc4f1e19308799b01f2e4f2615a268eee8913eebf54884813ab2ec325e57dd92c85815342c8c8f5dcd3e8534fdfca3bf64257ee0d5180be13b6e4f55e11fa2777c1488f9c18e0ea80ca0cfaeaa8c8e76b08d8de67a04189ed0439d9c778255be130ca7803f829e69841eeb7fb69d6f8438d19a71a0df2071926f633dc337ca385ed08872dc447234d1f104b9f46f5dbe8e4c4d70f705a0a5e9eba3bbec274905cd222a604605531c28c239a2386077114f544c86495e9b7bcadcc3ea490096261e939436e33e6ce8a0ad313450e3288dbe6dd0e8aa2dabda048787b1223a03daaa0101e93392c1ea61e846284e602d0e897fff07ab60c6f26503cfe58592a90d01262b9b3774b334c0f85352dce2bae363a8bbd9f121867525e59389ddc6463420a5e429761cf2993232a86446a50850e472168af381baaefdde840118e72493dad8de50cd464a3b141559e130d8c94458cdd7b23bfea47c2b40e91680227965b9e1550805b700fa4b51abbd79658d124e58314648d9e0e87d8d3ac4a8b6142846f3f7de1a6f529e6334b86bac1b7ac66175c5ef36c9360021b3b191e5536dfaea1db655953487b15617a7427e51a432523fa011d5569f72a945bc4515563de3b8dcb84e49224bdab556cd34ade02775bc6e548536aa20aecef690e6555bc591e19f961a17b2c4f9033257f8abfaa49eb20239dbaa0d62dbdabfb71476cfdc163f7f1baca61b190cc64a9eb8982e9a346ff5f33cad4710cf474ac0ea5c6c6cf407319627a0fabdcdc8997e60646eb19181da02c4a32ec26bf8f5d8c210f12b888bc39bb966ded81b238a47fd2870f06122107213d2df13a5f52964f07ea74a9c7fbcde3f51db60bed144099c514d86114c20e7819c4538bcb65f6373d597d1a097d659493181c58de7d6181c0a3a513d60eac4484546a4a8aceaab221cb249f6f9ed5bf00043cd201ccb8f73303b63f1d73eceaf1cd3c7e17e54b9b2cc3621523397866252a463f51519e7fcf39cbdb299f68030086b9255ca023a53c178625c28c488dc4bf0d503b64510dfdcfcfb025be6e8e5bc87f33334ae5f360a08b648bd1cfeb5c0d450e3965b4e974abfbc6d8f47e16ed87da3b0c96e0da4451fd7af513f86e53203bd86fbdd8d16b386729e31103a9f698001e52df2ab5bd3574e6e3c379a4c5f0225ffbd39b4b7b2690c84d188e74f30c547a4a6629a5f998f3100aa1f183893abc33aa47e1b952df9c10f50447e76162ba50bfcba31c3d895c589058457059d6d76744840168c9a9d31e05ac2e1132315641fc5e67c0dfa9bb615748d4c014688915b726e68645d5984598330b5ca7343bb84810d679f6b5a4ffcc584e89ac52dc01a19a3bee56df7a4fbae1d4b8dc45b9191eb86c017d3a72f251a6144c26800882836aa4273410102f12c0325b30b6ff910e927137c32b0295edd700533b12b1b64c96744446f82d37d3cb1f72ce07374938a33d5957e8ad9aa684b61d357ad68e8f9406a7c5c339db60b6b16cd8eede559d8f9542e9f758743b33afb41a13bb8802149584eaa13fe2ff0a4bcd1b43f7413b67c25c567ef4dc9877da2e0735901ee59db25e42321075048d7998ceae35a97d9cb0b650e635110fd6ba982c41e1db67cd3552f748ef5e29e9915151e9346800c08b0aae2f3bedc5b3ee3abdecb16a4e2704a8925ab73806c9cd44a5952a57f9ba089256e600785eac50584398289dbd883a2f2e200419e40e236f220a8beb40b07f03ec5e4a421b1cf247bbf23e7575ab7452f9ef4c81bead2af6b6033a7eecf6ceaefe5108e75ac11bf3c6afe190ee8e33687baadd9dc276a83f1779b850f6ab855257c486fb36c81b7b511370023226e703468a4365d5d85339766d3210c2d71c0e2dbcc646678d41c6cf98a98aedd592ca32ff7377de733042cfdd048370eb2b3a6fc7d965af2d9722ff82a9a0d8d5717c5d9b9874de87f22fb1bfdc0a139132c4357e81580d2cd335dd87966397a2dee3bc49e56906a133deb6fadbc1fd4fe34e57c29e7e02e3376d75111d160a3350ec7a43ce8545554c6582202b0dd728c01c5dc474085a0ce15a858ef9863c4f83d8ec36bd2c65a42a3538e50b93621d4640fda74cd3803c1b1cc5018250c246cff61aa335dacf004c9920731270233407d8e1e2c1301bc93e9156fce53db7db5323c0904f359466681cc729e43862b6b06d16f7ef15b082d82f92cab584da54eea1bba672558a4f7c85333ec2c8d60be76279af0908465fed27760cf707ad2d32d32ea2558bdd9f350ad9ea5771639a5d703787db4dcfdbed7b21fdab3daad20bd673b56b071506a34d9eb799e665638069f40af47b747d3c0bce8470780988e49476ea2ee4526ca84a147c45b1a3e4c407d311da5d94f599e4f334d622e05a80fc17ccc87a10c01a2a4bdab8a92a9f101cb32fe8dd1d4baa51b26cc27c7ef19df5ffd20e7375eb5a5bb8a98719feee9f3435568472190a957c010a00c8fd7d8f47c2c6e6b4d27e8318a744169e52d797878491fde65ef9fc6d8ec7eace147efa1859e34536edbbbb14a33363819f1e2548c20746f808307ce9bcdf6235e3cfaf10b8360bed907395e71a20639af34d6d69dc3a4a9fe6b427eeae2a19d21e40acc7880b4f2bb57c99277dc873199b75b52aee5078606fd561caa2795d729bbf1798a5c3e08031bd29a08f5d8740deef195517f4ec6fcf6f667626c35248124fa6554c510ad4d77a0d5a609ab41de40208a0faf08b9bb4cc624a0e337174d2dfa675b653543b052a06fd0f9ba74ae9181176774901e2af2e618e8c9826bef694a2b5ef24b5bb8f01c0e8aa6ee59eaeffb49a583463eed20a05aabef59410fd2c6628bd5f3e04e22600d11a4cd7c23a68e33f55678974d00458c18721d0a5f4a3337311a0c15856b9e6acc761501ab08f49c8b0695372c4a00dddfa77110603b1f8952be07e8288da95e04f04d849fb40e74bac9774c1b13040f1ae39881647e83bd461daa21833b55003473d87b01655aa3fd580ed28198e6049bb1a1d802833d6d82e8c46327b866bc1149669741886fbbc3cbe79a909e99c2629c3e2bbcd7b7374a71b6aa1686f3b86e74f901e1536b29f3edeee95544cf994edc7327c371cd4128f07e43c28e3d085ed8a3848abf88c988e7cd0ad945f8b61704fd423100a995cc50fa7aaaa98faea63691ac5ec16bfd5732c005f66700d5a962a26fae79dc5e26aa7f2e02b55e86daba4727c984644b94aec02a19a5d0c2ecbcd4511a7c07c36c45d60f3b7490dc66dfe78f018959e6fa67839c82e348f55bf1079ea1a6c658e8289b0c2e7988cb2f96d5ef9e62fceaaaf88eab08d1df28024e9bfa60d7288a7216b40edee222f49d637919a5dc5f56b907d94b4059ade17f6c2451e0619bafe1f9e521f843b374466b533b69c71ceb587a6a2d307085fcad9ab7c28e7b8af8723ec69722fbaa758f55e33449c42c2b7370c5e6fa1c7e20b20b008141bf4e5b7977107c4a56e690256a9e2c9a4ab746e7cdb24a78383f589448c3ff9e230dc55cf3ea1559a5220a2b85e8e34b0fc51c025a9c3b31f314d119a0b9ad9dc07d707335579e451425a02265ae3048664ea6f649505e2b990ce9ff5fafc0ad637542fd83d8524ad34450aec21b26b2d1665f11567bf338f27447e0405de3efcd64ba1482e813ad88f8cc6aae468dec1056c75ebb5f46a885434150d9b7a4f89693af9973cc59379611296763aefead29ca8b33a2e54338ef5bccd5bfd0660ed17e07e71ff457dc5774c402647f3f4894f3628f41264c8bd0bd6676d8375607cb3a61997c2958862ad9d9a939a46b88336672accc62510fe48b8169e5e2b3fd56396ac1c2679efa87a8f6cd82e23a7a722c8443ff4e00d010d9a6f8c469dae80e4ff9420a62b68af27f0c8441f5de8a11091dfebf501a9e1334b926d7094a7668df0c4e685ab6d1eedf7b8532ef8017e05d370a39f71be27e56c6462134a31b33ba4465f35be7931eb570e56ccdeddd2d4a0e7853f23d69c8ec915253a353043af8109bcfd45f411ad5e6d368854340d7969729b87ce525beece6da9ee053ffb17a410cd3f2e3a2bf755b2f19b055b2bc0aae4cd85683059adda6942793408c362864e1554ded2bd2b6b10920b19b7ec620d0df4d9c5cc46f2ae95bc2f7ed6026a1bd17c1458582a458459ff36456bcbeadce36bb674370af7a959cd5cdd4f348c9b88056dd358581b9f399b90dd4870a7c530529acb14240207ad768c596950cea56faee6363592bd21cf1dc6bdab04fac639235ca2433b54c231fd774154db27ea38894a403fa9c58f9d3155f4ad38c16f3339fbf413d2b3e1d74510001aff6a6c61d21ecf72c7b1f0c02469d86e41bb6b61c76ba2657fa430554b545cb2e57d5cb0eb9dc7c638ce9949e37138830c1ff3d08083018993cfecb61edf8e4f23005a2f263ebfb557084a48c2dcd7819bd0145c890bb08d1ae7ace431c88dda4643c5acf79790f0552525a93d567dd2a0203a26a7f6207c4d07c77987bed9d637101fe2d46584c9e4038267909c5fe192c708dcb26f67b1b5070c42d9f9dd632e719e97478f2fb365bc8903cb06403d1273f7c33d076e3b8aaa93bd410a1e2b9e376519555cee3f2e5dd26733edf395f83993756488a9c07b3a421aabc604ef2f44344764d404c4bf808130f804a71abbdab711e6aca256a9a2109cceee5d6e163ea90c7780259b354442e1a0cf2a3fa165adccf66a6bc0c42585f7c2d5284c1198970f9b5c4792dbe9d7d1e248b5500c4ae4ed7572b9147c562979aec6fdcf51af354315f2136bcee07cc8b5243e6309744552341892012b1fd9b96300410640ecee87a0e5cdf7a50f4148309e97bae01a149d406ae13c4259891a76ee217e39f084248c163cd8c39112921944cefccc198b50b6c26e3d270657cab8763115a2f92076e67426be99884c36e09388f400e9a09214939c8ce089e25f0918b130f024f49676e1c8c1937d3336e1af022de786d8b575657574b2e3c4d4901d5ca3fc8c38db758a087dea8441a56bd7a4f4c48788321e254c2618170acbd77a91c1452f3aae4bc000082db262aad55e21831bdc02474f93d7920dc493584a4c83ef23ecb835f1c5d623ccc41851a80688e64402d1f62e2b224d49d8410dc5bc01a72fd96110a6d2ae36b397108a813c6df1040821cf3fc74e44f7f70d9188f49370b83eaa32de614bcb8ad5ba24a21e58f04750bbe430a86aaee9bd0d142d3d49e712cc2d0288ec80e2f156b46639701de130237220c53398af0ad91688e13513f9abdb4a23a339b7f127680221ff5482518770c6adc54a448770cebc21ae282e62c0d920c7cc183aa82a40b15b533b09b7d9cc807fb71b0969795d5ca953fd9699f0208236f45812fb9b1c8cb7cc461315ff403f592634ec5b5dc153b71b18ba915e2f6dc8e49780ba1953c2d443e130b13ac3cc5261526b48b5a03cd781535462713005ca111dcc5c93a84b327f2fe81c3d08de0ae92242f3fdca0845d23bf0a4423a9f6f583023d1a823cfb14658c138a0a958d86ee27669d0d90a3e4928a4a2849d234a398ef0997d24e82bb3e5f80df80a2f4874018bb8ea71c04112094f349ffb858b286a9b3f001601d73110446d63617b6333e14bc5c9a287584062a9bba4e3989f21872dd6d90a58c2426d248cb116193b96fa60a106698c54c2a176d727280604a9b802f5c8391998060e6d0905d464d7e4fe2cf98e358e500d5fd18047921fcd66e43e05dc1c53bd45734a1b4060961f36df140109e9414fb48aa27c4933944461a147290324e906ff66cfcaec9db3b43d954a4d91f2628c07a2dff58e2bd1cc2f9c1cecc302a9cf862a2e9f1d985b57a52805a4ce610a98b45ca0392b69c681ec3cdb8c442a922fa91662d19e7708d88f16b4adcc838f1375b6e928285f24bf4d1cc5a62f6080a529e0f3c730b3d2b5804ef0f911d63392933bbece3830270f0460629a7757e841ed550c1ae3ee93cd11a69e5ac2db3bad48b9d9db3cb498f8b9a5b572310743876b75a8e81e94a5b5fe00767174f823120c870da03ae0c5670b7867726cd1ac918b521a5f5c96a93aa48982a295d264514638017c2c6c66abb46aa001b335efa156ba5aae5d283f4e391e54f6dad323f9993dfcc0940c32ffa8b28392d405f4ce1c84d6e24e6eb1d0def09e607ab2e826bacfc5cf6ac5ecee4f2ad53c22e78468914c148611f388d821e4b4110d20d764b2b76e95a52f8394c2484a589a65a8f1472686913e962c9860f2491e000ed85a59f36d699c29e86b625feb5118260f7cbf252fa48afd5afb4d4461d53b4689530f0a756d6c831a097237d44c69a2efdbff38a3e7dcb52d68b59097f3ae9d089a4bd7859e45a26a7eccbb8b2c4acd665a764c54cbb7969b5596e06778aa102f110996010f94a63c50685cbc8b3cad9ce0a3abccea521a38322d74bdfea7d1b0f9470f22c45633e56b5f5121bba410b0f6581bf4b3fc038d4cedd4ca6a026191b72bcc85cec1652645b384ddb3691e63f6672f4ee317d077daeffc6f5a319c79542f0ccca9e981d3a0ee17e225f452b400740b72712f9dc33c7d5e17b10f312a506a86985f2b2758612596924cc8a1cf8db18dc45efb7d71521cb69964fb427ba9d1d430592485c7f8ee8addad1598ed52ed75a860047677567dd4d1749f7de6bbb3061299df88e3cc293188525594b3e6f758f01524c698f3daf6b2912ab90cb534139310dd9104a2f2a7619c22893a425c4345af8d2ca221c93e41c1667b7d0f5f78dfec688e269079e53785aab5a162e8b93f6ec0f5d6164b8fafb567a66cc20e116a314a5d9fb4ac8c476807b0bdb406f44f336801397f08bf439ff201b2d8df73c67fcace739e05644008adeeab74e6ef2d91e295f1ba139bead3fbf09390079ec4f0019dae3e77bc57da7e070783cd5e01eb75d8e463a950dee0513eadb344bd3ab9c23d1d79251f7de77b276caf8cf915f75f3da054970773e1e8ad74ce05f65313a63c331da0a110ac16dd4a4fe5e816efc80bd96e456e93f28530507ecd2cc6c3184ac9eb34ec7e7b1456cd4f3aec7038862be1fc2ebd0acfc53684f0a3ec218d9a74c0f816c1caeff473b9cbcaa60de0872e25b1c2315f885fb035fc29326111320afce80f7fe161b19740992a26f4a101345459eb95ae25651c1ac56e4af7473fba99581a92de5029f2ed16d37d3f8f2e8d189a6eb0657f3d14083fb6facb29f193f62b6c1b4cba82d12ab60858ec71e63071f36ae2db8cdba7f48ec18245d328d1efe07476609cde3359c50824bef4e8f79dc606f7651b56ad88b5ecc198b8173a7412663b0f030a84b1717e79052b29cefe2b395647eaddd4b500c70f4a697cae760e92179c154b5750de4438c91f66719b9a26c234a41dfe7476c2bdc64f7d2e3a4f3b1750573ed4ac833d5ba5c01f71f41b9ffc2c762ddd3d1466d5aa73cca47ef5a52539ed2daaf9c5744cdfa008400407a5abf741a55a65eca1bc922c210deb288d9755357b9046f26af74625b1531bb6ee82a97c8cd66e1b8e9c24f2a7a8c96afaac3f91dcc2e0bb6dd957795a394a501fe46155c374b4429dca8f45bcbd8eab56ab55ad9116d7aad05c86c0fdb0afae92580a4e126b92eef87bfe5e182911b9f2d0b1f7e28bb2d47a25f3bb7a7be7bad612e2cb874b3c592b10a5f1b9ba8898093ca74176b05e8eda097fac5cf1f4cdd2497dd5864b0f25877cb6cef4663a425ca247f0fcedd2011782adaa8d77ae04b9629d7488ab55ef62ef0b14accf20b419abd5b856772edcc1e0d286d7d6262bae138ca5970758d2443535b30986212e184207e8e97ad3cbb07a8240a82e55870ccd741d0d7911257839f03e391b3e2ca601bd7500eb34b18175c20716ac1faa27f3a20dbd69f5a0e688900b6490bcdd6ed4dc3d6210430e8fe33eaa251b45c196520b25a8c876377995d70353e23b1ed3f288d9fddcffb860e5e209159f3435b73003279f0b672927942e405176fd66dda446d8dd0d33f93d610216fe48a238cbc8c3ddf75ab14120ffec10ba6def88d052059c252fa82abbdd22ee34349b5c071eb5716c95a0f2c3af8c59f0cfc1fc52d9b2b7bf2aa010d0c6e34ae43070cd5f38d80d9e970177965986aba87bee83413046879ef4797b104ed37bb97be9feda79c8e4971135b4bc12099864d1298766aa840994ad643cb85b20506578f49a121b71158ad1d54dafb2d84f78a01260182a86b3f8bac09d9057f24db82c1a529785627200f79aa9f170dbc372df5afbd3d5016ca441aa94c63c7eaf6cc0cb7069802328cd68f2868e624b364cd1ca9c33c33662f390cd9f9de39060bb872bc55000126beaeefae78224098eefbf514d016e8f3eeba2d0546a7c804b8408d90dc27eecbb5a7f831b8aee97760cc8641a95a97f7760269a59021b15cd916df92bce9ce06ba1e2741298a8d34b805e799928fd3041b191c3f671eb6786e441380d3d8a447468c48c3a45db67dbd076731e744621360024e47b1ad725bd44fbbb6f7d9921806404fbb7c50f27e02ee72ddcdb4fc08a3abb0043a0589a328d6512888dc4e4735c8c8db038fff979384650f24f4a302209681560a1a5ca2fd29497a93eca1777d95f100eb353b5a6f6943d92ec8e4f26f4a2d650256483612a905a0c7eebcc01319a0dd5404470144db4a6591da4d9923c86d822eec491e0e36c89a5483ab9dab0af5cdb1aedb639944f601c73ec67b4439980d5d1b99658200ee9ca4dc5929d5cf06b7f5d63a5ce0b713342390ec8f4549db25810cae7ad4fe2ebc22c65db40dbfd9d6faf1d97056a6673edec588dae02ebbfa9f7a4276b7d9c1bc64697bcf9530d811227d3dfc76efdce54caffdb527fd03f6ff7155628ef375dd8dc45f5facbfc6d52a19705768634fa1004938c4db24cb101c5c0341a37cc1a12a47dae16bc2a014740ef3a8d651d42d2f55a3e2359b7aa27bf5caeaa06ff050d30cd690830b3b32db459f4affe05ff97f9cd02df0605f42dc688ce776d9a7ee752fc368f3c52c19a22d9fb61f2315438aca243b79ab085b19a383eb1af7ef18ed7b4b09201d25f0cb2a6d8b0d8a3a138d91d337646e2932747e3c2ba1fe132e9ae087739f83db99b0fe0f70f033815deb0b38e219878fd14bf07230f5bd69b8c3058854155286e9daf52343ae3132be2882e908f13aa4839bc1a149d605508484e605b3397e2a3fa944c2b3e150913c1a9336113f1f12d21aad596257ebf5a7b0ac4e17aa2f514b7f1fddc96b7ff9e710aba2db2e2f60d10f7a000f90e5526c9b6196b241e5cd1521b65344222305d34bffec8e955382eff8a7f6a14c5b7b17a4fe3d28d62e0ff9c9d7ffbdccfa0cdc381ddcce4a2ce3516696416c95481fcb52d488a265ebac175538af7e3d8bd68313284f8604d6858a1b2b905cb3e8dc93c6becb8d8b6246bf8db3b4bb52ad8688f6b83731258d4dd8bffcf6991336ae403d53bd51193870b1bbd83e9bc97a3c35324b93e4265793d2c82c09ddae0ecf4ded78e495239a24b0ee787bfe3ee78cbc7673cec68cb9cee4b79f7be6e723605d52629e4e883dfeff01c00768b92f74693bd03d1f2b01b29c1defba3b78b966639904e67009c388f19713603ba5cd0fdba880d8892d43263de43da6680cb975bcd81207166595329012865ac3432b5cdc5c5cdcb9b9da51d8d295a1c13d2f666627b8066fbe631b89d6b11107607b96809ae05e3071712032a341079a2a79aa66247a316caad1ca179b0bb4bae4200e933cc7a6fe0e1589096e618cda723d6f57c9052ba437af02e0aa374eb182429db0c752deebb5baa4db04f76f695c16e1dd1d1a81e383254cdcc0304807ff0915167fd78322c7a82f6bf332802d44543f5d3e5c952fd9c6bd9a1728aad07e87e60eafb74d445fdf1f5f24c187c39893fc0bf6451a9a06bae384cc16fe4bbc80ec3538ace260eb362812f890d6ab66625cf37ee3fe142c2f98ffcd53821b017fc2d8003a06c9de2c0823b0b1da9fa5033f3680a82ed7444857afc91fae79df8da82e9737dc2fe4ca1d1c53ac19fa7da14ae49c2f4961151ff0c742927493a637517b867bc12adccb8df8278c3b241cb7315282c1765010967aecaa841ee473821b9c712c4f6e5bfc32fef9e91b0346a4ad3751da43d0c88df01af462e06f42d92b92deb4196c51dda339325d17510671c1bd52d118e971a4a09ca16d54ff2c1ff181b1032e57eceb4603534c0f91d88d3950e3d4e50157152609ff7ddf7874bce60761751443fc6e1080e53d3c0f892f40fbdb96eeefd897825eea98240a25c42100bfe026a611817cc5e41a87efb00a05a2a8b741aeb6507fa7d10e8c535680aecb8788a648168059593ea83e5e0e16ae7484a0b4fcd89933e23fb8480d67156ada66081409ee80ebfc558a0eea2b3bf85b817ee2eb42dbc02e55023cb598a52ea98e8adb25db383a1dd167426db5ecd36cac74bdd6eeb1ad9280ec6f40e8e9fbc3582a59173131317ee052210377c674778fdd4f3ef17922ebfc24dcdda41234e713e24832b2e28e32726242215c835839bd0c0d64523982dbfaaddc2e0bd821da774eb3d7572da4aefd1e2b8793cdcc1f6af8b230f77fd5a5c1d93148b7481f4824a35742022b98b211a5a9652f3bbaa1d53f3641a86e7082ce45a6536621169892cccb8ae96b79713a7bc55e8a01d0b0ba33781753883a74e05c8c2957a6312552f1fe219a3fe21bb89bf77b6e54b036186384292eba274a2f38c5fc1a567d25fdb1fd6834b741c19dc1add0818d958e420703f155628cb0aac4c724e37bcb409375221bc445ecc581f2a8880853a927e48a3e900e95de27ae7dddda11159e463e139c7857aa1931206ce5b21ca48b524a9e27cb79a650131b1265f2580fc1a5e1f834f7414a204d70346aa129ba3459746c5e813daebbdd6d876af9a5d88179cbe2be3e9f709a51c43e0124a1bd8693bae8dbfb004e2d447075e8f49891b74318f6ef4b341f31ada4e2ddd5811081f80215cce9fb5a80241852170d4a3893ea1af4baa9cb85a184117468cf9a3db29e1cee3800776ff2b9a62a87bc382022875327130aacbb0596d4103b31222297ada055092fc6e0b756d3ba444c5de23c2782a9511f1e2bd6dd27b9ecba63e2127bf796b8857504c950da49a7db1fda1e46336017021c59476378887947460a3abc877e7bf92e0ce83094aea9e2a3c835dc6b80dd9f2075695835b12ceb07cd03aad89d4a1a582e56fb21bfb5a1a238f12683b5aec577581f2be042cc97365403d9da50a11f94831d7aa154c772b2301ba17cacca8ef78d2806b31b391a8d4f982b366a855a85f72b9f9030a3b46e1304998e85ee6219d787855c2060280c4ec6ee61df0237388f74ff03ae16fddc93b954ea44ab247e0c0ba607869243caf17834c74b9c0d1d5d248c5d1683663cb9210a326570e58e389ca396042ad4f71cf480c0c7e92608fc1c71530b3b7bb279b572d6be8bd441c33dc0acfbbbe58affd764f8f4b2e2df2fafd8bf248601d77e37996222f01e60b2b60f8e8674dcc039476f47bda4aff7982a52fd60129533e592dc7732ae5fc6bb3e76999eb10e03ebca7e136fe005fdca7356cc0f0cd7ea92d1accf5966cdfc4d040e8183e3e57bee1174390a89c7f1bde0bb604d1d5992050cfadb18caf95d795272d3b0e60aa1c7a6bbd3d51503b8fbbcc84cb28efb88bc7653d05baafd0e2408d57c797ee7e8ddb115c9f114b45f407a26e68a2d65c8ad56cefeb65cdde1a8db61aa9d05454c9981a7998854fbaa7df9095a4426aac0aaa8e7825de264f31fc6b443d85518b41c1342a930a3039c4c4064e055b7a40829768bd6b116069f5149b9bed919b415c538c928cb4a7bcfd11b662f1552b8d5d829b9f74903e8a8f888b98ecf30b59c1d78a483d60c0dd43513260c637a008cf81992d63b7dfbdf0eb9fd8de10af8fe32336c60047c3f39337670225e3f99391b582054e98adb0c4fc8ea5d8df744ec90c8ddd9848a1fe12100508ae40269cd1bec98380485e4e6a04d5b623d2dd3a3e7351341940f68b8ef5b25cc791072f8c00ef969a98576b7fe2b48b854f2f5f48e060516916f339d3a5f8320d4f8e813b546dd2ec02791fb637d157f3a9ef3e2007399d6d656d615f063902469565ee848dc5e593ed8b61e3eaec15f8c14d79ac3c5d63371c57e85776d0edf88c181aef75b8a5c0c598d71195fd4f2181fc9a593946eb805da334b6981e380af4edc89810c44031ab203dc001ad03830972b78e1263dd62aa8a7bc93d6690a49baccb1c5d907eeaeea4a926b5743bc638a0dc6936a46e95f976b0a1cc85c3392dfe42c1875ffe555169e09d12e791328c81cb3f12dd40d8c96c1e0cadd74537c8944fdb576badbba24559182ce31838fd088e65e0bba37505b4ebf61667542da4652a8cd9d21c001974cbfbfeef98debe830fe3e0ac1e4148d75c9d63d6f40cc0164304a11ca38661503d4f6eae119a33f780434fde0f09e0345b09e2c63d7f5ecc69f9634c9043e7904810e5b20542ad38c01aa240f07342ad28555ef241ef6aef15d176fa9225e9a8ca35ddbd237112958a5a16d78cc6e7d27696fc89f0ec0008c010f182ddb6aa97ca76477b9180f4bafaf995bc8c588284281506bdd849a880809d9524a2965420ed00d750f37f3e028017b33ff8df4c8decc7b235d626fe6bb911b77d89bf93c568016b1374ce3571867b03774a44df6667e880ac1a959c9890e4d4e7278128fc4a971b6086206c648e2892352bce009a322d46842ca08ca08425254154e34c9a214dcc52169df7befbd320415268c6a28620a12e0c0434aa31182156444c182080bb620efb3931b66b0c33d9de050b44bb19e6a57a77ec171e2b4475a789ac65de39cc84006582cd9989218638c357c45bc841495c90ee9417b0c9493d9d316edf3926dad9ddb55755a5567f628ebafbea00fa77aa10a223536777da91d3f1ffffee0967da2c66e8ccbde3ac5394de3348d8b71cde76b9aa6691ac5fa13c2b9c8a3b38ac0f3a8487deaf52ed7759ea7793b2c17abb18b8fa3de3b71eeef3dc29b55874e3aa758efc1543bb630445d20c2362dd77bf49ed669da3dee9dbef3ea689ac6d51334ad565ad24e6b9ac6e50d6b9aa669ae1a837d1a91cde27c35bd3ad7d613b879effcad31eede7c6b4c3b6c6bf75e3f8af7d5767bf8bb757bda877bdcb9a5b29e634c8d69738e65ccd6a6eb630913479be59c738e386a4ccbd97af6f6945e4de9f599797a31f0a62e79bc4ab96975de388ee3842a776dea82f8d3bcadcb1c97bbcdd33e0cde940dbf5c756aae3a34579d19a6e6c4c994520d7f38766c83edeee36034b1c62cddb6ba8557c4b295e96e559d6b1f8466070d6daa5ff9540bd1bd7dea923ba5dff4b671dcb671dcc6715a29643d8e7bbd3ad772dbc6715cf78e3b377ee2740fa3c4eeb7c63abc6ddb36374ab106f2a0a7db174689f5b3c6bc59edc5da46674ec126cefc0c7bf3384af14b4a29c518538a5f524a4f31c6c714d34d5ff131c618638c31c618638c31c618638c31c618638c31c67847d5b8bba9cedc37fa7975eee6cee93c693d81e332f7cb715cd61b1e4bd8b394810a21d2df4803566a4d4974cb2119dd47ed91b63d24432c715218041108620f4d6c19b6a629b369dfab4bf000967e87c5e30ffd6cda76042ae914ef56febd9c4fdf3d3a85d374ca4332442b17c97cdab4d6b43dc24210cb6b8f76cea2e872393ddd88ab887844b4b50ba5951789366110a7530f44fbdb9e823cf4bdfd0efb626136ed5bc6f0749ae57c1ae7d32e2d757a9202b5c183f52b88942211c37236a54a1ed0696f9a878e65bd5ad57aa4b0a92e3310b385f40f10adb2b1be3869e2545ad20d146edc13e4e2249c6477268964095393d393943be4d5b0c89b302eb89092d24c2c130e2e6aac9e9e06e12020f041f7578b1c39b468123ea573931acb8193a1d458bd3aeba0548aeacc546335a57313d48d1b23f0e07f23c70d1d3746d800f0ed34546e60fc991b239c5e26e7dec768d9d24f903d50cafef513a469bf2101d441dff71500361dd47567e9208e7b0bc64a0ec5c9f92c7ff98328d541db761b777de927881e28c5a4977e28d64118bfe5eaa07b0fb33ac8dadb9cea20cf7b0daa8334edf42edf98549da0544a6f4c9a0897a4be35517582c2506f4d28c948c26f4aaa4e9028ea4dc91149df8ed0db1179885963aa6723aa6f430ce18391aab319295263e3f6b469b135d55893cd6983326e52362ec60d6a356e61c088fc473131462e57266ab5ae88a295ef3323e72a35568328d577a342b72923d30fc09719aaca2eb5a4f1e53685bedc8cd81d2d67d188de8ca4b4912aa0d69c660ca7cca578e3a50b8cb983a3b7293b38f4ac37a997b907bbea596fc497b98c5dcb8cc6aeb3ded478c915ed7a98b00c11887e4d104b0e96a096961170825aad9c209812ec7f0ce7350c9f52a9725a3ac21c2c4b3f2dd7f9ca46869a38368e630387cd08ac5bd8a9ea35b62944aa4e4c07c5623a13b1a1836cd8a8f12ca5862e9f8daa8e8d0eb2b1d1d9488b2b5507a6836030bd5d8132a5eab4e8a09616bd4d61a252755874100b8bdea83031a3eab47450aba537336a2c56cfd2412c96ce4f35a6d241aa03810f523995a20e0a0f04ea1f201a1514eadca4a7846588406a0e3e2443048d3019994e3b38ed1aa6ad89ca942a1c51dd29428f6119620a28f5b00cb1c79c38255866a6d448c31f88f9076211a82275db9d0c556fea6bce4c3567a69a33532e32c3d8e5d6b44d2161975a95cd8856454bd29c5abbc44f569eba5d62228c241c43c4bbbc4bae947b748f2e91abe41ab9435c1fee11b7c84db25099698ab545987c68c226d92396d8219458234832539d52a7542a9648b552afd8a36a8635b244b5a94ad374da216997d54815eac49499983841b152d2a4a42368130e89bda9b409201a2ec9de5425550728cc56ec4dc819415275806832d4942a963271441c19e2a83314d3ae870a0347088bace78c6ebe1fac529fcda829dce08868525472e0cfb07e0443e6787dbe32824c3825c77d4c0bc7fec5b2f4e31d46672b5507886684175d5af1472fe40d2d5bfad9eed22555fd47bb8bce4c55a76cd13f6a8c662655a764d155a76ce92655a76469a7aa53ae3494aa53aa747eaa3aa5a8b31655a70c75965275ca94ce5c549d12d49fe6f2a6e16b6bad95b3514fe841a5b141731a1afa239821b60d016c6c5e52223438e625356263613534d9a6c64c816624e75b322fa279216f5e63e3bfce0100091eb0b9a6a961034000ee1ed0f1344802fce8858495f0219434e1d74328a9f32194dc798c1e42499e0fa1a400be9b791879f38d0bcdeb4ab2a2f008861c6148b2e7f3df4ef8d10b790a14a2f0cfbb6e0940800f40860004780a4730e48c5e2a2d016e68c8efefb80adf6d62b89b97132a851086bc397a2165ac96917939b9a0f6d40c4d86c586e6e5ae7b80e6364034387331319f9691da03b0d778e9528966e64a367a3c8c2e5f68687ed7cdd4658bcc59744b97ac98aff4cb1254ba146d1eea942e9560301b9b1899199acf5ce63157aaa173e8d0a1c79bc76ee335405d7edac379399b38fdacb5f158db8fba066673d86bae3ffea75a96e338c7f1117e8300e38f9c3df2ec5021853106095460869e9186ecbdfeb3afa9e357f1dbebeaf9f530eeb5f5eeb5724d28f094300a608c8167fcb133f2e88c39269430ae20017b33030df309679480bd99c1d290c333d61857b03735f6230609d89b7a3d56609cc1ded47fa461cce1d91156890dc1541621bc09a361967023003746c83102ce08de7fd808b1df8c30c2d10b69c28dee343f7a2149b838df9a356ec3047c981fc1902690c07de625f561cf7facf918d86de6a709f72f25d4bb02307e92902f0380ed31d7fe02a251e508713e977e0dcbe7a885e81e4fefd24274bbb47879f97d39a5c0c01cbf9c5cbc5ed75e4ea898986f2f67183232cf2fe7949999732f27151a2d4437cdbb6b21ba7f6f3ef67d7ea65ee2c8617df5b20496b75ef2b8bce5250a35d72f6793cd612f67131baff1f246c7ec6dd418a9cd2854e91075861ab3d7233522438dd9b78cd4870ad02213182911098cf40802d4688c81128d140a75a24d68d34899d4987d1374498d2919c78f333263cc0833beb8c6900c312443accfb971fb1246b871e34258e40b3858fefd08e647d501a2c1615bf77ef4b2e3f605c88103e7472f2de860bdfb110ca9e3f628e008affd0886c471fb1574accee5501d478b3c386ceaf8472fe4085ab654d21ff0f74730e40dbd54feb4dce5f61f787bd1be15de6e1775cafcd56d77d6adf756f99de52e3aa6e759c6d6f87135bac6d6283aa1a0e1f18edf908d2a4ee1cdaa13342735a34a954d85ca94296184b1a1a0b8e0428a14fde3478dcd4381e2e4d4a449130a28f0f09450420e8e3d1f8bc56030d8ff98981897cbd56ab54451fcbe2fe77c1f6475903d50d53f407307d9cd062f9c38f4214b688658efa4ba3d4d5afd088654a92e84456ac1a2ad97d38c14644521cb8f60c2a06989b72f555a3f7a816ac92d2f271516c77a39b968e958bc965efaf96ecb398505debe902b2d5bfa499d46454555d94730a44a2ffd84b751514578dae59492c3339b9cc2f1c7d4e21bbd30464b858ef634d89d98bdb1b712962102d15db3e6b4e127ed4ac116ead629965699564233c4b00cb1477d10dda22e33e0dae2abce00cd16b5904b75fb1f53cab6130ad4e59cf269dde97236b1cee99267dbaccb9c6d57d80149bd997b5b618725f5666ebbc20e4cf5c6b5ed0a3b34d59b5749d5fc026fba3e7f01e0ca717d1ee672b94c088b8a95c3464723f37a116b3edfc25133c28f5ec89acfdf05473f06a6b2a872b0be1a12f46762461b356c2ea4e6379f8fc1c1d19f8919c1bb6cdcd8cc67120090a365427274c46a5c39c70d97578e1b2f171b969c50c76bc074a46235638e7b18574b6bf589a365472f240e2d3b822147d03221b19a91e6c6615c2dadd585b0e8cb84b8b0a86ef4d2c68a79b7a197becb84d4bc865ee26cf412be109ac3f492bd4c484dd5d71ff5ac26d45fbb68f452a8b316ab09327a29a5c5189d5fa08e81f95c7abac058f452d697a59734ddd24b7ae96ab16608ac39082f27e6614b5c5d88b7432666465c5dc86b82572dc9c0dadb85b89c15ea257b212e517471b5ce025ec8771891ae529f8fcd749ff7d785805f7d9e4788ea319709f90ed3ba4cc8eb2f9f5fc1eec8847c97d14bf93221e063f492769990173ecc8b5edaeee22ec4e5adcfe7d5591792baea32212e7a4988ca84b4e8a5fbef42c4b3e82519582ebb231b42499990965ea2dac5ea74cc4a2f715a54650d13b7502fc9c0da9a7efef492d052d59f90d61057ef929767ceb5ccdbe62f6ff7f638ae96d1606fec53c0f7472f2407443383bdb13f7a21b570057b6343ed1510b9bce91c7b63cf636fec77d81b7b2dfb616fec63b037f612b061154a442b668117d3daf4421a0d5058858848a6eb5840e10ec2b2bb0bdd4de9b70202b4ed202e9b76a00a0810dd415cdbbb106db1505d8a0ff2a2f23ebff28000a9b083b07677216d534f972198a2ab1410209a1d8465d30be1ceeb74093e08cc064a5dc86e140cf2b23b20bb83b836bd50dd5d577a0f02b329a59f657719589bb5ad3495060ab178435b53b5821a28fcaa09347f59cb727e90975d7a5a495614ca6efe110cc93d08cc2e59bbd340349c9615d14445459159cf6a02bd101679f4426e5a06d3b4ec58cb6260c8ab65ae6a02fdd10b69b5ac55032f17291e7d2ab15c9636323262d1473c248bc562e12f6bd7462bd0132f556923154be712c29914b59128c3e5cfdf2f64b1b48df5213c9f0782600a831fc2d3ea52a9548b4d7d080f99ba1006a274f42259ae7414235bdac86848265974abf5f0f3337667ad74944386baf5213c64eb42782b15d1a86d63695a69058220f8f91083572ba952a9540a4810bbad562aa25164ea4aa26eb55aadcf3f0cc3300cc30fe121c38ba2288aa228be8ca9542a954aa5fa101e52b563646474c443ae7e142357373a7291ab17855632b9ba51688588c845166d578888c855914d83885c7d080fb9a2993d40f206da7610bb955a7a9e35ae46d5288ee138ed8df6d4088e221422dde594f2c519bb3e064bc3eca144bb7e937942ed79803298eccd6cb237d3c98a4fd62545d3e5a16c6c8acd39401955eccddc567b35e58557bbbc51ca8363df5f8067ff50ebfda620aa5a257ef770fc92c4ef3955c77b7dbee2b4e088a3c6eeefe9588d65d11b3d2d843d101515e5b4f1ef057836d63f575b7d95eeb679dbed6336dda92b9581376d88e1bd175acc856018be6ced50277ca84b7cfb908e1e0e437d41fdb56a6ca3600ffc5b6915c22cd05490562f3c8f8a734eacbfad6af8f44e8f563ae79cb3e680677504163df0b826ce18367bc0dbec19b3e129a554a3e1a759cf03c70d1cb591def3be6dfc0e03f2e8c0ef20c802fdee516d9cdbda6eacfb1b539f3796b0edd997ae186bf5b43b2c5a5c1d695d1b4190474ba585ec9eacf11347fb8a09318bae1ad32e8eda53de387707f2e8f4145361ea7bf8dd0bbfbb27f869babdf0135b35c68438e93d16ad87f68e7b15bbdb2c44852c0b332a2a2aea560e098fd249bba042b527942e96a07441442db2d4523b034b7fd012ff07ff62ad0b5063da1d0b00f258c1c7eca177fca17747f6e13ddf5b8df7b0aa7355bfe3dcdcaff7170bf7de59f7bce3b1a4bbbcff099f595cf05ede67d5ef983af87cfadd822e78c7baccbfc7ba1c4ac5b41e62d659b8b3ce855f3dfc62aa4e6eb96df90135f74febb2e3f2a5d5a6ae12517b79f77d0e0e6d558d07afa66878abddf35222db6336a9addbdb9a81c317d2360784bb90b65b9f200b1168dd731848cf8903df310c63aace8b4eb085dbf08ef5302df6f66ac6be7fb1507f5987528330f67d7dd8b970cf43753b7eaf23f898aaf39d722e6cf7605567eea12d7e47ad6fd531f5d6f8527df5b9ed5bdf51596f8d2f16b6b3be9da55faabf58583df5d5534f5d753b7a3155c73b65b1c7f8ab51bc6aec31b4454dcf32667b437f521a1c7f2cbed563b995f8da29e842d69a8ec98038a1d8c068830618aaa2fededf6df5b759353b79428636b7e9e985ec6aed4f59d031032ba2b60cef8a65185aa2cab6fa0297edaa95364fc8a61cd8f4da52b6e905661858cf30b328b2afbec0cca2c8b65a49362f247c42420511b969221d33500111395f7549c3041d20da27af868d2fd1bcf37306575b3b718038d2825274c4aab127940fe060aff684f281277becdd29561d8beff10fd6e2d577f64c4bab0d9e03c4bba71332986c7a2f748088f77452c5d3acb11a5cde347c6dbd8068a3b8d933adeaab31ba67530e53ec5d7d0066349408c1b3a723942b56ae0533108dba62b43242c7276804a1d65ab1b602e3de7bb5cddbf3091a52344ddbf213349cbe3d9fa0c164e22df29cf33b5ba63dfa18b77d756df09ee7b445a27da7ec71e30384fbea9206a1bc3fcbdadeb8ed476f2c3d0478607b40be07f12ec4b2f3bd2983389f1041ca16f77c4204a7cde57354d352794b95427483a7af1c5407fd77ccc67123265603e6d29f69c5c0b8c496d6eadb22d5419fa23ae84775ec8eeaa09f600fed02d9a8a87da98e5da98eb9738eed27681cedf9840856eaecb1dbf873f56b85a95f0698dced487705796cfa65ff83bfdde26dbbb66921bb395dde07a17b1389f61589b6366a1744a27d27d541b9276f561d4f071ccb9f0eecd11d5f66f6d0bd3d3bdefa3c561ca2bd0151a0ba9d8c8068fb4c28a2f94a95e5ce3a48f6418940a04b4454c416caa77b822cb8ecac4b0db8beffd47f75fc9a00019a558888b6d06a83b757e8dbe07395bd01a9b0ebc4c91648be90b6c12ff60644b3b7e71148854d274e7dcd806b0bb9f6a6e7c4d1f6f4dfd8e39e1b270e7d1e65268e1d873e7ab5f02647812891d52ec657b35bcd41b8b2dbf8a510dd40d60a512983b73b2bfb4fce98b2ebd17eb1508fb6d76483d1e6d9f8c91952f6378764bb76f70bfa70edee68d7a3b1a4652d2dfef0586a1bdfdb3e3cbe58a8477bf6d8a020405b1d8386367e0aec518982d88d0f96f8ae6e4561764a7d83ee8876bef4b811c8be4e51526354965469c20c262b4cea9526a226d6a8ea000112c4b5f183e48d0f4448c8b5b1d3b6f8c8ee18191915d154f1c8ef452a40bdc8ef464533aa898868c808e4f7212790df8b78888888c8234c7eb7ef11f15d10c00fe5d93d22cf2eef13951f7c9e3d41167836d6402aec0ba547a47b360d19629796ca24efd37dba4ff7e942a926d830768fc8dd3ad99d212998e8c1d3164f4a507440dadb29f6c8ee4cb2044a455022011852e4000864699f8e7ca0063b5c7114c511516469a54c1b546182043b5c0101134990a585caa2ebb11c7bcf035d183777e9ddaa86b58b359cef76459bed2b57cbd954761ed1acf7650559a8363c218291fd7db74dd330bed7da5a6dd825dd1387ea12d7a38dafc7fa66dd59ea2ec802fec1f76580597feff9a85753d9f7f94eac4cd9570bd91d846e2a126d3a8a4496ca79705ac8c95aa86d4523c46db354b6bda552ad19d68abd7289ae51d539ba448ef61099efdaa53844963c3f32d8ad3d689714b880cd4688e56cda7468bf50bfa3002bb400017a02c545b8caf2811a1b4457cd539d31883ff794a8ee1925aa894df73cdd993bea0e8f69c197f669c47d1b8f32b97d8ea5106e1cc2436e41eec67ff9a049f6157c69fac7f6b95f10c952fbcb47b7350d14ee39839db3cba16d4f8928d1cee3b437230644fa49896e124c4e4e4850705284914dbf22a4ec5c0405cf3b9239ca44aca75288f9dbeeb525bd9a18027cadd562f16c186f9aa66d541442d4156b9aa67d7a5aad746adfb4d77baf76a90c43987e39f03a2e6f1abe7bce29da97b5d65c6badb5d65a6bad5e4a081da275bb410e1162e972c51cf6fac17dee6e54f2b4d1101e92bba78f32d965ae1bb7736356da6ec7728697bd6c36911bd8be81fc006c9d7b6dfd6bea1ff9562b609239b8de27a208c4228e2a1199ef4df02893f95529bf9cd7eecdfbb9d1b02fd626fd11aafbea20762b6d603b6bebdc6bab8d8a3a61523aa3a2c8ed4adb2708830ae4a66d199e47c539566daf1def9d2306817c74bbea206f6a57d36ca55aaea76478396f9ba6617cafb5b58a4388f4147fdefca14bf10b3cf148e628132a85485f8a799bb3d671d4e69c964e9c795b2d160fadd5d22c0a21d2e789335ff2ec59e6ec291e11e9a9b7690844a3f6cb47b78168547de2522b8438e79cf3a342503ae7bc3536399aeb66b58bf1b5958e80d211887497dfae3d93eb522f1f5da674ce39ebefe7ede9d49a18027cbf3b71f4a5ad164bb4541442a442ac2ed79ca73a5b4a85486fadb5560891cef272a9970f6edbd36d6bca08464f689911a0fa70b166c50f2be896a46cede10536586badb5d7da56cbe5c319fbdbd3c90f50ecb265b7e89e3675c24f73e2d457f10651dbf45ebd78a354a3df2cc8026d01bdf7de7befbdf7de7befbd9f4e7ad08bc416038c1f8cf1039c264f9a604c91c01a8846617aeab4e7c4c1f41376c521a0343b278e0bd469dbd268d29ab41e88ec328b382787a7ead49d93f82b549dbbf15bf87165e37f55a76e9c8431deee64cfe3c3701ad6da5bf7d4aa8605a574cf536d579b3a9938ab8def0dedaa6973e2d0cf89a35dd3a5f6794defb07356c9353c893988ad4dbfede9a4872176d5f469e7e4008010fe831d50018218d648ad43a8439838f53d415608215823b3c71a9951b6b64626ce0de26a97d4083d2104554412eb35ba63801d789893ea18608710ccd8dc656a57fac49ebb5c6d03ec4064c6e80906d8a1c8d43193be1178f301a27d2df3d6599234451cc4e9840726db090f4cbbdc76e561c9269decd0b4a9d3b6f742277baa4489f6dd2e50a77d35108de20104dbf4461b884651277a828824ce53a5d913448d36559a38f33d414820b1ada64eb327e87eb68062317ba8d3c419291613679e3a993ddbecb11a286ffa6ad1f0a6dd7979ccb59f339fdde77754c0eed87cc27c9e32d99dd7c3d4fff97dd67cb23e1f83dd597dd6f83c45627744d7f5e769b034c9ee8c600e5de69de32ea3d6b3116e1ce72ce3a8677a3cecaab1868dd778ece0783dc3c171967b638d9ec1fe9ae7d146cf70b07c846f238c9ed5b0f9cdb5f1a56baeffe33146cf60625ec7193db379d16ce8659cbd3e9bf6a69c4e8a24ed1c336fe12065661ef3709ccd68a411c819f899c3bc83d1482992e62f3d7b79f8188dc442ceb6c3dc35cebad75b678db357eb314fc53cf59646b2e4cce5e1b97116a39170c899ea31a977bfe32ca5910640cebccf741a29869c7587f94b23b5c8d9ebf7ab7176034723bd90b37c98e3d048999cbd6645b32172048d44c959eb31bfa191347296facc5934d28a9cb13c7ccb38bbd1482f72f63a8e69241839733dbc8d7fe3cc8646eac8d9ea3135345248cebecfdc4623f19033ed3017c7d908d34836c8d9ebf6d74837e4ace521f79f8eb31a8d54c999f89817cd8648ad91522067e167f48c6b9df5d555178f44c9998f1a69879c7187d133982351f2f8f7a719672f3d43a2e4eb33dd39edf432e38cf5183d7bbddc7597b79ce5489484d1b3d96c163e75f0dfbdefb03946214dfb65748d317b33ef32feb037f32d230df6669e659c678d39f6667e35c6606fe6552325b237d3decc87a3686fe653238f0da9d6f8f23ea3f060c5c3a24aa5d22feff38abd7d4b4d6b7f3eaf356573a7fb7b771fa4ecef19c50728b6a771a3f72dd43925d4d8eadda89da7ea889e278e3c2594dc3bb0c7778e5b61c7a6f8e3c6594fc5ecb53e591f37d2bd7a275ea54b106f13517c99935363d613bf6f5c1d1cedce9c0bc823bc87bf10e4740b436085f1340bd67778cbddf77d5fa74b3ab465b09efb3e9047fd6afcc15fdd6379add54a6be958812008ae5abaaca5108ff555b01ee4913a057be8d873e27475779f5910e1607b14f4a9fb7b141f8e00bffac1afd557678daf9547c183c75f753b5fc3e0f77d1fa8b97f87d5a5ddddb5d203df3bbbbbcf283e34b127e8c3eeef29950186760defcf96536d0b43558cb4c7466bb915d1e330bb1caa62823eba5dfe381e4bcee3b2c6b71967ed425ac659cb7a4e1c5ac5f4b8aca7b3caaac2fcc09bb7d6da496d91a32bb6926096824d0131b567911358cbed19a508124017bc463608625340f48a3ced32cf9e222bd8ad1d782d6f7e04126714225776941c86d8f3ad3da3a491b4e93f027894524aa9bd4994d28ba35c1962adb5588b8246151b850895286838d9288e8852841e766b8c5284a31b9e4165e2798cb52d4a0e44b452567ac032f13c2a4e4b7503a2aaecbbf175b6f9cf73c5c45c6cb55cb3a7fb3771b22ebf4d6fa6f41574016ffcac85eec6f7fe36f9b010ddd86a41e4492a8a0ba2c85188a88003f78c42c4891dee19e5892cec6bbd3dbd77ce596ab7afe38b9bafeda5cd5a6bad5e688bc4b969cdc188c9beb7ef993546efb13db69ab518d38b8164d75a1a78f6db4bfccd8e2fbcc36e9addae1ddaf78b321b62e3577a42692f7477e909e5cd3d6adbdddd7a5eced7eed1fc09fae077eff4cfde513dfb69776b1bf55e96ddedb7f1e5e5abfdd569766f2e5c20bbf18e6a338dda9c8e6203939d0f608ff5ad1a0bbc20d1556374baa04055d9a52b9ca73587585fbf3da15cb9b2eb6fd1aecfb13b3b762c597ebbba70885688b39c4d2d404086d9634bd7de31566181e71a22664f285788583e487d019302c79e50d00041093668b520853da1100109660c161759a49abe269048193963c602c297839728304cbcf68462c5886b4f285688761114d74b6a6a37783944fa09054a8a5793903031352515e1ca11a7b44e4beda4336f594f20d5c35f109a8da701380a1a6082b1e7bd1f7b9cbddddb3e3be297f7b314b25f06d8f839bbac7bfbb4af7bce1326174bf6a6cb9b4b271188b68a4988e5e4269412bbb4303908b06212e2a6cbf27e5a7085793590ba95b88be15356accb217005d5b62f1838923bdd150cbb2708c3a6833bb30001de138441cba1086d4f1086011855b15d1c28c97b8230e898811530582e7ad043b72708834dd2095ae05533566003177c7b8230c8e450021f8a00f70461a824302ac3491530b478b144d194704f10060ac60978808410515124f7398a7b8230a86ee005126914c996ec39d5c663a90161e16e7cfb570fbc8168d4eb471125b9e37d375002ba73071234b4b72b60925e8f9f9c7aaa1b3f900395beefb0e08f52f775dc53e0edf81d8f403c708f1fa5d4e79621c856ea74f8b9bf771d993aa7893a123c05629f80f0dc3d3bb270836c25eede0461d02051477e1a8c01d4441d19bed3441dd97d47b5230b00d8a5d277198686d84adcf7efdd84214cbdd344330c12dcd5a9b45be9480592fb9c3d2dd400061367ee166af03475cc3d75792be843fbe4b8d910297a60c905edf3e00b4d905df7c3fb8eea82f6180220041492d3425ac801134240c90113424421b90fed09fafc28719f5b860e6c25224c722d5b3b384a609c39e0bfcb764c7bc3cdf9247e92df4b3125ea25fb0f609741585b86e90409176ced3ff3da654b2fd81b2d9b414fa84ff23352a52b27141b8a6cfcc382ddf341565b863bc50aa25d817e78b707470f78077f14cd86c8214bf84324770bb29002ae626f8a74079f64f4a9836ae29ea068124da24b8ac86643e492fdea07716aa019b54b14f64f10d69681c6091a2c215b7ae17bea44b24aa48b7c92a0955d56a24f1b5592fb243b3dedcdcfd4e53cd63f3e437b3e486bcbb00023667b7e4259a2ca2e6b91a95ff31a80b24b0d3cd9b38892ddb97148253b6d94bbdb11888fbae71530c9ee1e55c02481c2a83d5f9d660fceaef76c1a44f66bdaf185f58fee130a9416fb3535038648ee130ad4d39e50a0a2ec054c92fb553a2a81e43ec10414cd282bdf51bbb1688b8a8ae274c6da17481b7825873ee0be1b74b70bcfd6e0cb3000534d46285e308d50ba88b25b148c255465c9f0c248d5b935b6fa00ab862b79cc2196d5a9955475ca5a9376ad47549d4bb4eb84d245ad5aecfada347be8d4f09db6d255ca7e529c2a3db0ad9e13a7e6a1adce74e1394064f168441dc42be6d0edd053218501a070428f007876744c28210024002047470e1c1c23dcb889d9a86103abd1e39e3fcd8c4ccc0be6c5e5d2c2d262ad546298023fafe3f2a6e1d9736fd37ccea2ebb192a7be690862499b422b37897ee1cded393c54cbecc1df5176affa36cab6a78e4719bef83ccaf25bae8db2d665dabd73a38c3bebdd48c588214b34818108960401d61c0683b52e63b18e04235997756f6939eb0b316c100426b6b04110a8a8542af02a2d53fdbbece5363636ac9696969649832a9c30e102307eb89204044150cbc0b35cd672180c0663b158a9540a0a0e821004151a9c317b227596cb6a6e131313e379de0a74a081152ca09a4200c5bbcc652f87bd5e2f192d43c10527b062044a20510426a2cc652e181818954a45821849c66862c81058f4a06209c390c5e572812018f3411769c0c0c80e681881abeffb58585852a9d48b490539788111327438925aad569ee7a98ac801d1932184714409ef3397d58461288a62b7c4174c3500c2882590103f73d98b8c8cccf77d2a954ab3224410948a74f1450a5a5a5a5ab44cf599cb5a6868686c6c6c66b42c0354c4608c34ca90c2082be0c7cb662ef31f0683a552a9d20745a480ca18455b1091fa7899cc696a6a6a3ccf4bc00a943002041d8a8c6c68696969d132efe36534ffcbcbcba86506e8c10534f8c10b8458c1103d98bc20428923582091848c8b8bcb101557a6a48106104200e1079a56ab35740492232298c28c1bd0d8c10c8bc52af2451551461c21c204894c4c4c0c11309aaca8210645caa042f37abd8cc220e2034e40a0822d803003030333adc0408a0fcc30c2851750d38c16407521c618548871654e09e2481a5338e1840c124c1b6481022c862005085850c6cc824a122c9408c1901f9ed07cdf27001d8a8060a244099a0063c6c6c6a604292700c20b223ed801090c06b37184087610e58a143310a2a6a666e6880d6a98dd30648b18bcc06089113d60c1931138894e20d1a1e8873148c02464430e4b64204593a6243040b1a2c91549000183223109c02205452280628a238cbc6262627c9ef0a189252880c195252a98d7eb459600073fc8c1061c9c6007d0050303934aa590e420041652e0e008aa86148bcbe5f23c6fc8882f88e0830f3a6832240c6fc5c2c2228ae2100f51e050e405471cf1400a315cad562a956aa8861ba0a0046154f1441739507d611882202804273e300326c0f0420c24a0cdf71d4905f24ba55262cc86e081531753b4c08b54cc65301b1b9b1983218a8c8882ca163af8c27b5d560383c16614208e5471c5164564e00311e6b2979a9a23a940d6a854aa1c393829cd6e7084072454aecb5a5e5e8ea402f90282a00d1ba8e0498b279cbeb802b25ce6d2d2d2d2f2c189922a4f517051e597cdbc15131373d9eab2968b8b8be7799a1846784004910e466378ff653267bd5eafcbc2cb58add69154205ba228322006612039c348922c4af0cb681e03030373d9779966b158d7b21e553e9006133e30a20815d0bc684633f397cbe5bacce6b2516b1da365435528f9c20a1a4360228230665e349b39cd655e97c91c868585e532d8651fc7f1a565434b6ac0e20ca4165051411534343497cd645e3493f9cc69602e8bb96bb55a5d567319cd7f182d1b4262848c2830f0c15318456666662e9bc5bc68167399cfb82e7b9d250cc3cb5e2e9ba1a1a17169d99091227c48a2441947aa484246e64824903297bd1e731996cb60befabeefb296cb6466666658b46c0ae10a0e8e20317285524c4ccc65339817cd60fe7accea32d7431b1b9bcb5c2e8b9191915969d9e4424a12299ef0c0862168f17a1d8904f275d9ccf5a2994b063ebc8ce51f0c06bbac75d92b26e6482a9031a1964d272918c10f4804e108164530303097cd585e3463b9eb30df65abdbd4d4d45cc6ba0ce6f57a7d5a8663c6931c82808512a320b85caecb66ab17cd5667b94bcbbcdb5c161ef6f2f27299becc050373241548181b2d830d4112450ba6400193182c2c2c97cdc217cdc2afcea265e261977daf696969b96cbc8cc5e53a920aa40ba66521940e98a670c144124eac56abcb66df8b66dfc3afb44cf59acb6cfee2e2e272d92f5bb1b01c490592a546cb30f0c50e55c0500193276084e1914820c3cb6cfe3d84bda5d56a5d467359b85a1d490572358128356012620857fc0083ef3b1209e477d90cf6a219ec36ffb42cf596cb6aeea265b3238d647d866449d6917048d66533977d9f1d6d64f819120f191e490532f43c6f47b9a20414348e88e2838d8dcd65b39a17cd6a0ebb8dcb652f6fb55aad56abd56a691a5cb46ce8c80f6ad002084a0c7184133018ecb2d9cb8b662faf394ccbc496b36650a954446328a9020c2935585aa2a6e64824903597cd5a5e346bf9cb6bb4cce5ba02f3864e207c1184122ee8e1073c8cf1f2f2d2f2172d0347168bc562b1582c2d01190c2982698a13a4b1031cb4b4b4b46859ea31c08cdc008a3086c08318443c9a1f2b2bb680f221092bbea0429c5961de50954a1543110f6270831122462a50c9ec98371404c1247408a2065dc4e0890f18015d1ec323def39e4aa552433c1401a585132b621471c4e545339754ebaf1cd5c517cd5417ef799e376960a50915479604912444eb45b3d65dced232ef2f97b10ed3d2d2d2d2d2d2f2213c648b8ecd1b0a5ef5a21978d545513c128c14b5ec45cb0490c492275584c0081e30b9b8b85c3663bd68c67aeb2e5ad6bde532978b8b8b8b8b8b8b8bbe983af8a259eae0552ad59160a44acb5ab4cc023510821952882841c1a4d56a5d36135f3413cf7a4bcbb8b72e6351a9542a954aa5d22ecd7bea4533efa98320086a594bcb869c80a2e28c3470d084104ec062b12e9b69e2595a96afba6c058220088220a845ec794fa552478291292d5369d924e3c80b78e0a188299e442145f1482490e26533fca299966d072f0b53a9544ae779438bba732f9addf38e04233d2d03b56caae00a1ccac8210a23b83082947ddaf33cef9e77ef4378484fcb66f745da6533241752fb0c2907a91d890452bbbd6c86f442769f21cd90dd916064f75911f7fca2ee33ee59743d96c3530a6d426a68038678f45696a52caea3ab0b61ba52d1958aa54a7dfe054624c597c89e3d68b2cbbb6d0fa2d89e673f4a6d0f96883f4c3132a5dc0368bb1ca2dcd69ac47a8f8a4c9be68d1ad9f51a9d5231b5727fe9bd4ddad48a664b5b65d353245ea6dd94f25518fcf2cb297a7a9b8890f1113eda2e930635650ab6466e5375a23590f513b4c2c37bf998c7f799f3de7be7bdd3dbacc5f770ad35671f3e57db7a45d027cf79b11e270e067db40d44a37cf03667e6baaef338aecba00f0f8fbde13aecb95cde0bf8e338dc591ff5d8c3f75e5c2fd61ce853e7f43ab0de8ede463f51fc40056cd73ef005baafcf5c616eacedbd97ce9cba3e17ece179413edebedec6dade876e1f3abdefcb600f6e03d1a80c622db4278e8883f774a5cc1edaf249833daf27c50c1e538dd56fdbb631d89b5a752a50a5983a3f33562f65db26d8f595c863aa40d5f19c7ea68ef764c6ea3d2629a35645cba841316feabd2753476362c63ca7aaa32da942a362f678afd7a6983d4ca4983d29d0a2d098664f7ebd06c5ecb9aff798ec13b3c789d9c36a62f6b498983d28983d4bcc9e25b347553d263a80a33741e128876479a9b1a0a6c072134a5a43a5399fb1eb3722ec7a0d8d5dcf7545d8f55f1a3b6ad7839468b7c60c4c9c7ad61864e2d4af460d4c9c89a31a81264ebd38029938f5e1182434712aced7726f543ae2215bde712dcff7db18440ce2da572bb5e8198092cea80ddb847a0fe3b78c348c1b6b6d77373001d51dd4c4a961ece0a833d85a42cea2682dcf166becf39e3c2a7809155654b1e9922dbc7bef8e0a1045ed5e4aeb6ffdab9d3edeb6f7f17cbc5d2fa40ed95a87989b02f2b757ca595badb5d6d29953f6334f9c89336dddb5de5daa54b58715b5387166cbc7d5e3387156f9db67dd2b01f9db5fc85ba75597d20bfa88b5fa840a005fc89a67d33c9a4feadb40342a9c7b037bb054ae6a15119dd55e477357bbd405a2513e342de40ee1ba89e1596b95b0a9241e0861a3f06007cb83215c116c941d98b16ddb4639db7d963a41b2032e78701cd77951a22889b2832b7660054623010aa0f6dbbcf7de5b694ed9d317c9e3ecc99f27d2138cbe0fc9a4d112102042e206a251f9427977074fc5cc9ddcdee1b14e9c5c49701c72722948def9ae76377e1c67cd8cd5a7461bf3a61e1c77e64d556157189cf3cbeffb71e751dcdd78274ec6df3772a4a77ddf98c98df4f2f78d1ae971df3762d2ebbe6fbca4e77ddf6849efdbbe2ec54d6d6cedd448553b35667b23d64db437f4b6807c6ec7d4f652a3253dfaa5c64ad69b7a6ddac7f548709c64f5b6760fc8e7a61513c47504040edb8e3eeedec6cf9bf8dbaeb6b99a57e0f9b0c71fda94d6fa7d3e2ab695f6b8d3567ab10f6e7db44e5a53e550d7bde87fe6ede9b415f4d15e09d8aefd05eef9dbd0b6a00f9e383ef77e5fce17833ef6f5c276ed0ad874b637f6065b0df4a9db5aab8156bfac1d83d06cfbfaaaf6d55a1fa38f3dde5e3e53dfbb3368c11eddecb07ef9e06ed5f30eddea25e17954ccdea454ac13acb6eaabbf1aa34aeaa93893444ac56b67b53c5649a2ce2a20c432b6a70f923d6f89260e0db2f283b5bac766a5da7a2dbe1763ac699aa6e241c5430679a878a852a5a989878a870c1e60dbf71e37f3cbc7be9889773f27a535e75aadd5d3d27a8457ef85aa1ff0f7078636c5b8729d16bb49e4fe679e4e7ac3248d66dcf367de2b01dcf35fe0f40b5e5791ecc49938f608bc5fe1de7b6d5000f702a7b3bdb1371a781180a7cfd0f636f0feb57dcb18dbd1c787be3516b537d0473b055dd0f4cbc7dd38d087eebcb9cc4dcaa5f069d49ea00b9be6c6983467d0a7476ace3d54291dc2abf77a7c9e057df2bc3e5727e0b65ad6e5aa626b7b18ccf9da7b6f8f3b71e8efc499f7c21f46b39b59c803f4f942550fe2057d38d0a71cda17f4f1b8c9f9687ae22c2ecddf2622d26ab1ce7584d9b5c3e16ed4527a91eeecb9cfdf166df7e59bed4ef568ab55c694b0afded04a3fcf7eb5febb2f1ff7de7b650f61da39e1a97610cbfab47d286b52952a3cec408b14014199a3da41cca18c5d9f6a8cde8a07048846799783d51df0f3f96eaf76a3d152bdcdf7eea81591def67963f6b86e1c72b2bb57e7047b789b06a231a79a40ef95f035396d6ebcfb7ae3a4dcd23d123daa36d821b5ce9e20229e27500d4448dcbfdbeb29cba6b7dfad3b464bf455f54daae236d0ea29661e59bf7c7ce77f67ea1a27ceedc63ce69172209773972a856caaf3c4a1cfdec8636fbc9cca79d3ba318fddc8137ef86ae236b9bd691c72cd2df066ddb9e464d1ec32060132880022892c6a2841181707227a300383092747827006a5371041062c3862b6c412241dc852c462c9bdc10b1ed8a003922343b0200bbb6794165c615b708585a2b46736b9428c18804006370041116f424413226ae04d98cb355d2e1a6aacc9afb5619da57dfe0579701cc7e957f71feef81d777c8eebbe7d68d3b1fca8d0be65def4437b2b5947ec12ff607d3fe0bd9584ead8b3e6cb69dbf6db8516637ced43dbe66dbc9f201ddadfbe76ccd4de00aa524d9728014110b4b5b504dc34ae7be71dacb585a4d6f02d24e192da1d467bf875af9b86bfd4b521782f95aa5f92569256925692594b89d6626a219976da69ab6d2dd9b47a2b0251cbddb59732d8efdb412f6541cfbbe71df43cef20a84b1952deea9ef85a2f56b15ef5954a0c53a02e3fbd799d6e256925116bfe01d4ddeec6549bd81b5ea2441445115fb1a5a4a5a495a49544a494de72bbe237fa959e561ccbd692cbe1903e1ca9eeb8534caffa54d1dfaf95a495a495a495a495046a062d259795d442725b4c2d252d25f6c9de6ccfdfb927bbddd3f6e9db55f52d7cce0f594a76be7855a8bd6da4507bebae7d5a866f6fd7ced1df9275442b492b4958b52b1e154aa97854cf3ba3de4675fe86dccf82535524fee4e38dfbee616a45c9d642427550bc515d89aad1d17d7754a76a76315eb2a9ca48d44eeb91aa48a45061d829968aad725b495a4a4a0cc542d9302c95569216928d9584a9ecd242416dfb56127a02d6e510f7925e5b060db19658a8cc52f2d929db1e8ff889b341c8a5be794abfbb5556731fe8438fe6fe4e8978208fcfe3be775d67e4f3f1a552b6a37a470d6fb30b37ebb2daaed3a99732d8d4eb1efcf67dffc0eff6fbbe0dfcf44fc8d987a12eaf0ff71fcb715c66722faddfbde9e12fc5dd5a6b378ee3b8cdeea8b73b250296569794c8de0eb31281b63baadb7fa9bb53e3f7fd76233da23abe5390c7a6b7e751bb6a144f7b745d97a2cf39f51d556fa737a4bc977783d7c679eb8df6f9a4fe93b5544a979aa637e04db5eefefb7ebae77fddbdef1befa679ee4fbc55e7067a02fd6808e9a6e27df53c4ac45afbdb7529702cf3fef227c879babc7b7b370e6d6e94e1db79abd7469c573e5022f4067a446fa0442811fa092b737288384a5705e62bd2ae4d449a76dd2920704499020a070e1c38be69d64a31db60293803d66ab952a083bb4dac41d920881434d9b686b4d6f68006db7a7b4671828a0cac6812cab91de674b2b5d65aaecc1bb65d6badb5360a130858519c18c23761756736c16031288ab04b580e57311287c20caec3f8062c4ec39aa66932f832d630c6f85e285ca06df7defb2bde2850acd4fb7cad0605152a4d73502ca9b058827c390e8a246c390e8a1068b5878de3a000012c3f31c65eed19e5892ebe1460aa599109927db77d6147c08c83b825f13426330d9f71a70fae98c51b7c755fdd2ab3a64921834f8a2bc428cad85d936e0903493850d1441a504d50398a15e18402226630050848301426b5db74f29237ac049e9c1c9b63773afd6cb72bf084e5c48e73342b5a71f1afa6a26bb3b6046c99b88bf1662dc65889d9a36d6c8f60bc650dd3c6d8cbef4d2b75d73a9d536fe8e98d80a1d2b4d53925d41b7a8e89f8da338a911e76b767142348bbccd93457b0073a1747a49f74a852ba84370f4eab8afd309a79c08bd95391590bbc08839ced17a0a0072853ce188215e4ac2aaa670e11d3571d8aec0bba62387266530e546c0c853dad4ad35f8d61dda483d1bef3035e0d71c80656a345a3a5abebad578fa2f5c9cfdaf8d558be8803f1e2761868b61cc41951c6eb41b7cc6bbb38df318b5c8831e24bd3ae1adb9ef5edcdf698bdd9b6ed3e782f18715eef45b4777929d4cfba23d2aa5d221d80e742bf6d5b923761b9c6b40f07628b88df82b7e520b2b0e6b72d48bfa37ad3eec0706c7b1788e5b7ed9d38af6bef02a25a49bbfd1743aba65ba2ad15caeb41374be5adc4798390b26794209c7649b3eb73ec192508239baabe1ac3746e166325fa7ad1f589756792758968775e7edbd2a973ef9dfa55872ca5364c5149c32217a24a7c5dedbabdc18fd91b7ca3252ada402c45f1ad1c7a83278a296f569d192588a33a7a1e1545a325aa8d96e6873c47ecbb6e917ed2677ad7150d804111370822690b1120b936b400c61835e84114688c41c41d52c40c88262db27852021d215c2145071fa6289145cd2e79b6fd2cc56dcbd8b6393f2c518318c221064f9e4cb9028228946ed3ae9508110c71240442cc9e396705e2071f7ae06187223ae44004871b908e6c18329b3d73f6cc1a26d14e9a37f53c3cb326cd9a346bd2ac49b326cd24984b74892ad4252a6b92f884583475ec8cd93077b67943444f9853077da5d9f49a2e695c28116eca61a3aa6b604b255691b5c3f229fed325dddfcb76bd546deddcdc79b976ed74eeb45cd3a50ca368775e748e4b4f7be3a2bd9651b437f8a2a87996c70e609293cc9f3c303a034ad40f43e0010969cf23058924594841daefb03b2840c1e40727302306559220ed57288115337c4006f9021b7c700111c608c1ec05a47d0c2aa0c119528a20ed25607768ac10821355d46008117a40da57e08815159c41daac796acce67c2110cb6f1a77acb530b46979ad6d19654bf9423ab2c5012e6f71d1d3de0c6d0e54c024b3bdb13bebf20739c9965b72823e549b132d5e69dcddfed3699ff9d2c6570f1d7bee926e4e03bfc37eb16d2dc8a36ae316af812cd01455a1628b5564198d9652d758ac8dc56205a8b8a8642b98692335ea944333030c000000a315002030181289c482c12808f33051ec0314001099a646604a17684910c430c818830c410610026444a4044848d200f446d6476df737a1052b1164afef77a6f9565b4d014e2a0223e2acbf80d5f4f00fdc9b516cdbe55f912ceffc2e6ff6d5b1aac02f7a8c19ff59737ad3fcd837f59c1bb4e2582112daa9ae75e74d1190d9b0f6f2500a627e5801d07048019b7f01f3001d7cbbb72e498c1eb4fa97407353f8a4b4b19d788155ca8f950e73bec02223f0ca2e703d10682124ecb13abe6ce45b20072ba53ca084966c9128c06d60ad5780321d320cbd647ecca36a02d5d3d3f7c34aa381d136a1907cd5f379665b13f7d60de5e32698c22659d38d0e3dcadf8c58967b46cbaa5c2d952a6f5b17842cf4d0325d0fc0f41987d9a2defc871d511df3e26ef2d072678161d900bc1d22a404b44a648c8274f18d5320041ebef76315986b361e92f77e4f5e521d8ee30e6d9adca9e3deef1147b6b176dde2fe44f8303242d94b0306cd8352b0846c51093232c661328aae354f8b86e3b25c2eb02a70730951960f14fb60291cbd49d78eaa3d9a8a707336e555863588aeacc7ceac877506412e35591c5b1d0b6d36b090b42cf0104829abde60d499bc585a834a596f0672ddfbd16e030d932533be92609e0f50d0e2b65b2687dfcd8183f7b0811a2c3efea3c13d5d1ecf3296bb71eeb01edf652061be5ac80ad498f4a1710e2dd727754d7ec48b1e3d83dfe099d076b370f6b1f91fdb0f4b689c4058a918ef0f995d82f543f999d0e361c711b597bb26e375bbb962c4e38cd33f200bcaa306ff031831415316b7ad9495f3514f084e4d468f479c013b87b457a05a2fc3a6a668964795a640c58144a77375b0a6875d8d92af19bc1ec5b2122a23193a0fd601bb01d477cd07620d98e1a99daf42d8013615e16e697272fb25c36c8a006b3b8a2f2d06f02912625d0b8b15d1c0c5f33bbdfa15182d992371039a9d967686ff212ae677d8252f1e7cd3f8863a7d8fcf4fa7781bbd5d51c2684335d997c03bb3c88dab53cc664ef1c30753f846e7b51ee7bb1d6248deacd48ac544735e7a11450f1c5d2201f9077ad686f2cb9ce0aaab05fcff8c73ec61b777b0f90e35ebaf7d73c544ae63bbc98dc505e69158184987129f47c0e7137b862d7727cf085e0eb1bc008c9cc13e52bebb569688d6d2478a199506d7bed6a8177e91f1d521fc7f4f39c1adebc2c6905eddc18f6304081af6f7aae44754c4495497a8ca9c330eab4cdc9555260ce3573c257332f74d03f16821bf466ac0812ff422a501d2ddb6ba5ddfa1b318d735285e6048409ac6fabd37fd80749675e3ae652fe84eb5ff004b1c5b80b2f67fa9f8f6de7d20195de413da3d8d44419df7ee5778cca46d19edb2ead0979ac4fde5fc471b251c4096fadd47f49219814bce3f806f697b20998af87da1d745c96d79e27552cc890f040ca7d86fe29a78de18ca5d6cdc8fa0d79eb2d0a519831c37cde3f12883f28c2eb9dd731496b31d8409ef00625342df609c2b650719640369b85fdb2b836eac75e78ffe16dbfb7f62be0e2c20379561fe9e550c75e64fae80a28bf758b090bc58ce4e8753c4e4381579807f3bafb85a7b418c456c73a75786183b685ed910d221bf96a74df49b12c9db190b9aaf8c29d1a59032be8c08237ef89fc7f48205c838b1786fbce7ea42cbfb9b92696ab117317db3709abb6ea0af47251817f3e05830ffe6013538e9ff397254d67a298fcfcf0d919290bd816074e44ce3074f8ea6134c7a4ba9cf5319a025c241a0eb4c89bc4c8dc30ed0e312fa8cfcb45e7593dd1a00636349d41b793f2552273227cb4adf1ce87c5ba2040a9cc63a92a80406988fa397d4b4115018bf55da848a18cf3837c013ca5dbc95fc29f0c5909844af5b0d49e4c538f55117dacb78297dd785a4d2e57cffbe9b6ce4e7241e5118159f5e9f65bfbb80557d1895e688a5504dcd08ba93701abc54595f82b4ceeab79815fa9baf120a421359b6b70c4df8248fb54c365476cf3d553918d07dad3e8c2d85a6fd6cc74101059809b3764654a7a2f392d2d3b4f491d2fef4c919231536688b9b9120e9cc49b46810f80185199dd1303a56eca1b9ab5b88d4dfb072dfa18925e81cb4a0293c07c67e06c1820833eabd10813da11820f9cbfeedfae8f946fd752ee6aff8d0e8757da59c1368e6528ced348de57d9cbbc057b2c7d22bd79cc8d0e116a76cac0febd63b246af91a470d3e96ddff3488b8a2cfb32b803e1815edd8bc3f12ca207a0d306424948be9c48935cd6b4ae67471acf3f3c49ca2ae5bb1976e1d33e7da42101d45bebc31cc8f10f4ad4f5815ed1b706d2f984c53567078cc8d544a6791e7b49cec3720792e1fe56faf5a2066655155a27d9f3eff76995118cc6a369548eab4c04f47ea48e505b096c221012aa13d33c5be540df53f8f5e794799dc164c395ee3d3dcffafc486285353f3165150bb85816b9a4558ca4e12e9a82c4b6d9e1736814ffb85f1563d9a5ba14bb502e37a7eb2f18a734a41f4b1093e49826b91072f04ba64ace511ea8748c2ea6e0d9a6e0dea0aa5a6ae10ef7173b8e4823118fc5ac74bb1462b509ec2858ccb825bb01b70465e76a151076bb7d1067b9c18be7bdc1ba9990ebca1d92ca06ec03214a7552731d4c61c75b7b6751089b5219dcd5b6f97b6a65fd370a45db3d8a318deb61389561381797837f061ba0c963aca4b2c09e382c6f7f390d112bc98b60022953b0b97ce4f04de6ce3a674de960dfb3ac09a9573338a157337e99d179776d7ed8b92a8be241c58bbf93bc843d5e4dc8fbde2ce011ce0a277e14d520cd224ff8656411be94a304efa9cf724f355b6a155a1055652968e15315c007e00aec7a91f8ba94dac9b25c573a6681dba39213af4e91240d3d6e029e7478181251ae1c12c5b9e6d23aa76b8203daad045c7d3bd1076742858f92cd3ce620d9da920f5565560e17f7b54904b36fec5bf1208b384c98469401332acdfbc716ce0d5254a6a8e0075cd4fecca520f1aff9c0a1955c60d4495116e8c70f67abac08c551c2722c54f75e0489c233c1490d24d03403b1c82370a48fa9f742400c713cee3e531ad1bd41c39005210727f6216daca6a40e2152aa6a6e7e63d1ffbd050b6c16612a1d1061023f0505248db6efde795387cc7a095c8060542ff80fa68399f1d61886a9b88666f3f8fcd9ecc04814bde76c737f397d7e31961409ce474a3e4289e0102d0262f9213a9d67b7fb92f50d4b21c0335c4224643608cbd2a44ba304c6667d2658298c3832c8e65cc39a316e6bc609ac6470413b1194a446b6dc95d1ee13fc7ab99028a9e06ec530d1974ee5241dc2cb02b0cfb845749e5a02d2357c083308c488733110b57c382648202194bf2f6ec2c6355a31b708970347efc4368c473674fd3b75a4f0db2d7f38e8a700078f3806f95b452f1c7f78692110f1ed2a6a46120f1d86f931a239c638b008e826b8ec0078dadb93accbb57403eb901f469538a535e181b5815e9564ba5b530a05485917320c20818f8c189435da0420f1967716b414a767671dc9328ff179a11c2b961efa2db120362e7adfc2db399ead960bdac186ca1ae636ed587a1b2b32e2b7d89a4e8acd906161874bfcc384cc8117455b48543e60c25672d297b46b8797cfdcab1defff7f23bf88cc4b90062d16402b03837330a33e488c407608c89a9141756a7afde729fe676d88ef5212b875a899e46d64d3116cec1ee9fa332c49dc2aff86a03fb871542eb1b00545d74d5e08656eb6832456bacf3a6df68b85eab4b2ab33d637eabd94e41079dcb80f1c59654e53723fd133b23aa7077b09de0151d5bfbe99da88d2c528720a9fe989fb1d0098c6c479637a93021937036e4f3d3547f9b2680d5c63e9007c2c7206d6cf21ef596c62b92f7a0f94c2bef5b0120c90e5ab597ff58d4b186f99cd9a96a684e629add043fea0fc748fca8d2bc21bad4458c8d651c405544bac2c75c9606b88d7a4b081fd60ea474c4f6f4f3bdf999f58244ae78edc987fb9991f3b6b4ab69cee534e811cf95ab4fb00240fc491aa4af03c197195224fa102ce4bc0307d2521e418e3ca4ae2c19faa096825c2cd6d2c6e4ed57c81243d6e7731fe718b3481760b6aaff58e12d110f0425880c78b97c9e288429d2166012475078305ef8109a19186d742dad22c2b786bea5e86b770bcc69a19116987e08991cac97af7dc04fe2c9f2b8d344083c17ed5e28177052dc0321e18665ae4216747911efa6a128bf8bb408530daad3854eb003b8b2e8fcaf9312723da27d0c5503849efa441213bacfb998fedd14dcc6d8e0c1194d0db65228e44ff7a48371a8f17d635474605a00781495007243ac942db25803920d89acfe15067d01c18e8112cf2f7d18cb8024a99fb60ec0164c8fc3b70bf84a1192262b57dda3bf011cc7c416bafea1dc015c6553048164bfe2200e3d5f8d62741a38ee126544e3960b914c818ee538471ad816ca382c44fb5aeaae0655c02f6b702f7416b323d54aafdc0b5a6d82fefa1f9d8e2de1093e66932b82a4b64c87843a4134e4c8e6c7e968704bdcdbd2722da68454ab0b2aeb8b43e66bbea83c55677e6a5886de6036008898e3e014fe01ea4e523cd89088c1510a094fdaba1458d7362f6cf00bab3156885b1dd467cbf4280b57b9f06e1f30a3e0830d69f827e0d1b8fa370a06228e44923fca7d4453a0a9a942d43dee1c56eb5e828691795614040cb3e67e618359953e2e8e6eb9d0d46004a7cf353a1cc48ca7c62c7a8e278205498034204969e3d45f7b49fdaf137b9060ae626b5e52e89c0bf572c3f81f848be25ea206d77cf85cabb524fb506aa65300a5130b42286e1bde7a6837e66f0de9d6c509dbf6ea2e9a2c01c65fd88cd2aa1953fded9858a352370d8bcd88d5faa30a59fdfd86de911edb40c0c157d287c9514e858d28fd80a337d324f29aaa5dac23a076ecaa70d2cf5cb7fcfda2f6fa0c96c90aee3c8b13eea53bf409b0f338fcf83b84c6ca9b61088ee24a906a071716894b041cd25059a58b95e28c987a1a002be2a57e6f8ddf423d89053e0eadc5d103436f9e0b85b26800d0af843f5e15014883be0f9ea83df1c032cfaa539b25f84ae94b2a1875cc7d62b931f7fb78785a095722ce63589e29950ecca91b122cc3ee0f770bd92c7eb6ccd03b31ee548b85d660bd8fcbdec2dab271f66aaa8b5d0f756baca32e1619d6c73a12ef5699fb43a806435e0d8f371f4184fb89184e0303e77b14dcd7de7fe3cc88bb35b5ab3f73bba838d61b0c40a949a30f7780e1674a3f35830c95ca1ef4d8040e17d8f7cf14f6c51142dcb573d15007d09615b56fff8c8c471457ae85c6ed27eeb77d10ccbb158d6c87c9a7c9fe10207253db506b5913596aa093ccb816ca8585eaec8d96effdb4bf03eec3a9ab90eb2cd2e77d104ada1538189c77178f6d42540622139b0c11a305431b4af142672ac5ed701fa85581c956467033b0aad5b7e04c94ff45fe95370427cdccd2222e3a34347d380f2fda213565f73356eba905f57fdf51f5b00d88e605fff4c5c769e92bd027aad53dab4f9ca3f234049542b1898554294fe0a24dcf82dc8fee33ef34b5a21fe7d102e301ff2daa13e9420701a300fa68c39b541ad67cb1b06661222f47d066289e987dd017f2913990f976b4ba7735ceeb8652b90d24f3517599238e1bf987f8a286298429c6a588bd53ad25e23ba8c5fceba2d28044181236a4e497a41e7b08c6f7ead8e79d4bf05f75d41fa86b60d9632f7cecc95cb7ed97e9b655359edf5dad721d30233a076f1287cb7232445c099b77fbe8fee0d68220baac1a0c009e04f4eca7eeea7996e1cc84a51ff700c723e8936d817e4907227fe840730f783813b03032d9a2c7ba07227007e21e72c6617369f0efc26b2dcf9878db2df5e972721f1d42a1c91468a471016142e57685171c62ba7f6f4a27f9dd07b6605de876eb45c8ce04c7b1340e924430d788a26b16eb53968b2b3b8997c144afc5df3481b7409b8b6dfbe275790622e8705d2ea76d7f4dc05a5532c981048f40f1416d2d9eef5209761847bc153f6d297131a2be0ff90c8e47d1bea261c8ef6513d0eb7c9dd35f01f407908837901980018e7d5fd1a40502f1c1fff97f08790569e6b5483e977d19fab98589620507227a4245ba4c00eb870721dcd173c9e7a86838aabac46042d299ecdc890705002d0f6c7d08d9235bc91d90479b6cd300af1ca114e9488ba066e74225c407540217b3524655373b716a51ad289da1179ee02d01f043b9ff780f052c02b74b28969236e60bf18190c4753f76f8938ee54db7df2f8983c1a8ea2a8b5893aafdc593fb9ffe1100b2ffe77a42555b208a552523b761c400b4f96973843811987336e38352e44a95ed9631abfe77265819d8008b82cc4cdef8c0657f1f38d12361bbb230252a1322c121a534c8422885fc4e23e0eb8468ee9d2764815507bd683cf6ca766de8fc0507c1f4d7c3ad25b790e7d58b37d78d45a4b3e0d1744599b5f434542273e8c4c0aa7b1557966cb2aff3ab529abf54410639cb1159b411c46688ccd2ce87d498f798e105d973cae5bf72f1986135443026a510bbdff1bce41d9f579b1ab00d33a880eebf639095c06814a7878182047283d1da7d6da628ac4ede253aa3b02f2ee41c141f06dc81ff7c0ee222768094a7f819d4b07903ec531578d4bc363a40c62f2ec5b01782a8b07ac82b56e2090622a17bc2f87b17f60020c20dc13c91b6f0c45fd8f0ac0d156848d6d7063cedcf923f45bf20004d048ac2ea5520fa541d8d2620b98cbe2eac4c53f4e7f79f073a38202823ac1d4723f9316d06909acf84567de18719f70fa05225ee43ad4c454df7edb4fa6bf369a5b663f5a401631d7908464483ef581addac70a5e2cff096a8a6c5f883e426005c725afdda85557007bbdb59686125f4ee3720a6958ee670c9c9ea46e21cec2f52481c7c1287a1980dd60e1502b09d93db66f46b5f2a564d0e696954c6cfe247be125dfdfa3d200791f6809fd5f64bbfacfa699b420f2178f6862cbbb5f8159e1699cb8d16754888b6b371bdfa9709d2f090073991bf4364f2787ca9f71e1a8f5bfde015c9d3b80b4f57c67130ecb8d503db9a6f5dd21e5ce74cf1482bc02e632d52285c2394bfc7ad1e08201d278f22906f86797c181d52bf8293ae89e214e530434bf3d5b753623d9efbd3e045280d3820041143049e3df1e830829d9cef8fa29a2730b513dc898735296d21cfb604f1dbd6777799b3565be0bb47ac4b750ebc94de88db4fc81c48bba712ebbdd521b488bc51cc0c68a81b234aac8b28dc47719912d4523020ab21eabeee0add41395250c6cbcd956cc2eb3280cb2a926037ec3ac416cfb37bbc0383d1544e7aad30bcf79eb215611571b71c1acfa6c9ae2aa85c140e0057627dccf67b196ca1a943fca2770501ac60f89380663f28b13ed67ed9755a00604b5d5b6a593a0b0d792ab15e05f71cbc18ecf8037f3b020a5a7fc99679dcb7ee77d1417627f3c6de7d9fdcf7e9ddf92fa5716a8a80b69e97817ca1e0e7623d0d9cfb7a2d07a29a0ab3003fb1adb3a84cea065b2895b266f2fc8ef27682d8f49c186d6a2140c6d6848ad82227e722a787bc137223ada6501c0f43715d457e29af9df81d5bcdee46d690145e152550293c979fef00018a93ab1b0a86389becc511681e7381daa13eacabe2394fdbd2ecec66ae337a652da684a565dd278a8325bc73b0ac4fc9be39d5df928464e1498178d032b2c384d98120f65a08d8195349c6aac37e3189fa2c0382d54bb304a6670c250cd08b65cf95d740cbd962f9dacf91af78c80c61d29257dc5661f386980fdec0c5c2345b8f9f653e95a414f00dfcb000b74a6a156a50bff69e7e29bc411a547ea172b7837db25c1c29c4e15d11ed9c7c7cc84becc46fa7cf819e3bc8577a39e9d883d2f241c446379005e703f9224a0f09d78e073a92dbad54b277ba82fa62e54c5b61116332e7619baef010fdbe31adae092813603293860e484e579052ea14ad37248b160a5a3594a74d6ba886a1f8c99e20fac6b750fce211751d3a4b662cd8305fd483acc837dbd50e271ec604189b4399a44fdb1d0863f23015f9de93a8cafcf3c036aca07de621efea195d72999cbe35581b6fab88eec1cbb869f9619b8bb24c2ea0bfc2602fb53df3e0b986b846e514f22196c7fabe238fbc2d6aa18478d3c41dfa99afb063d49c63d1c6fb6ec36c835078526885787a50ecc7f45d5854763a1636d9c6a0c1e2ea06c27fcf838bf44cf757492bff4296ec484a3b3ee3fb9f2a7ccec384359ed02b2cdc9c0d325f56617bfc516ed62d23b8104a7615feb81da5a43bd3f988af67281e408bbaa23b6b53c37855dbb0b67a69997b42dfa150847fa2d97fa7c392a6dedcd92a27ec3558409f6b11a18289c30e487d5aa4d5b1c09934f758a0350ba6a38cbc759e18bd093cecc0eb35f40ff74e7df7971bb63c59d70dad106e95bba980d314d5952436ddb11e831b9cd808b4d649ada437f47ad9f0a7afdbc89084cb6da97238852498efaae27efb6a0ac9746ee000d019a7d88e5a74388a522cb70d7cb91dc0116f2cf767f733d83fa51b242459e1d929f35964ec9039e4ef0f1c89953f0fe0a9ba0f7ce17a48df20bba3a0acf05a56fd3be095222fefb0802dbf56f7662b51d5fcf79b67d5c0af81065b358bddc5335bb578732dd65288828a0a01b6bc4c22728d8cfc603e80c72c0b11f74d5875de0b2da82b3c87b3464fc0cd0570e450c4dd81b4e9c47db19d68c274b36932c4dafe830b6169d2abcef19de35da980be906c5b06d0339f9570806ae7bc3eaab8136120c881c558532a4189a2b8e6bf65511a0ea0bc9cc54d147de01655946b21d5203e0ba475d692fb12f3ba664a57349a855dcf4b8a801f315c4c9ed831890e183fb7b2906c6dbd4b6e9846e809493973a9969e526314d5cd37a16b1b75902e8f16652f0d510827e62c3210159e1f3962277af54976c57dce633392b339be7b3a81c14f5f9aec48eb3e81dc9d5919b9ac3632b2d36f6646dd5179ebdfd5a82a79f5e5b5dec9d11181dcc6e806c1f06e1d288c7302e6600eae9e2843cc1c5502034d61d41d011ef6813d472cea93f1c7d22ca213bfe22c6ccec776452385a374b87541264760b1d901b8cd041b7ba633142c047a17ed151121c08d145ffa0022e15d2d08d2a94a49cba71a80692386464920b9d6fcc22de8c5a63fd5b4a3f9741cd250979e08baf56beb93e9d92601c966bac33ec8af239ec36cdf5dee00f9cef83e92c4b21d5d077f8b91c463bd623663240f38d9af6690a80921ea8ada7b4f3d98976d35688171ae5f9281fbc5c2159bc97b5b72d2ad61a0147b408cd82779f801cd586f52f10ba2b19a32982ba0027f3ba6e6e403b9d46f6075f3013cedeb6a5e159260eaf458a35321800021212b7703e75a25dd4530ae4a5173cc28cc6853fdd645d9f039d8092dd02a10dbf8ccea4da91ed19b0284a696daf1ee7d0402e73a027f150a4e50f22df50fe789d25743a03ba38c088b59fb9ed06d68c3a789165040a0fc5b7a3f5cf005dceb08005b330657910ca4d795a0b19e2ed6012364ad9290dd9b347b2118a7cb1001a697fe87ac5397b9efa15956834c87d3aa3cfa1f73c03578075ea55d232d74c1a77e5a996ce50dc0bca4fa6b174b196a2a85f064e367b4efc2366837047c731de9c73d0de657e32e0625b54c009d60c8f4876c3d23f17803c6f083f8b7aae37b3a4376d454be9cd018672bc7daf1fdd6a0c43b2bbedfcb6d3ccff56ab394d22b79a68e46e9674d46dbf1be260697c085d6cfd1f11240ba6eafd7fc9b96e494c2560d7133f6436ab378282f829f6f59cdce7e863f1fb149d72b8c8e46ba3166d9178a4ce69ab596d6109c159a40569cb27364e5bfa65ae74a85b39ac1109bb2442fd0c782a5f3b5d8fd36e006981fd8da13ac4d4270331718aa98f8574e723c04c4172917863394e2da52f833f113385f6692f08cd18eaf428ed2191d2f806cc562cf86db53b25bc2edb5f7033f8ec24ceabcadaa5ed4203786fe2d177a4387dfbbcbcb4637eadaf5c3b4d9592a16c62b419e4316c89279fb3965e530a17fc1c6e0104017ce6cb20aed9194a8c0bca781dbc6217ee28551ea3f22dd06aa473670aadf34e4b0131a13b69e33b4b959a57c23784dadfaaf7756aac0ccaa362eaed21a4bfe9788a742a2f41925045bdbce36c6d9ecd45da87e52ba77d9c31b68c674d850f758421d9a5000308ff9992b37df891926045b4bd154f06887062cb60dae2bbfb9069f34a41aee75c1008fb49c5a23b7a639713c57b6e645a6be0c27bd6fd0d2533325bcf9bbc640439c02fa1f7e42545cccdc73eb3b648404bc775965bdc00b81883485ee341270b50dcacc6c98b600a7a3ead55d5bd368ced6365140fff9600eebebec3df0b8c1efbd3281cb91c925e948f7cff0e16b18f3322fa01a19a5d00619a0be43ff413b6a20cfed95fff6b63ab95f3955643718cbff41d1de5835a1bca8e581666aeb4aff216a81a4d509931104e9693b7a07fdfcf923edfdb7ed93ca53c8fa0165c4581155f1738d492f718f59e46c73bf47b87c7917102d13e3a3d07ffedcc68ef4eb25fa3091904e697bac949f2dc6eb2a1d367a264c242607a140ffc1dcfcc476f604cbd837d5b0a8d5d684dfe6e562651ac1000abe0defd10300078ed4e48ea7ad8a4102fbd27c4a2e002f19fb8b9bd919b08df8f0413eadc00e75b7f5771b834cfe06131355fc77794aaad51f56d266c52b0d96334d19748b1969a8297a6dcc0a01af1257fd34d93c8c1a8061ac9e97b4019a91b57ec902e83b9cb2b35fdd606472fdd51ce5cf3b0107adc18dbd37262215a616ad36b28a04b71f2141454a9ba656cddccb345d35934b6bb2049263ea770783a906952b1ecfd05a7cbd0f997dd33662b176fe7ca861bbf1a7b3bca2ffbf820cc55bdd95a9a0ed700746787673bd6b4342dddcf164e5a23e960c42589d7a531ebc3a3a07b6d7b649a8d99343fa2b77255a8b56fbc2f65f5bb183962c60a68cf4ecbcf67f0a7acbeee730809c59f87c98f5d20587c469e2d3b8328f8ff134c39c523c5db7e43119bf2524928a0c4ac02c4e416cd548b35fde426701980fe27d3f3ef95baf041daa93859e1b108e1dd936bd3bd59ee9dd12b0a60ef84b8b8adb2bf37cf02ce4b33fe8cf229d312c848d318f378c29fad68eb872614c857b5ef9876fe4d38d1df65c22cc50672b3a9bdb6a704eddc4d0e21fad1d10627fc01c118cf12c3e0dcf0be63aa79ec8cf25af06bde68655e142bdd87f362fb83ae7e54eb39ada410fc80b9a5e78b9ba97ad4b9dabdd293170627eaa45f9730f491c90869956563b3e42c1dcb00ef8a19895cc63890b4e9c6ce1c5bfb2ba1a232694a7bd617933d62e2c97e55ba4649ef7b5c4afc75bd933045b964acdb0a6a8672a7cb4121d8f5ed4ca3f655b7c28897e020293759ab9bab6cef4f1205944d3a325e9f3ef0054e87b1aad6dd918a4cdaebd1d49773a0798237a1b0347f93f89b76adf30713815cfd0e92ebe149e6f8a0901046e7d249fe126294d72c2d6c59e84a0639cfc5085f7efe80abb7bcad48ac288aec809437c73909c92be1447b9608b45be64de2c76946b84edddf6afece00a4b1f71c0bbb6484826ef1376839c851518e4a5258fcf5248b93c8816154e31aef7d248b6066daaf625711e6f753489ca844292acd4c526369f58c465a95a4201214be45de83cbf47d9b63e62b7b8373b040bbd858873b779f73e0653227a09a8b7937faf70a5795a388fce05324be4f9447801cdeff9356198cfe883de8ddaae27924efba02cd88a4546322cc32fbdb1e917d794a8993660142e79190f7de13140af57757776dc72345ef91bf204900967a189bf75e632edf4cd0463b215cdc478efc5c7644458b068e000f3bb7fcadac3bce3ac808bdd63f44874b179fea69da2c4a11cd6bc13e201120f3dec32c7089bf143db7a68ecf7a396c805f2f14a42be46c7721bada5782091905b20cfd3e24147a58cc0ac85b63073c847a70310c1209c9e47a306e124c5fcf01bf193a754b44f4a3fe7f7690dcdc002515cd730283907115f711e764d4fa0da80ee1ac53477eed6fce5e57890c01745b4a6b8fad9bfcc6ffb75c2a6196317158059eb1ed6aa31adbe5bf4e4e761f5e0110f86b76e5a4ebf2a59329053590921fdc42530f293757a307fe01cccc1f8c33d45c72fbc22258ef17192a52520b48709dc0983e578945b2cbf70f691705b4c64f6d9c1e93b61a8c4061106bd0f241a2c51347f3e0dd25007c252cbce3295a4f74c7f8dabfd6f5829840d74d81a213c1292f3cb1854895a5c266988561a17619dbae6c8f705e121da5a0c05154d6df2d5dd7cf4e62599a8cf7c1fa10cb243fb2c7b797b5bb88549cab23228321b8a189da5ef0c292549939fff6daeb20561a9c37aa0047849423a3b492a87eb7a2162dc06cf721cece0588c8629c30bbf08822f092095c33141bf29c4992622c62df13196030aca5366a1fdf1905e1f1b63e23ae10f9b86aaaa746d4d6cc9d651b2048dfa47f635744b5ef280c3fee73ffe36ddacbf052d34b5647760dd959661577e3bd512678827e9f9fbb5f8892b0f169736fd548a8bad46e50bc02ed95ab0dbef8e4cc414c85f15ea29f01bbddb5a8a7d6b8b043e6e62ff006911400a5e82383eb91782c8aaa47db268db2120f4166cdee6f20b6cdc7fb6dbfbb759bcb1e1958bc6e7e54ec87fd4542e025cc1c09afec81855ceb585ad784f361b9e795f6eac9c9ded9ceb4f3e4e6c52a32a430b8b16cc366a8a305d67ccc1f4e40e5c5152ea74bf02d9d4d1a800d819c74985a73b588b78bac078ddbc22172fa32ff3126b29c13c035779aac24591903955d89150be03248143fc43c39952b98337dcb050eddf0d83c2df4125dbf3e081c1d895eba63ab77914a352ace3c64d0b609b39b96c295d16954c552cd9cb4b87fba338b3d520f607a6c6355b80806e5b394a14bd7bb9682461eb1cdaf28a53087969a41eadf362bb754a3def26099299266191d4f163d7b8faaf389163d848c1ad7019a2311cbd9ff4718f25d61aaea11722d1ca6323a58cd8567be490112a67f2f76224ec487704fb3f46d0bb21625531c73f8ce8623254677c88127110fba6a1f03bba3543ce8c4b00d553cd6b9baf2f65ddaea7d89ae14038807f69221c9cbe238c4b4a0826297727cfd46c9a0e4be5149fcf6fb3200e2e3843f7a70120931d3174825966ed11af96b6e204f9e882cc4802195d3f80c4119030bc55291b7de751e266af64cfd05a23a7e404135723ed5e369e96cf6db5a601055f01362947f62dd9e94af62f1ea462549f82cff43625e793ffa04abe84b1f0a023ae87ef3ccabda1b190dd43d1927de4811b19726f5ac4efe197b4e71d0faad338ae492b2fd838b46562a8319aab8048116bdd159477bdc7ea77219d9d8dddad7bd3ab694917a2fe0f15e72a2f0944d63ec25ff0ac30b8aafad2c3485a2fb584f924129be0381e3ae2745ce4247bbd1a8cce8841ad5995f4be12d8ad5caf31d97126a9bb37810128ebeb8b224f84f878b51d6b2ade2f39726b1b9f792dbf421523b838a4c3992421aeed09b693f85d0f0355da9542f43014cf9f750e4d7aadc9cdbca505c5404a41ee350e563cc1875d98cc19f794e21a32694855cb203108bf7e8ca05246365c45ede7e1d5292019d4d36e8f05ddfa7fee95a32451e86ce3ce85115945b40fe0a36c06c721a8588a4e8bd673441c7265ce9ccd07405f95650a4a338977896ea37db10b17e39af6da767c6313ac231946f1359f744feffc4d8aaff9d37acad7243d7be7df7fa91e5ef3ffbfb4672b53a2c7d3fc493d5f377bda63036033c2396279dd2ff61e5f0b738393ed839156e4abf137d25c5df9e1b40f1185be988b53672cbe7b8f787e82baf5ce06dd73ce6574ca4fc01fef2cf1559e58395d99ce361074fd5cd1f37b54346a4a15a795ec2bb9bcb171b866772ed3964640a01f74cbf741e861cb19d60d1a8e014e6aeba6713d997ec5f1d9677d47f496e0d6aa970e76ccae380d839ed8a04bf59426a0488f042d8a4253780dd9faa1f9eff1b5415855584031ab05a332f053f0b975ac28ae6bbffbcfc51f3a6770ab4c881e713bf7addde8bff20ff0deeb22ca4d46be397bea1aef450eb6719d64dd64f58807b72e019a7544181e958e8c4e0e91bfa24b4499a8992c836fefbd2f5595abc191ea6509ca96c322984e5ee68d545fc5c96e1394c2b3a3fc4ffab3b492cf082e211480f65272b5e18d001407e7ed895e7133ccc767c8b14068042377cc6529c56c2c2d22d048ae57746e30c56f7513c6767a159600d7cab35a58bb439cd6aad8a380d51738982a2cecbc0a283152503a02c1168369345ca1296246dedad6a5864dd378b048994559e9586f39633be2ce55c3e5bb551138a387e253d3ee660bc01bc234029417614ffb0df9679436ddb994d6b4e0b4f69f27c8dc436278d2494c346d19d1983ddcee7f730243f1bcc2916f75fd29d0806fcbd99dde88f4d6dc48c2ed8b7a4473a9c3446677653e7ff5e37ff785f70c9cef0ade332d06d37bbdfdfa886388d59df8b3f416b4426f12e7233a7a1bfae6277cae9d765c90eb26f99e484e8b16a6775458db1ad51f18ba9c059d01f7aa3a76e34c12525d1bb0b1304320e451412975fab529e8ad3e950800e2ad7ff8d4793274c215f5a92ed840d9f3b33c3d7dd6ed4024dae3a8b397441a93df8a8a2746d732dba8e6887e3a7958cdb56c6859e63124ba7e61dc18af4cb099130e48ff0ec9bc179af9c8696fd41bceedae111fd0b28e262452a0ca7f2c62d27c764bf5624dbd2aa41d62be05a578fa18a3e5852529489c597667644c1375811d0ff7c99420bbd14a87c7a832564b7473ca7e9b4690db16f134a45a6db65fc00470885f54377591996efd61f50083cc06545c8e53ba520b2d48274408cedc33b201ec0696803b2813a6281258a7770dc410e3250d8341a89f456884ac5063dea8ff11e4b2bcaa6cab43109aad6e56faa3595e522b32c3fdb0e6b8531d6eb6a02081095aef21f87f9db2a79f9b92e10e49aa97a78ee3e4c5b881d84bb3856b0970c2868cd73a0dfe73fd821333ecb380b9b0dfb80acad822c35b14d8e98cba79ef0f3b9b365eee701ea01cbbadfc40e49c716686866612f781484a5ef7f204a69cda0045cc8ca2010dcc0cb50d0e0c19750b4e425dc3292a8581011df6631b3fa8096f26d1960ad08a3de505b55cd219f4d4a6d9b303b34635c4f8d81e570e2a7ca80762664ae5384fd3c32ca8fea505010d099f269fe18654dc952899366a9c536c211ba2519ef06a579fdc17a997f889aafbf5bb0122d2a54c0d523fd1989cbd3925cd69063f4c6cd6a453013415ea5104f6662fad9061176d3b45a846c7ce1eb665b614cc2faae07eb8463dab9fe1bcfe8c682bde64d3bfef3cd318c3f1c44687d7afad26a31f98cd5fc17d8f5259cf84193bffc716c187b35d11f78b41c61340ec619bc8a07d58fda5c0efbfa4aed17f72408fb879b2cc4cbff1d1fdaca53e02b99f0e2c03c660727eb18c9deca72ebc621201054ce055235fd995cee916a1fcfdc26012c00c6d75e62a0a855efd1800627faa1980021ffef915ffc14fdaf05caa050196c0e27f95592bf1ced55b1f0418b6d95c8d0820f2a497fbc7e58d3079f781471e8195b74cac16fb8cebbfff7cefde53cebced81db6833cc61f7efaa9e1b745c3f924c091ce1f46db44ebb6564f77f495da3eb0823aae0821b6920e9801bb0e0fabf4683089976f4602bc83551a3ef2dbdeaeaafce889e5b839e95f646fceaf4274fbc9f0102ad95f586b0eafb2b579ccf207ebe15e89de46afab32eaecf387ec60af396846af9b73eaecf387edc0a7947ae1afed91bd33366d0d65921eec58669a0980a934f081f8cd6b142ee056a24de89af16fff2c2fd94e4b356ca7b62d5e5bf2c819e857cb6d2aceabf39313d637a6fa559d97f19223d9ff6de2aabc07568fc468a119c7842f8385a8f35b90834dc90b4b3071a7ec3fdc965fe1fc1375645052558fe31429f5411d444ab8301b7ff7a9bff3f88544c803ed41c4470fdefd6aa3f80aac5c2456bcd2082dbff746dfdfba73a36045ad60e2238fd6f6bd4dfa0fef0c5d7ea32f8e1f63f5d5bff04d5c30ef1239b70fba77c79704d9b80484c021cf88053b0c011ff5e90d5d900108d0f811becf68fe340c9903f43870d2870aa3f8bb182945589841162524d0a47fa7b8e062cf2ed9d008938755ac002dffa5b4de64c3d0d0e60de00ae61eb769fb4379d985728c889dc409af17b6620005cdee7dc50a424b0cd08ee0df43492b88692bb08fac0411b14ceed20258fade8449e7407a5037a108716273d0e6a00347186c4198d213d4804135bba836e4399dbed75774d8ba52d4e41defa1e1088f3a222a2aa7b901307c325d0f7bf9073dda975e3478e2a2720f7befb93fc2b167960bfbe60815f222db7cb526676f9d4ddc261648bd3cfe351883d3e3e7ae6829c44d98af529c45e1f5f7d73019c44d9cdf214629e1d9faab9e04ca2acf3b237c1a1641fbab643115c744e802007573d71106c018e90aabdb4affa6440ce7ba8aebeed552495680f235539585b53106774952c64e786edbecef730383e4a40069c6bed9dfb15b27323a3b6f3388ac81c9ca9e4d92cf3fc958191e251c5391b550d861bfec6f8da324570f0af0137f66cc92ce483f18b452cefd8e28e7c7ab0cd63c5370604beb3dd1af208ca32d3035c4532de916c6e4cf6796571322222b6831453359cd82f6fe655a6901f3ad8717177502dd0c33d9aac042193d84300c1010e2e1d2a0a0fa221d09d091a803106101ce0e0d2a1a2f0201a02dd1998643065903918222e0fa67ea24a2881c2c9996433603463b90d9efccc3475e58fe3e085464845844ec6aa5478a6f7a98fe693c51b5b03c5d07f79e302e0a5f70ff4b525f789843f210fe2c5aafff43f417dd7fdd808f5c5a1f9f630b3b3b555be337ecf7c265e31e2f6d650dc06ffca797268df934a9e6b4d7911cb35622a2388fa8c649729f81167accdb09d797cbb8280cd21a72b53d56b7a9799b37328191290d53240fcc61154c388d0413fba259ea640bdf36bda793f40a3bfc2bed59ea1b75e444f398c77173dd9d52b1d2ec13a9bbfb835a0404813964906c1301c32c375c8146e86739485203bee1c207cbfb89c96494bb9af6bded9947229cc442a4a692d658d095614cb30beba81fc11febb175e1cce29c0aa800ef4bf14483911c6e6cf6bebe6d42756904a115012b82c91c18b3745b26d380183b2060963fb8881e273d8bcad8a972d5c293dd37830b4fecce55eb7b100053a438b832bc61bad85a1a351488a5ef22e0d47d64214a4699b071a53e3c0d6e869cd08b4b3949f9ea7abe536a56b0d1a25474a6956e6ca0a7f4580047e8bfb7ccf5dd19653762f1e7593bffed11bd3c9365bed7e694ec0a9a5e7862f5dc83dcfbd7419ddf5e7f6a2526940a84cf119daa3ba64578c7156319ecbb73f0f23e795a154ef78824213f0a02710e9a46c37ff2dd84a40df616d265b974fa0ee089ecee4042efbeb612d7a9fdcef409d4aaed4d9733f17090b45193544a59bd60a8667b701a6b8ccd0d7c2b1293827dfa3ba725e8596d8d916b15767950035be6b5be8cdd0ab27638e76db52850df4d79ccd3d02c978c95257fb9b390c8e966e0a603a76bf3215200ff60531dffef92f40c3a8aaebe498d65a73d67996c354f4d7d25ed3438884179606fbaaa9746b455d80e1050316ff4dddd0434c29dd7a68492bb0aa965d7b262c93cd568f35fd87f2cc19e02d3178b334271003b305cdb6ecc4a0eb10dcaaecd0b28e2daa290d15b119e4ce35167f38723d290f28a973395130f7ce75c1719d409b8c7a43c623cf8adcc98e046d21fcffaca163724af94b68d83b195d15200a44a6aa26df3bb8434e9a5b0f293d0bf7cbe71e3a6ac50241f774210b6d1b5a44edaab42de121d8dbb454103653022182130f4049b26fbbadae5d7ceee2c36c98a08936123e5582c012089184cb4be726b1490984dc5830f6a893d7bd290fcf64adac4513f4ab23dafe2981b0f0fbbc5ca5744d06bd2e6d92723de18c199aaca0d9619d8efd2a363794cde142a2aa720596a5fdf781d2c20f251a4f06bfa009341ee30f7e83c053c3da819c4d7e76cc77b55be741ccc248e3a532b9ddf77c6a9d9ed0e7e6b701e8cb4d3aaef4b11340fd51ddfc4e478d0e1305fa479c955a3b3182bcc9f5c906c22dbb939dc378d295ffa92fafd73c40aebbaa454bb0fcefaebe1c71c9ddb65862e067ae8bde8a1b498c114050da1dbe007e14e5c591e81b17ab08ce03f09e9e87a94c3420b3c94e4dbe491386290f99bc6fcce855e45fb0ae05f5af022ae6ea552e688b91d95f6fd5c19f4eaf6e0e279517caf106d949ae759e289ffb3a71379761e2d2b50ed1008f034e4f0234be8dc0f1b9252148757e10e04736eb4c10dd1a69fdf7bf2bc75819a24ad1ccb4217058f4b2de13f29621d0fb2958b576a01d9cbae9fc36bcc3e4545d5714c8b8d17dafe868affd25b40d6b361e4d1c1f866b5053916b2a2fcd513a4e39f43302fbb3a417b386dae60fedff5ba28515c3810367347f81d9ab706bedce609472ace878cd0fd037e1c9296a3e182eecf063a64d1f17a1234ab5c9fd5634982d46ff7aaa0f30506f4ffbc7a4fc4939260251dee51f64dd3b89c8c48610e1ea70d9e27c3192474dacb7ca7cf4c6d03ba814e9855005c4614f6929692b2ee2101ac470ad5a6cfc56046d637b85acd1463dd542cb4cb511f1d2d0efd6f6a4aabe87dcfe2c88add73a7a9eb0818cd355c114e5b1c9456ca9ffb4e4fb4f3de2dbb44f19b9033c04856f56e8b77736f331a757b77fdcc7d51f7f28cf21698d193d5605b1d72125a6693ed546902422f3433c6ae49521d203e4bd9a6c89807a77812d659d6b30b895440c05df26f47f45a999190044ecff8c5660e04a02b8014ec4afa2bf6be71a03afaf4f111f81a07d2353e67362d8df54e3001efa798b263ac44a1dce3d00a60448a0b4b4a9a98b03c2b669dc41fbf95a316310381928c96f98ae2763fd36c5691343fd9b461b89befae60ed8a4f403d9b760ad5bd8ceb20326d4a899288044df883f3fb062c7653791ebc78e8b43886688755240f6abd24bb3e075b6401409cd8dcc031aa14ab31cfae32a9244a0c130bf85225e888a74c67d1ad11061ad08e92557e05d81540035ae9411e315589732b26b5f660a8bcac2b739c9456ea8216df980f5ac5541ecd610e1f3fcbbb2c9b3ce3a2adbd8fe336eee38d8258ea086b82201623061e5f4e505e6c21818a0f48e7af5f6ef3dce3a68401fa8ae1ff52a8e7e55b8376d7b6ab9ecaa6a086004e1704fd051b39ec76f397ac5bf2588f4fee9a8430243c7933f0bd6e206fb535b956c07fd41dab7173ab8074f55cea221d56db404acf75ce888564c7837c534413c16c9456b86d4b8f58f1ed8ce4c9d7b77dbd3f56a34a95c4254fca555e5487208e6d49281944b4a1228facde5e8bc311589ec4cfe237af8ef010d0b371b3b17ce16fc38de503a4a82e9014d73263978d6c4c3c861295871dd1950c76c32f750949219a86f8ac82b82dc67488c0c1b6a22b06cdeea444cf5e1e6e3635113929ee8a1d13929d3d178e4d518722baf5800f1823ff747c34ecb14de2453ebe0419b98a066e9117578aa4ac2f6a615277e12de517193a500398f91ed0d635acc9feb2708e7f8e1826eb0d753466589b9a7148c908fc75dd917cf801a74681513d5f494bfa471e890642e5998f27c0421268a079c834b9c960b0c080bfb3035771b945f3d28fd241adc87edd6de1e7d359fa14195b2b4ea8305e89c7f97a1c470887787867bbb3dfdaa297cc3e37b92fa0e484dee4f682f90702afa1e0b86f834210e2b847f0380a4a4b50c0a1335d2d34ec515941b2a4b4dd1cbc8b0949fd46f1d4fed100c4a8b64429352d270cfb440a07faf72a457b181c4929d406a753c26056095989c863a08dfcaa7c9700a68a5c04ca81c5ff05a0d7f786b0b79b897df1ccd138788e8a9db955d7761975dd935f7ab8911f795b6fa0a84e3be3f19c84728952b31e6450069a2a1dde0fa20f8a08bbd1de325ee0834b796c9a95f9403243e925780521bfbb9a2aa2871f22280bb30a50fd4e8f89936e6b7a90eff23dcbf17b35086dc83918207dc633aa5e76ba0ba7d1b4fbe332da3cc796c2cb14d9505b64748d1cc4745833b9252c779a7712193afaab87836f28ac35ab8798a951d0107551e235b8fb3fd35002c9aa99638437c95a01d1f9423e21ad98570bb7e47b9d90f4ba3e55c9069a8e40a2be1028b5d7b9cf9284bfc07f7c0a04aa5bb752e97492cf82d11012a38e9d15701102ae67ab1cd042782025d8361d93acaafedaa97e7ca3c3dd17a2ce4c3dc76858d2aacbd89619af14ebf9f8dbf1da5b890d9456730851c755dc2b92376df033daf5b5baac06433530266a6dbaef1fcb978fbfa8b39b4b3c152044444ba838cee6bc64063d64271e95873fb2afc8735a404d40e207b28b2c9cb581e629e1149260bc27dc03b417d43b3154c4031148959cca171752a87952e890c4a65c8a70649002f5e3d578b03b5854ea64e18314d4a54d7454e0c0d2c90472bf233e5b3e876e67d51734568bc32bb4c85fb37a08283b6c4baa479bc416f1cf2d4a7108d86dea64fb0e667e3db03a1e6d8facf776651b9d5efde0d3d0e1a29835ef305fa13313358bff79ec9c22a1ded10100b43e9453ea15e3e357071bcd9826a09f3025ef13b708b2869b2ac38829ac96e548aeae3c629d6f907197fe2076e23f51c5279bc16345effb885169d37b0b9efa3527fd663eacd046d8c98bce534dab101c0b66672b40fa15f0dcfec567c32103a571cece81a9c8609e67450eed5b3ffcd49718d134cc0dd8e6284660e83daa468cb3cd900402e597bcea701f58c9d249e57fe8293d24a768559635db49a57daefced0a1f3b4093434fdef7483e9a8ac66c214a12e9f4479a9e306682165c919443667ae260d9224bdc94cb61d6ec3a5098eecbf9c9a5a89a9f857e70e42dc0ff26b1254feed9e46428bb7d6293126873f95b97607aa32ef27e819abb0e0f9dd2d12bd7f77d7e2014d6bc4212c361cb1b3b1b1736827df4871ee71900c4f2a4dd2196382dff8a2549e4dd5f1a5b0bd28a0ef7705332d601908829566c23c05af10f8a960b01477df8c51d88994de43ff0067c7efbfaca50f2c8a77cca0979c66add06874679d00bca8e130c8bcdee79111ff6279ebf122b73a816793050dee505059885035003abaddf011b4c9d04a864682042712689c991893c4c92a74fa299514b5421a56ac43b9ffd84319f7712986fe7f311ad7a04511538894b785f460db84352a170cb5d633ebb61b01c05c7fbfd71cf444f98cade0b9af5a80c666b2cb46f6f4d612bc07078f4eb3ed692048bb48fa26946f18d1ce543411b1444442c9bfa7a6f58defc94dfb663efead6dbce996a71f77e099ff2c839e420a7a0a19e091640dd2871103221ad5006f6ffcda43b903fcfbfbd1df24516aa248ec1c820287217e05384e8f83ba3e189200c0e31960833a6502d89f644be49adfeef146c95d0f5e2a6a12b858cac24d43e7847b10109fb717d18858c4594912a14b89079b0f9ba03b6109f736855b6bd126b0d882211d710068126e4d97cbb7b57488330d99554cba0d7253b3b020b44b51820d1b955120bb1be96d3673c38f6842fc9e3d3b58339d37b9796eb0f0a8df67a192431e5c9113fb6fdf701fbeb942334f31b324d84d7151c3aefe10c0351738bd2de29734ceefb0ded8ee41f16de411a1264d0129e8d70765572cd8642ccdf231b2ad288be93b05ed0b0c1732bdc4d9a24ab9971f3d55499ef3c1a510f14633209a076e1b7be2b6002a0f63f68fe8bc7d9077e66980e89fdbd6db3fcd0c2a5df05b1400e57b1a8a1cba2d7417e9cb28affa31be30abeaf74300b7d1b77e8c0b46edcd171a1e0ffa4c32b80b5635e0d14c4bb2b1cda13c4be8906e4757edbcedb4c4717837a42b4ca9bd67fa4743ca4521d7c3f20767611b961a48a6c89260d6b317fb7f12802c315bf3de25d401921ffece4b629dfc3d3ae5bc6eafdf4c3185147c20510159fd33b275931c38e4c59e69ae0a705910481e73b48b08d1783db2a24a59f1c61c90caf368e7fc6700d174a750c3e8938d47de448af3009fb3d3b679a355b0a266d27d2ffa5fc76f71617274c0401750fb750a080e983df408619794d5629f783a9ce671e6160b59f0034013dabcb15d13a61cc16bf96ffc9672e48eaee85400f2795cb7eedd22d05f3c3ebb1dd9922ecb3f1d90bccf14ba382a9eed49b37e0385d5a40a45e1920a2203f88b8121458453096cba7b07134b22bacc10352e728ddf8cb594512c184267887815d422a62cf1a8c8c35f899d258792fe75f9e5ede69e45addb744c0514f087235de03657d279195564056e4053d752013a591506ef12d9c48a0539dac33103e591d6d8abea1b1d334228f92d3b5a49b60362861c6f889388bc88b5645e1be09c4460c81dd144b28271011107e549495e434cd6c1971340662f4d086bec5be7baa2fe77e520815dcad99b570e29495c447de20227346e0caeace70763ff688641cc11e90792f8cc10f7ab67085d0b7e1a0d65c24a4b6360d22a703e9d725765f461ffb38e75921443e0faaf5add41bf8396b4ae36f3bbcdcc0d1f87ccb15fac4c293bb0393dbe736441a3be4334694308e5e9753559706f9e9a7482473e8dba3d8edfdefd05fe54105d17bc8b5738e58572c543c1759a6d641d3411dfd13d7dc8156315394ba595a0a985ea1665a00ddd068c3bb02ffb73e097ef2fda4be85ab1f0fb5aafb526c17bd5240129eb789e23eeb19609977d1430fbfb1c6758601f5e246581c597bf288ff6a46d962706d017b91c3c7d481567ac72315568be6e38c49b58eb38c9645190e3b42df68d3635761441de9c31aa00eb1b1907f08ade10d046611f865eac93c734b611f357e4a72840efb8f8a040b54c500a8f964541bc41caa04364a9e84ae72fb4cff70858dae236558b6b9b5fc45d33de30467c2889fb91205ca10320bc33fa76d16f0d00f7edd20f771e10bfb07e14baef00f20bab8f281d2764bbbf29b26ebc0d374f7e1ea914402085bd705f404e8fd786862852b7ce5b8780b547eba39dcdae965c499f5aa5e804ba0cb894ffa0bc271b7faade5db46f00d99cb404dbeca48fe5bb985b85eb5a9b6e9f203929e435e7a2b15a5c9070f310478ba9550041dccb09119349fcd88f4296f89462d0138169599a7aa261854fb23495b2f07fecdd98da8175e57ee904e7c11c9456a00c1e259557f895472b056215feca2228c00130eacf379dd4ff13cb42115778015984c5ff78872bfd0d97cbb0ab3aa651cb3c903ba670ba1280c85239ad48a7f0f66aafcf4286ea0a168b2b35720a1d7f71e48825074bbaa31f83bf113a70d5430b6e462d0d1611df83d27fd68d544fed1d0678294046302d3db1b6a6d46d5f76820e1f4d798ea28dfff898d339f51fed6ad5e4d5806ae78af71b6dfca8e828939370f594fcd79967c843534f6139268f06bf49130f1b6081704880629680587d02dac1adf2f4053e63f07d49dd0566870e944f0d9bd642a20cf49bff4c0ffb2ee0f0c8367a5c132eee91aa42f56c04a832183e10ac000066f41a0c06462a088104c978f105950b105cb9e2ef21a261158d0da1a4e1433f2228e2577b66639e8b9771b8570baf61d109e8b2a70949f0c2382a043892a0a08dd2cc873924d6aad5ce73ac1f403468077259cd2136c4334d420e2165aa29f00f9bf4378cfc7e48ea7ac8313ca2cc287fc50486bdcada4011da0f131bd76b14573e3898e25fc01782efdb710c6356cfd73e54d797b0e3eb3a947552c4a17312b0af78b944b8a08699157ea19d68c6d81ac49ac93e6b1e26ef8a34bdac029002c2c9b83e0c23f89f711b51472831101ebe3a8f976c42e9319cf5c86672ce63d8eb746902849f3045fee09d1706a9aa4d9e29d1bc25ff8646df85e5a109cd7e2884c3f68c6934d5fe067d166566e2083e8d78bdcc6c29905031fe397506426f20525cd1d72f8f4d230b77261ed4b270eeb0548a5c9a0e8d9267123c3fa24fa00fa1954cd478c293f1c9582d5e7b6d6083d6fa33b695a9cdcd2737ca820ca724a3f6cc2ae182c120bf9f1e30f91f4896c9aa355622fc7d398d0c021a9364b5ddc562bd234bf33e6079e112b86a9614add5b3ec88bb4a479a5c934dda9b3c14fb8b25ae65f0b257b1d229f8991d337e1abb6fd3fa555b5e8793b6555d532f238ef0ec22fd8b2a9efcddf372cce05058c012b69721e921d523492f09e5e989387a25bd657ae4d3cd6999c2482459aa8588e6407259c5408d48e1a5f2f69035fe87e29f29559215eb4f38043643c6964eb809a088881a7a80e6272064ded66105a5c823ed09ba2d5eac31fc72b7ab28b223db64f99fcd5886daf6c5007a43a9aa173065932f82599ccd4a67c86d82db18cee4b533059c33a2928337394525fa0a23e2bb30cbbb3cd227f00165228b8deed0d0e6dd7f8a5fb9b37bbaa7ab32ab3910900343b7a97a8bced7ffb65193ef9ccf70eda841f6863c66085418a4f7c4a7c2ac2de610263675f0eb010bc2122799e42b1ee858437a0eacd34b85629f1ba82e2679bfa8c472173d64f259130bba13913f64ba4dceddd514510ce8f83704b93659b7da18d837a6f45f59d395d04fcc79e7465917826332423e82a79b71cf6ec4050474952df6cbd7347bb8e1268258374fa9947bddc240dc6e404c6f2baf1327c4445f9a56299de61b54ab9134e61f6f193f68f7d1da0297128173fab36e1c9dae19a07a67bff6aae9bede19502f5a2f5d2fbd574d2ffd7a0492dfa6034483a044cfe712f3eab38a01cda74359008ccadbaaade4553ea0d9a3c123cf9d4b2ea692dfcc6a7fa8db33426f57cdd2ab13ccedc64eeaef1fb58f3a882b7851ff34f0aea1065df785b6d7c53d338b429c52738898c24babc0ebcbf173441ed4eb3db39b8417602b5268bb19704489d015922581a78c1bf38cce634cb1d26f7621a47f9680239c01f7c273d09d06950f253b36d7ffebe1e3de12e4c38e18edf3c320b864ea2080e1fec2ae24de81fdb9d95ade976b85f66a47c6c43cc4344072808195b6ee418b15063addd97d75c3953ab1894fa5cb6a7017d7dbdd96ef58dd3df0eaa57cb5085af7586249b8f351fdf636eba9fdc4744146bde75dab1d0775c7b3ee0da8808afde7a7650b4be856397a5ea8874213106746fa2713df8a4ff52caf9d08b7d12e67fd32309eff3f8b7dc7dd94ecc4e4fe09a303779f9bbfb9d10b2d6c9538788020c6fb9077146e96876bd086457cb7f455d70ba85f0deafd9be23e64fa0180fe70d03fe1067e2033ecf7d483ac21ead3d5049b203ddbc13fdaa3b04da340df6c308b9dfd632ebd7def3604ed5f4c3e6086ceb227848223d832517e774ca850dcce9e434d417ce2cd56cc11395ad9af86c0055b07b12b896aa261fa96397465a5561d0be2cd510b1fd2813c0bcd16b54b38c1e8baadf85260fc4f6bfa74edb25ffed17de80aa85df2483367db994eb696b9b21e6244f1f404fc659248a9195bc2005377806ba7b55d06b8c552bcce9d470019c400370c7ea72937311484d39a04cfe703d3cc96e9b3b58efaabd2715a4e404ad8d3990b6cc4a6b21bf4be87057d585d73b91b05025dca44140389d586820804d25d161a9397c97c0769ba2f51b8c3fd047f6a5d824fee74ae57d69edfc3b29a6b57b0847a7d4bc8f162336900f5f0fc93cf78b618ca0c86a2882bbb6f83dae67a1383d30ffd0031d83ed82206855242d295916971e9fd4bf9465a683ccb629e9e700c7270e05c4fc33791bc7cbc77c5e321f788f8310906e2e1bdd891ef173fafaf3e9c998caf7836df66f2280e776597d97350a488eff107645599df609cb4c2771d3493424a44d6cf4341a0d3b5f4626114ed03da146adc7de1230a4e1103436d6b49ca4e77dfc9350504a4802621efea602d9a7eb45e07d3a84c66da661f7aa6dc9b5035b6a6d4b97a361c359a97a07726920fc59519b2943a2cf76c977c0c3cc6c38a85d776c2fef9b0bb7324f178ef9519438ac47ffa40c3e0746293cb58a6cc87bc269f7634ea58e0ea42a470e52a852ca13f51b3d30d11217696e9a381a249f03a869db856fe618640c673974a7ab8a972d81f00c5cb8e4aa1269529bd4076f9dfbdfd5ce4995a5aa7a5f7d4a9c917a9d468eb965d0337a3d360b2ee54583f0d51c102ed78e4a1a8b3099b3c2d0b6929801d48621344bbb7a83b9aac328a46596651c54eca13829b6b6845db1c1e74a1ef5d89a05da120357db66ed7fa340beec8718936e83a61d775e2eb56a757458d529cb26bdcc638e7a34e58cfbbe81c1d8a29d3f9a30aa6067f1d6d9d28ba345c6e38ec74fb586a6c127bc080fc6ca625ced651950cba141505554916329615a8b5a95a84f2f76adae0933baa1b32407d8ff4f8da693feba68ccf6bb39fc832681dbf3faa397e1aaad6c2fa2d54fbb3a85c1626d2794efaefa2a4f3904181c84c261a722d70146a777b718247a427aad474638255e6ebfea2ae598d5d9ee80dfea15e542900a123e162ce9a31ae2ffa5df4f1ab2f36941fe961a5ca1ee75bd6274123e40d32b85051c3beeef644377ddb555905438ef26f8e176a999f757f8ea516605f4cab2d506a4e0bb8fb8a3e798cb51ca9202905753fe0e1901aee81318fa16c258896c4974bed848bb68e4938d7dd2ff6d87cf5a60fb3756d713f134931454f64156421cc9810d4ec91d0ee1d7e9ebf9b3b3a9c0621796392026c00f51234b325083dbac576dff0815818a2e51f654ad144530fcb9c0e6d2978e2d7dc69f62eaa5842da707f27a4624994742d5ad012ed585ce0a2f9e9d241a81ed6bd5b5d01cc3fb82868770cdd8234ddf49cbb411c11689f76c7bd3e43cd2fba3075f8e67b3c5b72f0c0c2e6fb5b1f13257b18d2904d1eaab431fac4a310dd8f216baa6a2786d8c3f19927a7d42371ae72a95667c9a3b72eca2e53aa0f9a2bb8163d0d5543ec631285163b86f8474bea733dc27895535732a56e4bc957bde7b11efa4700b8d3513fa2adc1bb98026fa4ae2e7b9cb3d1746f907cd1a637eb2e1073a988571313b9c86e734981726f058b362accbefea7b32daa6549c90db7778e21cb416bf0d634c069a26458678274d02b70cb4d279d351a9b91a66b931336e3b73e1418d3c3018a7f451de6376a965ac24aa377c2616d7a7aa2fe303e9a9bad14b4ef2e20e74a156d25f987017645704f852920b672521393ec2e7e426a838fa05ef6b8d2d0dba4987e0ca618cc9123aa8ab2b5b6f9274588a0265e11ef296ac494983d0610cc5c6cad77fa5694aac38eea58df13d1d88aee8dc4df40dc0de2de20ce0de26e24fe26e26e24fe26f16e50f16db601e9b5901fe81f4c4c020c508e81f5d02510074c41b8547b7432875694492836f7af52d40af112f5c598af1f2f50f3cd95253a70da547afc753150faffa7b3744fe4e401182077729969ee54d6d51365856954f3188749a28c545df3c59e0d23e2b64648dbd5183dc84a7511cd95753e16356445f5438244c72ea8158eb2a75e6c5fc9f5366a75d50aba2a13701672012c9c72a68d15cfc92ae9c506bc89dd137261c78dc03462ffd89506e6eafd89301be8b0df5a5c3cf6e231e6143ae50617877a6bb8d863b779c7c6548dd64220d490886ffe198105fa444fc5f756a43446cf6763e59f70109d883d56891b11f9bb83fa32ec81df961315852c2b7ebaf01b6d3f03ee97012ec6497eb832b9b1d7ccf2949f7ea8d2ba0faf2400f852d1e9ab5f7ad34825ba7b32a7a95a659765e597681c08318f283e93af39008da8ba3127f2605cc5acb6bfe836953c6779c63e88ea8ff1601e1b5d84330e1cc56bd915f85025cb38c42600a8cd6929d4d0cefe70cbe5532fe581ec766898c1e3e223e319aff9ef8f7e068561f32a250b5866eaa377cd01c445fba7be92b4b1939e4e1ba8c5f13307a0133d5ca184e419da66dc47ea1caf24010d428d76e339d1a5ef2dd98b174b9890d0f20e7906b830b51328e14ba91aba3541fc080740683833fa43b1c2179923005d465d1770da9327b08afb57017709848ee8bfb591d93e57b3aea7e9e9bcc03b2e8764ecb205be5f59b9cdb2e8c1491fd9d2c6330649ea1195acd36e1666158c6ef307aaa7434f59e177a9a111d9de1d4d958c90bee1a40907d862580af3680d5ac6db548b581307757e55809322ffa5308399d2d2a306403b3712d71fcbcfb58ff3c9dcd27a7872be5f0e1a975316ab7368242c4c626aa6eade31d2d42901d3846b96836faa55b2caa2249c1148a9be8c9dde01ea52c44530cb2e5e573d61fb0390881943a6d211ac27b90989005ec245c1945bea85f600592c66273c08362eac5d02530d4986c4b69d9d78023008e5fef565292c71773d6d6abf1d779f608b5859852643f41019d565c82eb99c0042eea5c47308ba00f281932cc3a66ef0f029ab9a35bb03db2c3615e1d02829e5cb9d37bb30e627002173b27975fe010316ffc1b880818d022d9e2d7c25912707ff9c271b2ee8f1ba8f419494446ecb15176515d2b9fffe79ba287f049de6c8c5801aa887970c33a4f75942e4f0eb56f0777d281235c3a7740ab98358fe1ebabd3543b75ac3f93315ce927c4568552306ab53afe61d8fef964930781345f5fef056da13c8974ab6fdbdbbdf36b187d3d4d5ea5f734025af0a42b3b4697fd10396a7140e24860f6ada572d382da80b3506d130b516fc973770eb25901c57aaaec8fe7d45bda3c07eed0996419a5a4ba69021b0ea1ab4422a0253fb83601bcdf7ccd2ad64b4d939f2ad6acdff1159ca8d4e02a9f0e3e99c0db61dc416ba5196f520e6c08db21208b1a750924523c43639f31f76014ae106112b0e3139d7ec3c24c54471ef1e740ff73636ea5df45a7c9142b6ed04c3d31f27749759d80eb05452b46ef638676827e1a8beb11056b6a6813e8b2d88260bfe80b3c06e0ba3fd152376299cb12e74bac5188131083e63309c0ffd01f890dd3901324f7abd643eb8f08b68e167fa05039c858df364aaa36a3a18cd3eaa6f21305b09e66344caa425cc4420e5c8b7667739aa7a821c5cf1aff470f9c08696c43ba38302d640f7b23b8fd286d26a00670649137abdc43477e8ca7f7edb313f117cf641b5d35b4d47e4a1caa746fb40419e234995d06f35d8f9e04dc2297899a0f77cd4318e32c9e7d4cd2cb7a6e415d3b745bc15815d98ff4e717b6441194f3e99ee8a7690783851119d1d7f80e271d32d08b9b28b67ee880e480366c74c1291d0f376ddddb6771107a9c484781aa01a834d12f3d772f94dd8e470f05c1c016c58e04a4d3a33a312eaae293d4b78c3f3e4def4a694d3568d4de2c5d24308b0781b70ff37029a252d18a746d9a290d76be6277f851e3da79656b04282f9461d86c77524389732ca69ae42248d8e64a042d91c75da8507ec00a07dbe22f08a54015f1ba4ae5ea06b027abefdd137cd85d9104ab9eb9a2098cdc7b59ea78a316d1d146a8e075f8b5e80506dfd1328446fb6391f4e699e2a38cad487156bca6afb56ec36ea96f2278a38203d431816bc55cd62a28324b22078542800c25921a5429c2509f169f44189140c93ec12f4a8512d1434d8c0a5a3ee32064bdba8700c3b71798aa209c7eb74502a7d3fe7b240a7648683986a0d184418b6b0c64637a10281a87c529f883fbef14fc10da315b47bcb91d4ca30d97ac9c44a99c3456139c1177220c43a287941afc544fd59b4922c402bdd58782346492b135062dabffb3e1010cf0dcc1978c62f114dc049b2ac7c1444a321eb4564e4163938218f27a7a02f1f2aa79c129e17c090c3c2dcb2cd14a5889662d1adf13b427ac4427fcd84fec8c9a559e62e4fb5802ad6c42d84146854984cc4506e705a58c80e2d10275eb89a6da58a89c6cf0ddb6a54ca882d3e5fcdfaeb5256c69b58f2880442db4782c524a049266b9b97f6a71b8d99ef91d6d70f6d032e08fd4d542b58f888847ce4418ff599d1cbefe643c8fbe58171ab24dd4b0717109ee1913e3f802df8147673432f88ce61ce0aa880cbfbbf29d60d355552c9140af4dcf74c87834b28114d4e6d9a5bc4fc0027e2d037a01ba46087614dbe512eaf1c07a923ba59a1a2422b4e29aaf0a1b2684129af155e220f6b7a8dc82440400db87275233cd8ef9a05c5a0a618acb3f0f1521f86ae182967b88c00a241fb26086ed98ec00a3bad58a8ccbd5d280441813bab0896a805dfa16a89e7a5c821b4390208106827687fdb4d4eccface43952a27aa86b2b4123afc0b1cb9779f706bd44287c00751c1d218fcf001043f0d5a894c6a7814009da82a18d2f36578e8d58b7de502a1027f566e5b21394300615180923439071decbba2c3335d969a36fe8762bb3237780b71d94acef87354065360a5ed3991b748de3d25da5c0e49bc697da726c9edf975070379c9dbb4eef8d740499b9cc3f461964c3b3908111672b835c9b2ea30c04671dacdcac77205b6cc5ff85eca2d18ebb82663c46291dce925cb868f92aeab2b8981860729eae0387d966004d6684b4ccd87b04e50b324519fd3f0b47151744ac7734ac9630dbfa4995848cbd8cac15983b9caa97583c12ba70be10211d3970bd6f08c020ef0a7c8b2a0a5c5cf9a5afce31eceac1e3794d80895a96ff6820063f5325e8c063bda0897b7753e8cc6543d63dda1c40194646eb6eef660e0439233b18ba7d2677dd049d304aeecdc76539751b21b65763b387dd3b97f5d72e7bf929c14f264888a9296e17d822b2e7e23b250cd549985ced248df24852ebcf7456acb15cad278a0597dba5a9013b50291d7ffe5ad2c9a32798b307fea71c9a88a27a17b581f321b216e393b2b4807b27b8e052694ee8a33c32ab4dd6e90222368503645e25377ae1a8347f82b4f86a0863d6a314433fa43a189b0a7fc3127e1b35e04db976391e1d55385d11d11b5ddac2cd34423eaf2dcac5b35d8dd4e10adc23a15cef57999862c7378d8d04c885c32f18dffa5272f9935df4a626f3c065f6a7e1e559a9fa7155b399dc0d85280236d53c0053d8a7b9cd82adc57a8a99daef49aef697bc5d974c013930f1612cc389dee2bf888d635c9e25d763a8f6b353907cb9d54a8409e07463ebc6288b5e010d5b1bb11143fa548143e6fae505b1d84372477de42595cd16315e1bd6711cb8e2312472c44de0a5d7ea0d6ce088b1ecedd994676ce82972931fd51d3a42521c6d9ae5f55adb83cabb98e79de8f706c1d6a1f1e8f5a8020b5987f218efff88add23e282fe460681f6616bd7590cc21fac45a96391afa1c966f2c48b2c39758193751bc1136a4f9b78b4829af85ed29fa6be5c09f1b465e96b5547e26e59dbaa12020b1832954e16212f78ad43e4ed0e305b359560b29cd1749b18cd8443017c6ea2cadc021181712e5a5838a829d1af63bad343853f58839587bd1e0ddda38745f0daeca8af876f470f904156eaec58aea59767d0f9929cbe4348da8c11a9dcb354ec93f707419043e43cdee5e36bc592e0c4ed4fbc4494a93c4191b55eb132f87a2a44d60452bd431cc155c643958125b876c243f340cca88a51b09d23197092211b32763f25598682f7ac45ab00d4c6ae546184d51b1106e0b63e6c3674362857c2042cdfaecac157bc92f995562b6c25fea3082f3065354f42342711617041671b544d9b9213549e3da4e3d5863200741666bbe92f82c4206bb3c73a360b6b2c2dfa021d7db958160baad3a9772df673556eff0d42701a086a2b788c256727ee9eab4d0dd3ee2e0e7707d12c02b6c98b808fe071f560ba5b65c5ef540bb56fc2d93da0920b8b4577d14f94bcd1c8c0600332c2812c77e63b2f197559ec86fd1793dca99f75aaed95e7924999ecece46230fc94f1dc66e8bd48e7bbed13c98be5c6734539fbace0190fb9efd9fc0af4d270f96791afe008328dd7607682c0c7e8db067ecf1ac3e0f9b54cc2266f7a52963dfbd38934f58c5666a5be371c82593c8272d8d9163db636ec4f0f5c28a4d52f0b28e6d9c09567cdb4695d8134921d3d4891c72c677ed9ac620110a2cd469aeb7ae3cf91fc1b3180ec0867cf203a3ef326e41b1618540bf7133daf681f32904d7c353e7253707664999a71a435b4262ff33cf3ba9ca4a5590df5226e7ebda7cc1e97fdb5db50be765f2bac2ec1208666c91286eff70de559badb4a33318a3cc297acf98646298a6a3054a45aaf8fe365b78210ace8e8c59ba151be14dc43b808e99f5995fb4898325a25ce8a85dbfd2ead9255627f6ac08a999d3d50c44881d9426899bc5dd3c6e05e0f645da40d94fdfae97116bdcff440907e80333b17beaecce10510c668d96403358d2c84b3e12b16d4b91f6d4d22bd4be07fcc9e825fb5319050bd3b2c160a4625a9e519e4b96b1ccef22213b5168aadff504749f690c4cbf976dc5876e6ce533870dab48614def054966c46aff9119000d3e6026f084badcb93be4d6afa0d1b0d3c367f4fc2a5e3d2b8802c38c321315b8b8a4b64b373a4cd19c3564436e5009768dd904966812a42fe54bad677a2583c45000ab23f25d1c77c0254d4636a7a0304a882e0b98846606a10763ba03cc0a0ae314bbb5481cd764e615476091668b46302a606151cf56a09aaa00f8cef39c2de24df360e7727c048ac920a9674182e6b625c7362321cf276c8eb0e71d85b3810057412bbfd21fba4a8dcab5279ffa5e8ab9e257fba576068df298baa9b7d6b1c4daf14616a5d91a5d5e02118b18e983b39027a3659c0f7c973f1bf443866d341f7879309b1f0408db9fa7f65217c49623acb3e23c5881e4905dbec04a4e26899a42a367644c3ac9935b6073b1fd1f514641a36dd262efa0435f7ac0bb29566073fa76987a0bde22d8e2f13017082d739f9480020430ee481efec20c2827f590b29c68ba6a52f284b5fc16f5cde0e990493994b0f6bd76332cc0e3ecb609617152b825be56fe4dbcb150f66f6f80618ddf885a8912a930a255bfdf9a0176e3a25314a9afc4b954b62e7f659cc6d27311db37eff3263ce3acce86e97ad79b344c53dedf670e16bafaa4c9d6e0d6709b722639e8742a866e9f0eee09e52b3f2b7be1f403b5610f95e781a470e9ca5d96d61d538209e06abfe1f5f49b3751653a69d894c694ec5921ff09b37920a63d3ca7ba812f4d9a9272a61ab0c0141f2ba7ab85e45b9fc87f43daed896d182530f44222f84824825f16ddb46403863fa9afe18cd88876960371bae47b62c8b998051b4c728b96d1a4cb02de8d290b0b62a1232a61249088da4e0ed4f96d96cd7517ce923c8479061150500a0208b1bc4b947af369765a68282052fae35267181384de3b79e100830a5f309187a096050e52ac26c3e4ed48f051e6675d435042cf7b72e917e2a3f38cc3b87fa63154080007798135964799df4e46546b4f132f53e82459304202a2035c1b2c8a6b146739d25ae1e0b1a9fc9fd2e8d48d6b1fa4128c428b4aa32e8e6cc5df71d28c557a8eecf69ca790411fc0bdb6c86947d504c20f03e88f3ec7cbe5d1f2e65b2eb9ce49f1fee64cbe1c9f8edf2e897a0482bc60dd30ee844219196d02d5697005e8eabb87cde8ab34f93315bea66bd75554dbd14e0e5daed5fc8a764df5fa491f89030dfcc1604edbcdbd70c956639e465bffdd0c389650b0e0cc18f8e6917a798ac97ef1f0356d41add9960d4dfb1721b7a94980930cc2d1da4f6943c08c180f9e77f77baecbab543c6bd821de4364e1bde4bc4a5313c15796055d04cafaf3530355049c8830408356734025de156d90572908b6a13c0aecf901968c5aa1031d80c1f0111d3f0c00135e506320e076d538cb9a663cb714406111bb1c5505b640909bdd57247d0f3148fde511febe9c7c70824c6c4a786b7850d730ceb35756ec2f3fdbc2e889924c87783c777128e09b48b8b9958e892dcdac7c48b500723fd182770f03e9e80c916f3f570440f1f05b0982d0a9918308c389ddf8977b6549dee4898d4db760755574c0666cc8bdb18135f3bf51fba65f291c3b147504ef51e3596c0aeea5c50af73c0f764f93b4983fede52488482bd8cc67a92934ede6529abfd032422042e6a84280f5510280b6d73c95a7126ca6d3c86cccdd3cbfa3ed38f3df0cff71cd6b880449123cbc7a637b4220adc5a9e5699f67df48a39e0410961201df84e849579b63d29e71131a407608bb9d8ea08a3b78d96b697369152ca14fe0e3b0f950e20eb6d66b3d92ca59a928d7e7ef6538418fda45bf0f798bd0feae3e39352f5789f9f5264d423ad1a329aa53c509f74ca683483c068f6329a4dc14633104c578c1562ad59f13a45b5f1afe0eb24b3f7ec2396d9df35c23898e0c77bfdc060361b83c14362afd7db1ffef0d726533dfda712525b9448f0b3dff178964a6d015327e10e5f82ff3e7cab4c74b5b0562692201351f0df6735994882ffce172aea61ab097ecd5a76fc9ad7dcccb0e2abef65bfe6333b5f2ea3b5bf2f63edeff15626ba56383ef2c36bf6977b9f78cad6733d7711052858957dbe9f0075ade749d7229d7415c249d759ec9ae97af6a4ae5ef6f9c05cab4cf481499289c49cae42b31fd35546a6eb89e35aedaf4896ae45423be92a34cb49d7d9bfecf58101ab32d135ca22d3550433a6ebf981b9e9fa1f18f003a33f30385d1dcc5e3f30d8c93895b0cadadf8b595960c068699529b3b575e68c9afdade29afdb9aaf6f7bd9df16587b61e923ffcb7b77dafdff97ddff781ff81e00782af4b3c04a69f05b87c11468d321860c60ca31fa7b87daffa08d4fe34a831d1598af9dcabd3c6efba998a0073d0068e3e8373f31a9c8ca3d3007170705ea3c1d16c7070f499120707a72cc77c73c25e250ebec1317c62187e6111cf741a9d46a7c90ab70079405083afd180200f086a346cd2004110d467341a9d0608823cbf6a352008f21415ed3cf839d4c10963212c34c39c3ffc1c1cdff3e7abd481f27c0e94e77b509ec781f2fc0d54867af6c9156d1d8c93ae622c5dcf5086c3c7b31dceb0902bd569741a9d6607bbacecfceba5cfecb0c16bbe345eafd7ebdb7969355a8d4ef32fd887f7de9dd76876d8ecf515dbd979fdf91acda9d3d8ebeb5bbc7762e1c3603bff2dc6ab56b35fb9a25c91b88a39ab6cffbee04ebac27e2dda3af72696aeb32be259ba0a6d9d0686ea3437e8a5d3e8342216d8bd825dd885c1320c7f0161b0118b7b553ec62af11712567e39ea5cbe9889625fc2cc58599a3fe22eb80b98ee908afb872118866198ae64f824f9182b7c322431162149a2617ed7e1fb977425c9900cef5588bf902489b370564892241992ff0ac3173311ce87e99af3a6f93a7136cdd73161e1f9e1f592838624f83837e898b6ba0bee82bbe02e271693b8b9c102cc97f33ccf0f7f397116cec25d709713636171af608fb160f80bec5758bae2acedfa60b09b9b83737e78b95c2eb80bee82bbe02eb8cb09c321183b662f978eb1b9a5187ee05c853e334e26aefff62ab44b213c248e26071d94e58b2fbe9be51d4331bd5778765d29ee12ee20d1d90ff9e36ccebb57a12fc45df0173676fc7c669cf78e25f91f89bbdca0321d47c74228c805b80bc67291f78ac45df41ed3f5860f435f28ee72b3ce1c7ef6529eae4562d70b5a74cdb6af421714651f9858c6da8431334ccf9c9bdeb9fa89c6c904f65fec61e679f3b11bbb31dfd4684e7d0696ea34374c95e059fe175a82094a4a30c1db7ca14e136be304fffa18133e34ce8b74c343e6c33e7f0e3bb55078e8f53acd0d3a5318ccd920dd1c8d4e034b359a1bf499af2fcd156216edf57b766a83bf16c984c8749ded55b673b8ca4459d1775e571ba47bce8174cce5de1b0603b74d95223270c5016050e9a1871e3220c68983c63a93ab60306bae162fa379d86bb9f115d8dddd3d144351082df7de2a34e49cb36b3c83ea8cdef77ddf48aa41138621599a29e372b95ca509e68b599224499a2f2e573b47bf4ad334cd172ccd953439c0ca8ba6b1c15f57b2d84bce9dbd8adbd18041938504ec6596e4e81243f0d3195f3348bff8cbfacb80551aab7bef07a6e9856b72ce190ccd385381effbbe50cc001661cc30138661283a7a7376958e9a1101d0050579ffe28bcbb842c3bd17e732a8d6f03c46067a5dd068c05ef59253efb0d7d9f6ab33d0d082461534c67dad9c4ab08bb131d516b1d0cbf419adc63192a97bab642225d8073fa6129aa5f1f8c3749589b19e64920b7ee822332c35673bdfd4959fcc686e50a8cfe0d94592deaa6b55251389a7ec7f362b759a0b244ba5a14e73836e7873cc03e7ea596821f19a0fc9178471debf60e31b3c4e41fa8e9b71cea7fe7b85b3f00c0fad536e1201e2b48cc9249908f6af37f1c3d2b5d524ead7cb1ec97f71d963ba6224f72ac5583708a74878963ff433269a24fb64577aa645b8cb05823f7f10a954ec7283f0e38cb3626b9c2b90aca10b80a0f93373e2ff0c965173cf9bbf496128893fdf7c6ad388d622911bd488ef9bffcc0e7cdfc45edfec3cceaba0f3390f9a3d22ec3cce2bd94995e8a49e7d725e74bdf8f8bc7952146f186210ccdfa7b5fe720631dedfcdcddffd810f3e368fc41e7c378de01b582c3d51f35fb0cfb9a25be4dbe29368915ce49e4ecb1ffab7c676fe287a0a863f0f58d1a18431cc9421831a1592d84012207640830656078438c2bdb802830c196b2650811cac8c5106135bb050e38a1ca850e25e81a18c0a64b161851ba654986265460621c6a8a1f585951ca65880e2882934b0058d2a358c74ef035678b8e2080800a15ddbc348808a59050a1d50420c9623b8b02187c3196496d86289aa1c8cfc9dc90d245011c2033390f1020682b861e717b71043f0d319b36854e9818a335d40c0015fe42af704f377e5fbbeef2b4551081cb060a38c153263e040c41946e0980d9859c20244f03006025ec2944153a5296b9330106709750ed637586106bcc1e045cd192b68b10219189ea8a2871664102325874ffde0030666c0801a6238d146152bbeb842092458d8001961a47184517ef72cd00a26f69a83f55fe860f6a148894c860732568a4e1948bc029917349920fea80c0e48e18022c28419c348874185cb0b64c66001092e8c3499145e10ca63e812c778a15d2eeaf0c1f00bbff0cc3ef76f4ef860fec6688d38479fe0f769adb5fe72d6c07775b601e6430268b4404507d490d980105fa068630628d458409b1cd9799e8fbf689d821149740085182a80812c3bcecfff3bbe7c41881b5fa878f012c604c47c018226151b86502387225ab84cd4606a71031a0cb880060d6334907ac1e18b28a20882011800c31c2f936931fc3e30d4e58f41a20c835cefee3972f5953fce56db3ffbebcfffa26b0c8a3306c5e93d47c47d5f5cb7be729ae723f210464250d1c319350c21040e79082ff3802c80a0818a23c0f0c01d3736bf8d37be3e9bfc72cef8de7baf7fe40bdd246cc5319cabde0eee2f7ffb74da45f7a73feafbd8fef4a7bf2fe7de7b8bd09b4b725f70df7d752a661e9bcc18e38b31c6d833f9c29f6217be2e865832304518010d68069011010d6081d1800fa471dfc5211498b040152cb4c0058ba6214acf071c1e2005d615109a86a9242e50c40d4e606106165b0fbabedc02ce8d0c7ee0077a209489391ce386470c3f50a603ce4e3a8e5a5840ebe40cb24643c5075888814ad50c5a902973acc059e2bc09e2156b40793631c2b4a8c1f5d2a2b76396a1534ae086bc023622a1e31a93e5ca147d80855a3ab840ad14707c1b886909dcc85940b1b56c6b0d7e20088263ecc8e28735cff6305bf4f6d763431e4597d66234a05d39e719b656228bd10236d5458d99187ab872c4161946ba85363c88210504ca9002849196c96499884c7768adb5d65a67bd434c9a3c135fdbc388d92245315a642cd1df9bcaa081d40bc8328fbcf0a275c6102dbca065caca14689298428a0a3a8841c61a20088220d883165da198174431ebac5d19cc202846850d822098434c05088220f86959978f89930de345837ac797f307a6b10abf58ce39af09b596410dd1add042034f0cd1c4144c44016a612503665060872972a840ce21be4c66c319593403c4a5ec14c51ce64cce39c42ddbb1c6cebf231b6d61a5016d8218a2cc163f333723022e336baf3b5e6932a84532cedcc8f28b2199582e65a7289e59e42f7fdfa77315e2165fd18fd08c9c6522320d92452ecaa9e7ef6322bc3f32a1d99e0589e2fbbeef0bc1d9f79191c4f7cd8204290a8365af415e510869f11b22ef98fd5f7989378421193288201953c8effcc0f73967200d57de317b331f76d173ce39e759fe72feb24ef3994130e79c73cee09de121cf00147a47fe1de05e5f488decd2601833a8f28359e7bf9f653c331a3639b22ce33bb8459acf3f7d86e1f77ddff77d2118668b32f6f77ddf99e3daebf9a00be4e80ab1803978263bf5a7add06cd161b0a44f4061850e583730c165062b6af0400d49a470258c0187d901fc51758133bce0c0150f00a32be345ca9a2628c0451a10449303a8c51c06cd0a1b04415083200886270862d1c606df0804c3e0409a0648200343f4b48c40158c306e9882430d4f943102f5a7331be597038220088117d6a28bb78c0d822008821a04417009f29e329d0124f09733990a6918be84665c645038a3cd17618821230c2fb098b9a10a0b16e8e200233048061f83af451004779c2f7425008220087efa6f6c0f8345d616b787c1026baf429e259b815cefd67b25efeaae7b8ee86c1eb9bddef6eca978537dc3bd42d9d73709db4c467023f0a53ccb28eafb28f063bad7d3ef5b83ee3a3b7fce7f631fc9eff7fb5231fbf8b9f386ad77f704007f4f91477b8eac5f4e5709b6a7229e89f87ae87add1d054f692cba40864f920e8e632ae2efdeaf0cbd4834e21df80ed39b2b674ed12647d2487824fcd13475f97a85e28f0f338f88e28f47cc310c61621e43334c7bc8f155c2643298691ab9a3f8ab6b7c12866130180c765d7c8dfb651a3145d175c4fcf143d48829fe488ee387a34886a1d34ad388b89bdc36e1b949f3348f98e2a3a669043ccb101dc18da85488f58800fbd72b51123b5fa6912fa65d39e3a8a34396b72ccbb2c4c931cb779da6914c9a44c6274d9df37c8d4fbe7664b29df3849946ce7b9ee7798e39a611d78fa38e59be699ae6e84a9bdc36655a84dc634ac64c233856c262e588ee984ac29bf2c99b721c9f670cd1db66f39823c0fe650058ead948cecdc9c9c9c9712cbecad7318d8c5f963ba6f931732c5de58763dae49a691172977f33923a65881a193f1c8271cc1c31cc31d33275a791ae147cfc3dfa8de4bf608f4e7fc4373a649d60f65f3bd89f289ea74cf6bfcec05b74f3197c11fcb104cf743dcf57bacaf6f766fafb67fb4bc3347f60ba76b0bf29abbdfae78af663d71fb83867fd69adbfeffb401004c122f946e014cf9cd9b94aaeb266cdd5e2656e04ce22f9af3f7effef03e4f7e487fe778a79eb7b5f879fc6d7ec5132ba9488e113618d180135298a370c3108e6efd35a7f39831887e5877e659c775fd737033f7f45c0dde4b601370882398c648945151a32990b967ea14a151961ca00a3881c4997d3f08b391b9721811d6b9cd93fdbc3ace165afb166934c4e0fb38610db83bb6fe0b8d1e1d4791c8f83933fe6e8ebfbcc66e7cf41736f1835caec7b6fec337abe8992a107aff79e9d8fa56b0f929bcfef1b6aedd9498fdca4eeb40cb563ef1b6afb9beffad7ce7b7a8324f619c9f966cf4d7ae44cdd69f9cd57fe1d74353f90fdf99fd9c1ddb2cfe9ebc110eecee7bb19c2ddb2d89fe94d0e672c4522fb9c3291fd9932c9f936c7d2366f9d7fe578f01dc949359ee9a46b9bd31547ea7886f31acf701c04d8f33c0fba46d93cd73c92370fcfeb63e68b3c9ffd5f1f83f17cf822cf67ff9799aee1b779f3ac7e235da7364f7ae3c3cfe611bc6f7c466fc462e91a8bc5beccb15c7ebe89c15326e64d7b8e7cefffbda7ebb84d26668c7c728da1a7c64337fff23f517f4dfd0dec71ec4917f265707a180f60b1c937dfc8a9f32f7475da259edda4abde37ef5edb791e9ef4e6775077da4dcadef3713ee765b27467b693ae7abf5eb0d71a7bf1757070fe447de7a04cdabc71522777c71ef6df77c4fcd8794d24b1587a1eb9794f6f503c9e1f731aec3d159d06fb21f84c9998fffdfa4ac9d7e7fe9cb6f9bbbded4dc2366ddaacd92d3ea2b3bf94097ecfa9df209fc2715f69905cf6f717cbfe1eafa5be1a63adcb5fb5d6bfeaad5b4d8e9fcd23e3af49f698f27ebbd075ea874d8e6beca91b3649a6bcdf63fa9948be2cf56eb246d945ae9aabed7bc5fbba89046ffc59cf20beaeec2c5e89a217b16a67576fe735a21a515f5c85a97055952a9b4714b14471a452452cad9458ca2ae5152a2f38916865c500f18a0ea296078058aa5439df24e2f96cde27f36845bc12adc42bd14abcb212afae402a4c44ae59a3abacd155d6ecfc55707ec55450918bc913963154ef7c2f4a8a5cf04cbc5ad3c689482c36d6d2869dd7328672859dcb2a58585858170b63b9b0ca2a632aaeb94244b1ac229655c4b28a58561171d25576938ae2d51a6295f88963f04cbcc279f32fece65f65c4325788177748e7f38f605c22b22afbe4bc8864bf8e6a6c229e5fc73423067650d1881b3423ab32110f4e8af34ad7bf71d3aac7294eea376815b76638d2bf91be0c9ba968a2be5f1062afa27895bf1462e7bfcac2f3a05526ca6b7a62952af18a95a8850a88a58a6c1cc7b1ac229656ca2a25969d9f079b6595b20a89456291582e1e9728f2a422095231b357511445114b144551144551144551144551144551144551e4e1e191f1f0f0c84451e4e1e111cd2c2f2496288aa2288aa2288aa2288aa2288aa2288aa2288a5859677986a528ba5cae711c499214c771748de337ba5ce338ba46d7388e233992e4389224398e6458966489b2791f489224499224cb922ccbf2659aafcf2ccbd22ccd0f964d190f8ff8899f298a3c2fe3e191a5a2288a595fc4ab2b56c42a62152a22152b51145f57727001ba8469b3b53d4c9b24367895894833a4183b3f8966e797f2657b98365a3bc776313d343b2cd12c3a0fa259766e5838d79bc6cebf8356bd91ce0d0be78a7b1b05b7b9e5575fc93ef9c12bf02a13816ab2cf5526a2da1203b1402f60161e02bf4a5f097bfbdc6bf4952fe19a34be7bd95c367896ef1a92f4d7ebc2cc0fa6ee4a3eec5e589310a3b0276f89abf0909329a6c23d3cf3722e719231348b4e8e78957df29b2888057a41c5ab9baa73c55665cce02131f0d09a4337f865c82d6ce535199c68ccca3e39eb06716bf1b0f358e6879d5fccd157b0682d5a084c8e599928b3c13ef9a9c050d1cabf8e65b095be82ad4678f29e77dc12b988426021cd90627c265966dccae2d66bdcda8aa15974de44c1ac9d1f04bfa42017ecd306cb46377edcd39b04cb312b138154c6ac555fa15ac7ac551cb333952d5ac14424d6f5c92fa6715e36d76795651ce793b93ccff3c9af3ccf27c192873f9df30b6f9c621af00c5bb90f9a44b0159ee53fb73857128b7cc289c832d9277f8ead2419328b9dc92af2f96b91507ef2dc31908b887585804578507e504d519190d06cf62fbb2726bb80574ecb7f23e5ecd93d371ddfc7f8405aa51ba7e018ab4c045e619ffcd86ae73f1d30aa7189b0551eb7ee58813b84f3f9470c5ca29dcf3fa6b9443a9f4918c829631b3bbf5f2137f51b743e7ef29ee41b8172c13ee0154e5759be412bb8b5f39f18e7878157378887d37c9224ef87e900984d825b78b6c219fbf2e017f0ea0a3191984faa2171bade8baea2d486bd930f4bd7fbb07fa1ee34927cd86314cc52636bbf9e7ca530f4fc6b22793df97aa12bb8a53713fc37ed3139c065c3fe9ac143690cd578b6b62f14cc42dbb0da7ac3ce87a5660a9a41412c223805b756df42035f768bf7cd9413e7bbf3f8f1182c6af6cf1863b06ce99f317ec6702d52c0b2dbbc85bd5a5598d12186d14eea2df62cccd0b05bbcf10a802c4e3936fe9b29e79a5260eb72682c36f937534e5edadb64aad3586c9c6a5581c5c6216663ad2abc6c12c9d5793265821f8798eda60738c46c9c36c96ddae864d149c57c21c46e331356e70b465bd88b6f0a8ae1b46ca2e41669862c836760169e652f7896c12d3c84d39e22fe050bfc72a3c2b98257bbc7887fc1fa826741f98dc03e6bd893249979248c5ccb8063f02cbf06c780654033a0185ed3c2437e85788ebd8257e0979dc93298e824b7f090d661afad157a46f9b2b1111b96b5041159ae44b9214a0c5156882265e39cbd424903a58c1828554069020a1728426c0ca5ca950dd342ca4b0f1bff2a85c38ebd4a55edb0610af0e204ad13b49c7005cc8a6a826ac3d66c20898daf36b64a63c37a69885a4fb0886a38796263ad1deb62c37c70b283931a5c6b583058176ecaec9b1c0e932c382476cc3025b1b4c48d2bd4246e587498d9309b0b435944c610d1bad92b912f448a9060af1c5c60632b7be5c08bbd1a6dedecd5a8776682bdeead8d9581d6be59b379a860afbb37e48a8da118d24503ad1d1b0204ce5e873860080c435a18d2dbd8ccc64f84c94a62c39e5862ea092a534ff4cee86d18666354e31b636be326ba68428ba926c278d9b12698d052b371145b5b52e02085980dfb2204135a4c3131c3c6696cd897351b36c51a1b576d98103b36c5d68e75a961e316f63aa5c49520a89461b3615dec75aa87dec6198c9a17d468c00a5b6c2ea0b5a58ba91db250c0cc864dedc0c2c660ec754a872c5e7b9db29266ca4a6fb6d7a91cb680f63a9503117e815e56d65ea770c8c261ac40b98261e32cd6e02eccec18192c7b85a2a5b7f1ef550a869e0b657cf165af522e7441c65ea55c68a167060b1430f72ac54217522c8051238b8526d0ec558a852e1b97b157a9aa2d1bff2aa5c2d5d05ea5a8d248515949a5a066639dc5a8c4981de361632b6cc8d8b014ac365e61af5229b420a50d1b306cd8a8a60d1ca488b1b1949427366e438c0d939282036daf525262d861c3a47a5b4eec26dea68d1a4348f57cd8784baa978354cf0605a869c363af0a50e30105a811d3c3101b26012414d0c5141b07d165c326d0c3c62b949929b8d82b54995e157b852203c40f3b6cd8175d9029f70ad5c5165d48e150ec15aa0b263c20a241468ebd427151012ea6e0228b8b24c6ec158a8b21ca38b105962da8c8b8428b2b2dac2250e3c65ea1b4ba58a1b4bcfcd82b94961119ec15ea09adde8ef5b081c318c970d9b17c04111bbf4339a001376cfc500ea0c14c1a3b9636f115ca012b38a0c7811ec2d878af503d7cb1f1af503d70b1f1af503bf42250f35a3ad4b0f1af50567a3f7b85ca418d6baf5039648562686d58dac4dbb4696363a82aab8dc5ab2a28641b96ae5154b6117205b3c98d94ddc2c80d4bd72632b535b88b0c6cdce47b2a5810450b528a8416b02a12122185288a622886a17880d70ae5003321113f6c2258d8f8cf18220b22aa8640620806b0110e2126dcf245b8058a8d1f082ac21f98d8f8b3c21f7a30024bffe0c40f5dc22d6b84444441a489189a1ec4362c5d150006065c2e97cbe5ca66bc56292a2afd4313590d275ce66b9562c102449ae4f454d0a607371b96ae522ea8d9f8d5b082c5a81143cb35e2de6b9dbad2c35d9cc96cb061c42780864fa0011bbf1767706166023b8ca1620519534b90e1e28b8d5da2cb25ba4451145d47af758a09361b3f155aa40833844bc1c2a8c16b9d6aa20d97a2ea4b0a0e85968ddf35448ad1e51ac7d135bac628af75ea8933409348139dddc27616921c47721c473283d77a640d0dbc442145348da06c8abc0fa36b748de393d76a6bc2080454741c91e3881e2f6e64c9c0e4308287861d3039606c2338616c5c9afab5320993330159151044b1b50902ce86a52b2fcb88b8c4f75a9f6cd9b82449b2244bf4b542ad404ed9589b20e0d8b0748db215aec1016348851d469b8d1ff66e82004bd39e22f89d881bf6be612e66072dbbc5ab9818d2d8ed8d12e260b48585a5b9dc820a46f8b0f1e7a23430013460300f48b3088e7d8efd79bf0579e7b72012a418fef3beffdc271fb69a4f5ef3c9afd4a608bad74b5370d2f98b66d1491dfb846611908b095e519d6b944d3edee47b98365ccc344b169d1f514c95061e5a837b99682705c1a45a3beba02017ec935f44412f60169e7dc133f0d4e2d45af00c0b9ee5d7a8a69dabbe828587566c45037885875c4c4f8c9d1fe472d315f4b2b3b975834bf800e9454e1fa3456bd3b68fd1c2c30677ac2e7757ec065ae4e3c382edef44e78b61087e3ae39d866813a88d499c86e0a7bde6e011517b06cbdece11b3c42c6c75c2de5fef66f93e62abd9f721d6f7615608e6fb50ebfbb0ccf7e1d6f7e199ef43355f900a2aa82008cecda32900b0d5e7e377529dd44a4cc174fd145bf940c132d9e70343437fd01ea8679f9ee781faa09e7d2088207c514b26faf1401e0700e92aa6e97aa2e92adbae4fd7dfae0fd79cb057497e60c02ab04ca8251349f06204e92a9e10a4eb29d391aeb2ef49d79fad33a11be92a248be5282a12129acdfe65b2f31479d2553c4fd9cfeeb5ca41efc64157f16aa560bb3e86aee0567805440209249849f03f509f1d3b76fcec77540d1949f089e229fb5999668d59526cd0a588220a2836e8506c9df5ad274687ecaf4accca3e9f15ae025e11b5dcab112b2b0db166b3d9ec7f4a91d1ecbf17b1bce6593e7cf860a37b463cd229e3959090d0fb3c8f74ca682494560d19f9f855d4e2e3e3e3e3e3e3e3f36b98c583070f1e3c78f0e0f16b08a6478f1e3d7af4e8d1e3d750ebe7e7e7e7e7e7e7e7d7b00c8d46a3d16834daafe116101010101010d04f293202fa353c13141414f441413fa5c828e8d750cd951b64604194b28dd6d0036bb8c6475a667f0f6aad11d564edefc77415cf606d5d95d1b23fadfd8157f627947af609424120f45b937dbea7a1df0ffa9dc93edff740bfadcf07fdb4d00f4cf6f9d4ec2a6be881fd3d296a99fd2a5e8959ae2b27f2f9d9af2e2c57b3fd03e3016cb3ae66e3194ad5e379bc4f249134013ef446b10308925ce87df9fc2e2802387e42401e1918914f2e0ac40ec031bc6172e0daa14370f1e4e49800c70e0fb18707234c2065148142202610a22349c2528738be5a1e72959a821f12bac251077d1ee8a221f7ebc9e8b723e41295b2054484c8c471c74524658310c820e24cefd03b40207a3c5e9a83afc707816b87ce01f39175a0d0030aba7929d374fe149026600627763a2286d0caba2202082c2d4c418162992ca94a480930c100250c40002424f920001dd48a80cc26900000afa998a2cd1968c6d0200309521c37c22b61b0c8e283084434a8e9d02949f08b972e5bb464c1e2001eaee820801905414039e0a08484366cd24832c019699e38dac51610036e29032163a845062b229704cafca0a566c1104f0e398a618e1e25923041bd21092100478a10a905f1e173137b997a07185858e166ab82268818028829020c80684896a3b5d0426300196030e40ac122a67431a2871a667041010dd861871890849081100acacb465e03afd16a3e0d3ea3c7c014f8b6ae17980ceec2c54539066b81b3c05e70119908cc44af77c78d40d4f1e5306f601eac73736238df0d8edd53bf30f98dd8258a380c3fac6fbe1774f7d187fc043f4b2bee3a1ccf2948bded9470baa71ff75b9639782d4ba261710ffa4936e1eea29f2412ee3e819f640c2749e548bfb47b9f9ffdab37dcb7b81712c5b3f16c4c4a6fc37d1b95b48624e1a68d0adcfde52749c4917e890ea7323d799f190617bc0eb7444d42001010d00198968016ccfc2be2eeade308f55c67ecb7879fae2dae2a3a5ccbaab767526a6d4ddfee74cfaabbdbfbcc5425ded38e759de0baf94da7b6b6675d1cdc74eae9ba71f933b54b746aee9776af2ae172ecf3764b3c5bcbbeba4467e3b980dc55c107e69e61818fcfe079bc27251e5f73c7b9bb939fa20f4f126279404d4f3a048c3fe36c06e5a18c3fa302fe15f0ffffee3803778c813bbe4048a8e66b5fb8f8d7ce5086f004773ffd0c57f7af8d5f6b97d4dcff88d4322dc1f1fedb253587c4da30c020e4e00c77b83f8b6b9f92fe599ccad47e8dbde99e7649bfb4fbf669f7eeb836d6beb5fd4d65a284d3d99c402feeea53cee6e49154dd2e04a4b7bd6ef7483e33271bef89eea956d3ed70ad1312a4bfa9b99c4de9003577c7e12758c57349b6f6a90984e25fbba9491f75b3a9bc9cad7d0281b7e3f1883cd9f1783b23b0047720a0d04fb0480747f4bfe082f7317b960774db3505b54d4b2a6fa7e4722fc1cf0f019e84cbf9001a02f24163d536aae5ed926c1fb8439930c45dc88420ee6ec2190ce0c438bfd3b19f9f764f93dcb105eeb8c81d13b9e321773fe2ae2dd03d6edaf8005710c41d0371c73fdcb10f772ce48e83dc31903ba6b9e31f77dce33a39e97ea9dcdd007e6a997adb29a9b70a8084684515b0ee35b8fbcdcf8c85e501a9b9245bcbdb39fd001202f241bb35e1d8a3a41f2a135bce557700b8bbea67d63154c10f1b132b0404c40790108d5599581bd3910eb7536a7149414b3e84842af0614b4afaf1e346e4636988b6c301b16a6be3290145f15a16b744e505ed96f06c4a3d774792ba6bd1dd13e038277cc158788bcb39b18fd4da9076fcc41cb8fb8d9ff88a7bcee664d7a4f270328072771e3fb1cedd73fcc40670770ca0d49b0ec753591dee59dcec756aee2f7835e96fb9a71d13fb3eeefec3fd3eb9bbce717a6adc3dc8fdd2dc3de7312caeb8631eeeef8e6feedee4e7a5a16d5af271f7eb78e6bc1d8fa7becef63755c77b6a555eab539bd49c5313ee5b9c8dd7fe121dae6d6d4dbcddd2dbfcf6e0ee3b3fdd8abb9b7ede77771d3f2fe9ee373587b32d3dabb65149ff4bbbd7ed70adeda378bb26b589e501f9e78e6beedec44f97b93bcb0352529d1c3db129ed72395b5014cfc64bc2e58294786c50122e9784cb053de996d0a09e74b81c4df764a763a27ba205193a1a2a82a20901111101f9086275381b93526eb76b839c9e5af68917c4536f39dc535290dab4630a72c714b8e309dcb104eeaeddbd74c73b729a60437a56afb73de740910449de34203f995126df6b9ca6443c20bd49fe7222150a522e7eacef6b9df3bd48903756eebd2413f09dc8d55aeb073f74e9f05de717c5bf79cdf7bece194badf7412de504bfd8e6175fcca25428a6d8ec20fccc0474a54c40fd53ebd45ef15f2660dae4bbefff5d91373864fc59fcbcc32749f723f9bf94d7ea079de0d76813d91ed326524d641b4cdbdcc1dd62da446a8769d66993bc99f8fb7e37919c1be32864e7fb8b37369cfa7f879889ccf712c54f6214f035cabd5c96b8c45e82694f91f04126619a250a3f986689caafc187821b892895fe5efefb1897495caf512a28e45f5739d5eb91e8140d8cf28f2855d535ca3f450323fdb94421ff7d13a532bfea8dc81751aa5efe9b52a1907ffc296f44a6bdfc634a55f54665eee5c75fa2289822e4cf8f93e027bd1cd1aa5b758d5c4fa2545334301abfea1a914f62b47c5d962914924a24c18dca14ca1625e4df87323e7ea3ef47d405fe35baa1e16ce26d369832c912853f4b947e9d04ff357b44d08f5f497e23f29a49f4bb9924ff670ca6408a99484a2a0afefc51eeeb97daa224fcf1a5a8aadee85302fef7515c5253dee84ba5a2b8d2aa374aa3802fa6556f943f4ca380f7bf3405581bff28e0d5195d14add2545553c44c848d303a650a4a55b5a589069b497edff7a7e8744b130d767ecf220079b383182a2b01989acdca1063fbdf31dbffc3b2fdc32f6262e0b261a47bcd49cf47a4e74b51f7473db8fb86260826085fba4ef9fbdf5b8271e21084f01dfdc97a8326081704d07b3c98edfb37457bee635da4e7de9fedffb3c507eff70249fe38a23d46240a8395ff4aa1bcbe3cdf8da0906ffe00c63f5328b01fdf4ca16c5102fbf1a1c49e7c377223f296234afeebefbf5e3f6524553e2cedb9698f08e4c35ec9c3c6375329d88f2f157b52c769e2f7dcff123d4599f9672a2575669f3c65044ba5b6ac59603f809b06892b7a4650a2a43dd8c3d2285086fcf9b15428fbe42f3fafe7999eb802f15f0ffb55a886579a24fbdc5439dd289b2983869e91396554a25346a493f8a24eae07257a7be59ba9949453c91bc18d98b24ffe2623b8d1eb477023f3f64a5763bedf2bd7ea4a952b47f0eb4b14e67a8dded4c9c55b3f0cbdd8e42981fd2b95927afd2bbd57d9273f2c1dc18da82e56f6c9ff8265d8bbd1ebd77bb5337e8ddec76f6454a2d7c8cd9be6e4643bccbe8dc64f23893f3826a2127fca1bb9a030a87105fa4574cbf76e2271a781e90ae5b40cd5e60fdf316cbcb4c1858d2d3666dcafe39cb3d6503ba34dc29dff83a98dc99c73ce396bcc06e93bd657cf6bf91f4bf1da04ef8f51f09a04ef8f53f0da8e7700bca7ef8fa7780d7d7fbc82d7fefd7195d722787fcc82d720787fdc82d774bc3f76c16b39de1ffbe317bc86e3fd310c5ebbf1fe3806afc9de79de1fcbe0b59df7c70bf09acefbe319bc96f3fe9806afe1bc3f6600aec16bb1f7c73678ed7c87bdbfde1fe38073c056b00e3be006784d7c7f7cc56b616faf622f7cf15d3f3ef9e59bff7ad89f1ffb9bc7f99cd7f99de779d9df781cdff3395ec743f011fca32900760cc93e1a0c02fa81673cb2ae7c07ceb5d4f2528b48e491aea24fba8ab3743d4f19907495a53e52cf024a677bf5ac34157b409f51a03fbd9c6b167fce1e321e3e3e730a9cf6a4ce82220a29be4c41451560acb80202588411938596165b44600c175d902923012fbe3033010d860ee35ce13c63383d6baf23047c8804efaf378b5df5c2c169fa41f455e506e9277b78e89583cff4eb2c48aaadff8ce1fcbd3e0673855cb186d3f467345c7383f48fbd4c24f6b08ffea22221a1d9ec0a96c9ceb3f72f24740661684e264ecf2281d82b78e5f9ba50d09f5d4eb177a5cbb9ea3069d480756ad929ce664242e92a1494ae455a2b78a5b744f13ccb10f5e0453400ef428ffc8876f0246ae44bb4c89b28917fa13e1e8672f027bac1c750a3bf41f7e3a0433e07d5e075d00c7e07fdf13c28062f432ff81ba890c781d6be07b5e073a045af03c847800efda3153c8a06f914157a00a041bf03bd2f010afe04687e65d1f20310af2c5e0b5fff4b8bd7c4d7fffac16baed7ff0282fcf2cd7f3deccf8fbdfe97ceef3ccfcbfec6e3f89ecff13a1e828fe01ffdf401f03b5e826f72db78cd83b43c23006999c6693a0aaf1d494b33a4f05a076989e68bd78ca465195378ad485a924185d788a46506aaf09a8fd49de6038cd73848cb31acf0da066989812bbc669496178080d7765a8a8185d786a4a505c2784d83b4ac80d3b418af65909614c8c26b3f5277da8fd718a465184ed317a425185b784d485a4e20025eaba5a599315eb3202dbfe0c26b4569e945175e234a4b0990f11a90d49d06a48cd786d2924c0569d985175e0b92965c7ce135a1b41c63c66b416919810978eda6e51660780d4c4b2d74185ecb69a97526716a1f3f803c980da6ebb7c19c41f0aa82206920516a0129b57e945a3ed255a6ff84e17c188e70a30ad2550c92aea76b187ed812faf1417fbe707e0d7b157bfa85d25514faa0743d6f7abe9006152aa7874163cddeca44b907245dc51fe97af2f0f9f567bfceb6b67c6409a5412950baa23b7f91dccb4423bca6a56b1192ff495731c9f748d75384e791aeb20fde275d3f849fa5eb0c84a7205d85f69a7b7b845484d45dbb8f76ede907e404e8ed4980965a3b50f00a00a84e51b20a45c7dea3792b02344380ea2b4ed3af03cd594ed39f03257b4ed3df83be9e709a7e1ce8cb09a7e9bf81be9a709a7e997e1ef4c584d3f4efa0af2e4ed3af83be96709afe1cf485e534fd38e84b09a7e9bf415f49384d7f0c7d21e134fd27fae2e234fd30f47584d3f4bfd097114ed36fa2af225e57e88b08a7e91fd1d7104ed3ef425f5b9ca65f445f42384d7f88be8270da2b0821b60c41c45511461cc105892494d82f2faf265e4ebc9e2073d695dccb5be3de7bbb6730a7eb6d02d2d6338973bdbdadff15c40b11c1160886d041448eab9e22701871e30819171e247692d05122070b67899b2e3126ce9717d8ab89d7cb09f3f54449f6c89c35ea2baedc13f35638f676d596d7aef4975abdfcfa7b65758081be90beb117f9ddb5d65a67d75a17f11be41863ec464e30fe6b997372be9cf9d7d2484e73f620ab70ba1371e77c3f5fa14fcd4d0ce7dd2608777f613ad0cb9afc40494f1dfcb3b857f28f84e433dbb58f54aba94e514a909ef635de6e0949758a6a796a2e67db80b7635595559da2da16a784cb3941a93f5445f373e80043b9022f1cf85901943b90ed752a135bcbd4e29efce89e9e985a56559778f000bad9d496d5e11e09e95556005fbba9ad4d49e539a9b76771af7b5a7a16c7a484070f2077e7e167053982847177223f830499e2ee23f81924883b90cac3b1ea8de5ed96a272bbf6919e06a4c335ddf64725fd4dc52de1945427f65f67036183123e4a67fb253adc8d675ba2f29e7dfaa8259cba44b7cbfd4ea7f43695877b6aa28ee0ee22f8f9038d1f687edc70fe58f223e6a30c1f35f890e2c3e714da72f700f8295483100c4245415beeaec4cf202a18f81984a403400d7077d44fa028ee3e003f8166ee2e003f691170f71e3f6958aabb93e027ede6fc9102023f7f1ee0ee04f0f3270420103628e1e769aabaf44d6a8bdbb53a1c0d88f589ca25e15876e7f4379d6df6aabaf451eaed29a7a4c3b13835a906f403c8ed08d2eb6c3c55a72ef9a5ddefafd180a26c3995f7bcddff3821c007086866e3d992fcffb43a95674b7200a45793704e51ede77238023c1053db42a9ad6ea7f4351ad04d675bda39d99440f81a12d2d38098b038deb73635e9a36c4dad4e657338db92eab4d3d96eaaaa7b16a7f2764dbfb47ba4a31629cae6f43ba7a7ff416a525bdc91aa2e212ded9eb76b955427eacdd6f42cee9ba26c4eafaa455ef7b3497896824dc2bfea64d7f4f37fd3d9580a9ed87837a616f7e47fd7ea784f49513c1b4e09b7a4ead424a4ff9bcada92682d8edd39a9b756876bfa5dfb7fe3d99ef0764b7fd3d9fed9dfb5ff5f7bda0321b5ac7a43fa5dfb6a5294ada9c9c6222101f1763f3cdc92a71d8fa9fd9c4d69976bdaf1969a76ecf3d425a6f6e8a6b31da94e51edd1121d8e7d3a3abaa92dae0997bb1d352941b147ed4da7b2eebe819f3cc600626d4c4a385687e3b5506a6b736271b7e7a94edfea6cb66775385babdb6d90c1fff814fdf0f1f43fb30b94d401dcd49bee6967c42af158281b083af57684a7363db1badd061f8cd03eedd4265cab3ac1b54ab6231697c10078473cf5a6db39b12aafb565e053f4f43f454fb30ba28ed4a4285b53d4cda68ec053714bb87617c2121deea65393b04fadad25218aa73e8900c4e29858565552b27d54126b63526ad5a5d7d9947e840752792ad3df54deae655bd696539f78edef9e2501e9008f43005293520923d46a6cad76d4a4048574805799da253a9c9aa4e6724bb86f7536276a4ee5a94a3a9eeaf43c5bee6989ca7bf5b6dbb1ffc3aaac4de955f669f7b99c8dcdd9969e8db2b5ecb7b8bfa92ccec9cdf62c052d8e3d9264ffeb70adcea6363d8b7b16d7846b3f97c33df9261b6fc98ee7a4db7d544e87e4816eea8db753527937d5094e55527951b9dc4d656a7749b36741d8a0845fa2c335ed947438deee792a53ab3211e081783ba6b6b5b5ac4e75629fd8cfd992763923afb6bb269577539928ed72aff26cac9a4bc22dd13d1579a0289ded9770ffa32eb1b5ecb314f096e09a784f4a27e05a1c6f87e339bded59dbdf54964909c7f4e47f6e6aebd4a4b6adeda653d9b77d6be3a98ff403c813d2eb6cff73535b9b122ee7f4c4c103edda5799e0724f5e7df2404bbb6f5299dabfa94dbcdd920ef7ea936775aa92cafb96dd35d93e97539f3c6bfb1f565da2b33129f19e76bf6b9f7d95676bd5253c5bdbb4e32de16eeaab4e6c4cb9dc7ea0679f7ddae1783627ff64a763f2ea936f6d4cedb31fdc70ac6ea7f42acff6ec8ee9c9b33a95a9fd9fa5230ff42c6e69f7206c50c2efda6f734fb625958307f2c9e56c2dee9770efe35bf669fda85ccef6c43ecdfea63ae95435e9591bd3b338a6f67f69f73c35a9e949a73ee56c0378201ecee6f43e482cd2df5427ffb39bfdd2ee99581bd30f55f0e359dc47a91e3c10eb64e3b5dfaa2c6b6371502aabe296e870bb25ba1d50144f7d6a92c3d99cd8be65d5d6967b9549c9abad9a736ada29e98ee8763822399bd2ee03f6a6b6bc9d93d3133b8200545600ba2337ddd3ae48bb0b8189d2ee03dd129cca539dec486877216c9689d2ee03a327bb0f5adece49cda94ada27244c9454a60d6e6a128ea7c4263d91b00be1839bcd48b7c3b13c7500379b910eb704c74372c3e56c4e7447d85665d9cddb3129e96cea081b44b54e4d44d45c12aee5a94e7447da5d081c80a0e6926c3a01e4704554de530e672b72d3a92408e0a6367921ea0938b6e9a90907656bdab59fb33d35515f4dfa5dfb37f5041cabf29ec53dd61e813b5875eae0c0bd4909ea888dd2d940d8ed942cc119ed764a72aa4e6763da6017422ef754447d529f44605a82539ff05427bb23b57d5252a1549e0f9a9a53591a6fd7da72b99dd2918ef794a42ae96c4f2c8b3bcad9da5d7bd4b24febcd76d4aa2cab643bcae56caa135cce76e4a4aa4c8e8e5425de6e89a9b5a9b7a327bb5cce76c45397589d8dd7b6b81cae7d626bb5a356a7b2499b83a5dd8e9724a72ae96c4a47aa931dcfc9c9e6e3e896db31b12a8fa93d3a82b9fb07dc5dc94f0898705f8263716deea9c9022034f5497d6a425397d858566db2f19e6c2f4dba639f1cee36c749a18ceb76b856c9c6ea6c3c253ad509c92e041fb42625a80928b8c0f11077bf8e35b818dcb8f29b7ac339d9f19cd8a7a69b7adb29e9d48fcab54a2a8fb753fa76a77b5b6bbb7dab53792a930ef74a849c6c2ddb846bd525bc2727364a6572d4aaac6e876359952502426355259d2d671b4177643bd99eb0514bb876a76b754f3ba52395b7533a6a59f5a989cacbd99ea83a9517c5dbb52c8ea97d4ac2f15add4e49b73b72daf16e2deea6f2948e54d6c6a4c47b72529f74aa139577d484b3e594744b702c6b53956c39dd0ed7e25a9ecd89557336f648656d6aeea85559dd93ea84a7b24b704bba9d920f20211a4f65b2e32535b1b627453f68aa926d89a636e15add138fe6c4560084e6d4646b59b509d76e3f6fdcdc5de5ed744b8edc5a257555794abaa79d4e6753794f4ebc27a723b5b5a93c2556c5a947b79b4d280a6ac773a2b56a2e6773ba1d45e59c9c6ceacdc65be2f474a49e80e3ed98da275bab539da84750b82595e794cbe198a84b6cbc5dcb32515299948edaa71d538b7b72a4de764a47adcab2ea129bd391934d653a6a559609cfd6a4f284808680d09ad4968d62696a938db764c44f1e20ee4037957d7a35e9db9d4e6753fa26b53dd2b46b7536a62447475f536f3ba55773493626259bcad33dfd4de5a9ad4efd5acea6e3a9ad0ee76a7a481e3685ae5d63703dbc726f5fec860ebbdfce7d5df18726f24c838620c4a2f8e97caff36020c77e45d01a5c0f857c0e03afdf7bbf2f0826b1df7b33eee0def0e27b337802bef8867867847bc11b3abe2650c620381bff6a8cb5d5d5f7e2f172005f977f57e3acdebbe27bb12800dc832f0635be65e07b417c45b83cf7662c4af1e5f07577eebd175b8075c47c75b8f882f85e9c8f2ed6a32b082e6f06e2fd76640c6e88b1f691478caf0f6ce82a40fb208be424d8b5e3075c9d67fac337fbc7805b81ebd8cff3f4eb575f7cd35c11f3b8177f18e77b81dca02bde12638cc10d2e05186b7c85321079439ce09a3738b00df7c337aebef93a4e827fbcda310ef447ba2f6447a5b83e840a9c2f787578cd9bab602cd6f22dca1763fdb874cdee776f28a47d442074f1088a38bcb77631fe2283d78575767253fc746fa65d1789af185e3c04438021c03ab267bfb00b627c4d7d668d6f608c2f78f5d275edb8e487ef775f443df84848baf04d12cb105f11ae786fc657df0eae0e6c01ce81491ce2efe68b2fbeb5ab039338d4377ff95ea29befecba6e79ef157221c0630eafa82fbeb7762fb88373aec6f7d3d7024cde8c21b83963acb1be16607c71e0f1baaea8f1bdb77675e031cc578bfade4bf457c643bc20f7f86ebeaeab65d7e5aa9107e881e002405f8c411caaf7d411223bb8175f7cefa579e84e6e80a9aba60069fc0bc0c319ad02a062a02180ad02430880025bae04f5c249e0410b95045b163e020f5e741801dd21118187554d125b90211ff000011a1dd8720859c0832cc802db2b08220a983b88d0f10e51e07e6008a9cd0e43df860321b101010d490bfed9f261f7f3423c7a5cf9392302a42f23789e1bbf82830047fa64f0d729b8675fc251e2ae802713581e90ea6453625577133c89bb43f1f3868badb5dd8e4c58eec94ef4f40b3dfd01c8e36fc80129a70fbdef2bc309bc90a279f0a1794a2c41802df4f407ae9d31872c859eab00da001902b2054bb18858048e59886cfc20d013b17a3926487111651d365086d1fbdaf4be124717bdb24d9de8b9642f1c0af9823e1aaa74c81b36a0a0a9f460280ccdb45890288bb541e1eb211685176458be40c4d2cb20f808024750885e26411cfa87f491af2ef5cf58e4eb111600c4018ee05ae694392e996b272c40fe000a208e7c85c207a627c1aa9bfe48021dba56c2c098be999637d0494a1e108704087bc2103e9dd705c9fce932e78c7398c5ec1a73e81c0a20bc812519632118de1cea203a485ff146ee91e28ae00ba2f712424a0cd43d220728e83050488fdedbb4318604ca68a1650598a72524c86045c448030c8876ba33d2045170c2c4f000064012421b3163b6882006a12a360cc1210c325954f18412446409029aa086a7360696c7010da831c3c5982990988a9200130c500292108c148900029e1d2f4c4c1131441428473a72ec7411bcc200a34953af4d1b1d50c38403f0ecc4c8544105144f2891441131c0c0c251020a400024211cc9420c15619aa8d2c1175e6451c5144f38e1031617544841036ac870f184135e8ca0eaa9b7234462a69a0b881186155330d185cb0f32bca0de8e1260c20108708488909a4fcccc428c153ae42003957a4b40008e142122c42f20461662aea082892e5c8af86101305021200047301052230ae2c3e726a6bdcd05c400e30a2ba89882892e45fcb00019607881aa0247083041832020366c7ce001ec35811c56545c288100423dbce04214d416579bdc06fec0c7065e430c4203817fc059ee0fec230b5d2092967be4df8900e7b83d218e7ce3ca34cfdd71e1dcdce453c3cad769e2286f9039c6982b14c11083e0f7e5eb20d52801047aa12b74e9973814bab28e528994105271068e62912e615797e0917e92cd1cd36552c0a107be70e8d23fa68ea2217ae300ca9bfe7949c1417a3e01e80b5e10450f830f3888a580f5a83ff0e9400f0328f0d09272bf5a2c822152b0c691694e17cde0ebc1450f04234842a5033352211b91b242ef868b00654e997342111d27911dd5e85dd0ba8c202de00c5dd9fc986816a84811f50bd4716140a48cd1838b471754e21a1b5e98fc86c4420bb407ea01ae50c0f96663917102d004cd23b4008525783082021031ec9c4254a6d2072d7ce9914f4b39a460fe80dd01e4c842c21b4352c6d00270145f99e88891ac632ca2466f0c8f9824881df4102932ba3a2849b85db0448f47ff0c8d600dc704c79f4ca8140bd60d63354b228668464404100000e31540304020108ac66259900661a0b7f814000d77be5a625096c8b328876110840c3284184208000600830088940c1105002bac06311f38aec58d633e266464ea74d90402e575d21899e916cbc940a901e98135c62a9392539dd60fe947713934a3d9566cd1eea365342af974b583e9d99dc0955e3319084cd4e9191409e480cf72456ee6bf56bdfcf7d0f1030f18cb52a2bdc5c04a97aa38aea42f191e9875e5a4c45067c6f87911ef950d7a6e511be10158bad61cb7061bf22e24d0ce84e513337cc6d4cefb3a06da4bbb84245001623d141de3eeaeeb2fc3da8d60a97a09a5ca901e555944ccb261a8aa77248489d7a154fd0cff5d291c0cc1fdc58b8a96a6259dd26021d93f987b686af73b7dec754922cb4abf6750c1597df7741733010195c7bd2c0813d9c37dfb446b5ec9ad2ff8cd06585bbaad78287eaa274d960c3bd9e6b2dc19b493e4922a69f1bb717787ed76149d45e30e6a9c41880fb5d833eed977a5c717fcfde334433bb3b6366d8aba36a5be5bbef8d3d3127cd07c8d71e480818d3a0986a23e24900429dac0b9e0de6075acf67459c058fc603a807a6d980686a92626b5e989682f2eafb941df499ec496cfc206ad921d7690015a755ad6fee33db77ef36110d68ca81f1050bca92c6c97f214bd7b8f8ba46b0a17de4a00c9865fa8a6483b6f4961732b8d83f5e3365a691ba17403e787901d2bfd4e07048230d7dbcad773c100f5bfe3978175796e13f69c7119e8d7f2c6431fa56c4da0c243687ca6dc4b6615c768d45dd60ffa16068b483541ea12231b2600d339194586d2f8265ceb8bdf5243a24be4e401911681c8eb50c02e418d1fac8afe4358d76be4f59c61a5aef9ce8e1748b5ac7413fc9b8c3b6ace6bd928d8afd4b8ddf29d52557a91e1cf4ab4fff7bb3f2f93a2370baf604f241fec01f782e14cdddf3ac358bf07110a0a1a3264de9b2a3117eada75e2cde803647e216ea2be2362aae06104faa3640816ec5471a3766e7e2033a98828a41f35d5fd424d93abc900326f037be7885489a26b3bf44079fc12fbc75ab9c8be63669c13f1ae6d84c4f31ed332e7af5e60f46b602101b31869a51a44ea09bef77f5003de00784d797263a67c70058b30e057aceb0d6ca7810d73cf3cf281e50dbfc1ec060176d57facc55299e1b1dacff5807ad462210a6e0456fe4c7052c6eac0edb8b1e1ddf32b1496cbd8199f8d95430747c03b974bcc9f4230565140548ad17fcd737db1b6560e8180abac222f359ed296db221d85ddbc9ba9ed054fc3431061c1491e6b8642f1ac8c9964a8c1bd58cd75cca559e2458d4e8c1dbc1bb84bd0410975c84d45e27b065f7ff3704405cfc6d377cbea9c7d08b146079ab922e4833838d311b50303d768236444a75dcd69d1fbfd1ffff16e031057154bfe51dad8dc75873a970ab77e332be81604b1c8ac30a1a3d7acbe274533362d55bedbf1dae1b5e3503d52fd63e5de14c644005e7f425debb85c55ba32eb44355cc61704f4a9d73860010d8edb442a6581c11a5af8eeab96ea19c639223236e3d8b1f5e09913bb98b28e4c863078705a4271a85f334306d11f0ee9bb40240bff022e7bd05b0dbc14daa991bda9940d0b28dc5d200273e16a6ca418cf2612414594213a38caeb1e8a4722e1565f9ac81f61acc42a6190e80c18794f76bc8d0c6e84354c3894a3b7cf3d2b50d3f292d950e9b798b814885d38aa3f58e2f5a68d04efe2a0c5690a964228c1693bd8f7106c1ee95bb3a0707e9ba1088a8ac542e8eae82888be0c19634810c0bafd2f277bef5db7597a3d074217f8f193513ef2c238f1908426cfe12b2ed05662b1bb6270ab7b2b71d2aaa9a487eaf49e2b16e5c8cd30e5d34fe151791f4ba37f7a5d742130f0a3f48e13dd86e1e6a09d819f5622b5bd607a90833827b0dbe78a0e8cae171f80e73b630dd34ac14e91dee5c8c7f6a8bb8ceb50420d39269252990ec3bf30cf7dbd28ac3250d22e0738f09e48afb63f5354f44ea11558719a52b4fa1a42b0d46bf59239fd818f971fedba497a76430e76b947e891bdf456e2c1781e67060539f07652241c4898c2773c3fafef1538c2281f9577e2ea2b9772686926ff233d482a86ea8a7815935b537b3986a4bb15a24df23d087ed3f3744a74e27e5b117d324e70b037bb75b0d431889c910645182a6ce3729dbc03aca027c49907121e204aa922b9e045307e34e69719ae39c2e490ec3b8ae3ad7bd065bb8664769ad955093f9e1344a25a0825681304a4703d61026d32aaeb458c1b8ad7e62642179b75dfbf8c2896199f83d60466f8929fe03af50f2b307fad86fd09aa5eeb06287e1883fe8e90ff9ee9ec059372fd129cc3591462728c907512949cccce0d74f6af3d6e7d372c7952feb1e98ffb77e585ecd4d93206a21baa7d22926ac55cbb76af097ae58761b80f3e497028c0815b412b99e80a4e9de6d7e6ffd2b4bfa11375152d0f0787e29845c434e7f874041cd0b55577d28c7820bb5896cfc2cd2d32b9c41164610e65d2f78c9d19a72326b8974e9dc73cb660708720fb766da1bd9ebd53487d0ec876b6a1da90f77167b75b2510ca1cfc97aef5f6b3ec8f171c69f63e28c60de29e777e72007cf89a5fbf6907e6c32b7fb2c9a5b85c9b8b037c1d95d90ba214ef995f2f97b8fd80f52b78ef07db6511bb6cadbca4f2893c3f2d4b9e5acc6ae90c9938cc42a7a760d3142f9b66e37a4ae26bcdbac1d39f82efed9ae0603c4bb925ce544b1f67e1fe85f705cb18cf163fb3f3c6626fb7118448e2250b48d287efd87e9d70f832b5fe1cc3ff286a6b0111e6a551ad36269566d945d649f020f850c66206cb5c480efefd827170ff0f1f5f8a68edc2260f6c3b008a844c4ae1b176d76fc22012135231df30a36f4a6e372297274f7c15d107679c091dba636bb5f846ea5e496bfd933ddd131b397fc6abcbc57f30687f1ab3beb045fed7abe610d34bcdc5efe26e733579090e919d850a4cb9745b4adf82ed9b1c31dca369ef2d2a113ef4d3b6ea7131b15797536aac8ec9802d9594adc38038611117ed92f5f64e77b056de6e323c00e58440986164b9302339ae60d1a145311ccdff6094b80e44f3522e39fa005da48489da750a30c8fdf51c2d540490addfdef2f99b7b59757ce6da84590b0ae794014265f9e45bbc61b30dd4e1220d7ee643e9c2d5d1619695be8457cd02b2d1907d703a758701fe9d2704a7df7a755600e5d49c5003751f26f337f876eb019602349e3254a0ee74d540c0bbdc90ca5d7984ce456e4be30d749fa18c56702fe84ba0bb67f06155b8b82a3acb23abdbe6417c4bb01766c9ed8d2c617ce2f3b3bb988c0946fd427382473c97b23b0bd46521405964c5c431e9f8a97329428e74134d256cde925870710a1b12fb8078cdd345b8184298fcbd657acbbed0a486049756efad183ca62cfb3ee939a65c1702750d3c5ba9c96990d55ee6afbec6bff0ad36c202d9b33d633b747498797ea3e1f40781cc7d0000eb06458ebaa61570f894b327b63a1961b5f80a88fe018d23dee4cd333b1ce6ebb5c29528011f7fa6ca33c0016138e8eda920fb11b0012a3414a52427da80bc5f13a7b8012c99a15c58d3a3bec67133616dee2618763be6aaf046b0960ac25809a1a66d22bf9889ced540aa8a31f3cea91825fc37448d2fee40f43e1147fe1d59ec8414cbb731d6b7b9f1b9402fdcce9036f6e17c47206d326937d43b820b6f11a39821cc1731374ff51e073d15df8c86d4084e34161eb68ea885f29911fa387b38cf5c953daac76327e32d4ae0f70442350c18ba1baa66d047bb191b785f729b64201366e3940e57b2ec61bdcba91653ad1404a3df66630deee561f789d528a94f7c0907db0e9b7cb4d7afd8fa251765d22535fa3224d0ed8a9b496003f481a2302b0716658382e3687384fb2d1c1eb6e0245906d98b215b3750508a6f5a4cd30e63373614e64bc00dbc24e8183fc40974ea29ccd623062b898e5d58781629136e965bad2e7bebe7819c4599f1cb65d656532d945297db1d862bd83355c9311d4af564da8c67937ef4409c1162ad021c1dd954d7e6d41d02fe58f48f94aa2eb8a0fe1ff2f46577c20692e9aab52099085c6a52f336b2a98085eb68f3f133c58f8c2b122725cd64597ebe9cc956885e7fef42381cb7f2d174d5c6c0b697595d79925926ff59cfc5df66f86e3d06e024eb01505f506bf84d3f33add699affb99d208451499937d77f71e5ae9ccf6c595af487e33178855fe62f02d37b5f83eadc072ad94544cd7c0d529888638674c0c31385cc99ca7e5178516b05c1b7b7c7ea3522ee19c15cf928aa86fb85c313946cbd515008c3ae3723b9f83fd9cf8a0dc0fe390968255b8b2ecc6d6062d8ae96f1d9a01fc689d9a98afb1c045c7598530a3559fbea4a6612ea0c260d02e01cd10aa9b6898bf48514b95f556d20131bd8cf004445eadbd3d076cc458b2c5839e6192e570d73818c6952db27aa0cb8608a6e664e8d84ab440809e7eb0b34e09b519b91ba4ac0b7ded11a41739b0fde06e9e1374b2830c165adf47429855da5785d6c7848930eabc78c5e0648949d018a2640724fc43f8b7101482c2b09514cbcb9561e69865ba80fe12d86cbace504a1d634eb53c903dff68c32ad4aafff9986c30ec36a207b1f4ff5c848ba159f3000af0052876dc612863f1dbb927dfb9be82421fce1a94091d73a280790e7e380f57f46e200a2883ef9afc8b625ad0cbb812de355445891074205968bf3727ceacc7be994f7751a07adee8abf5ae1a8761bd4aa79e89394e6adb66a9a1b23f80f7a53c10d492ad3c28038752dc359e20345d3c9cec3ab8ddf246edf1a8ed507bc0ad0bf5834019aab7751fe783be88e86d8dfccb115909cd21a8848454ba6b481f801cd41257e5ae51035b1aa689090975ff510c9d74e812eaa16807a0a18f54b50bf8c8587fc56b28f5b2c2d5cb1f054074d1c848f68c8851e175291fe74dd2fe5bef856641f87cb2d05941695d730a90e44ee3decd426eea59dc27f39a8c324ec0bed5f904515e5a01bd85bcfc651a43cdb186dcf1e5a3057dc2d533f6e39d2c1d843038c447be29729d8ed1b16b9d45254d2c9bec8176a0530ea8c86edab07a3a274474dc72b1ae2f50113abda289706c8df5d856f8b024ece46954778d238bea82d107bff792ecb7666ee183c57411eb23992f6b637725768c0e5e1095cae454f3a4a87cea8de0d2a846214274b721e26d7e23ea4edff4f281be23b1961d11ecece6dbcabf56be721f7ffbd1b9b2bdc654b0a70fe047eb69a63880e866900b61a160cefab91d3a1d845f7016fae90d339c8473f297fb37af0701e945e9fc09a8fe7c49e44eba3f4519590764a1b3803d0d5aac1397db557306cfd0af4f13d08512053bfd677706a08324f2b0cd426b8d8322dd000feb6ed9a1deee8200be216549e52c5791aa871a460e6921fd429b828069eb0a7896bf3926d68a2df2b97cf3c443d71e9eca3848c943e5c66f08d3066a5f2cb70208f530cd112dfe3f32c86aa523046d6947eac66aadab9d552dca97ebdcac904a5cf11e52bb6eb1c5b91c2e7627bd8cd6d12276ccb3f8d2a4f24ca2d52f6e4a98eebf65136abfe151d8a4c10d8b148845ab7e4624e2d578d7c9419edaa0de912b770f2115cd7d909c0bb5276de9f91367049909348200bb9c389f8ca1b933fa7b6f80229fe1669aa3bf6a384f046236aab04fba9445c1369d45c6c13209fc956773f866bc03abd18cf37464893bde974e31a68678ee6eefcacfaef4f59ad448ad7559c3d516088977beef05efdefbe3ac8ebac39c7808c74681e98d66e631b7394c8ca6ec1549bd65bb88eec713fef18707e306c8b5aee10a6cef56f2a36c8888b70858fb449823f7a1b1c35868045ff96e75cb6726da83a01b7efa259b2f53169ba022967f7e3b9b2c6644b99d2cf14b56b887242f45b21ebf096e38107b5e46aa078f4ac739fafee4abc8b787dfe8b7179272219390af1f3481ea6c77aa8052048fe1b78ccddcfb0770bde985d2c40081eae27a20af18102ea0e11784094f03e1c8e358fcdda076d40b422c733be885288fc3d163e35004023181315a02c701a0135ac91f78f0474b8f394332918635fd4c080c3fe1b765cc8954296125fab57bc0b621ace4723f5826df086b71619d442e4bc8eac5b0e72596844ff855bd80e6e81db873392ce4da621b5153406ed562d659be651ee1d5a5e51f85e244a5d6f261dd223836ba529253b0c9d5d8091affa62c316a803109b689e902b3918a983b86a2465ad70ae977c4c2fa2d1b00cee79cc4dca074a178910089dadffa3b58624d346bbef521d8d7c1171c624e9d0ba0d1c3a6adee7d6f6893cb0f033a24f8c1107c0fd324da34e07750583295e4381cc7379c158fe451fc959983c4f5ca6bc2a70da637aa162c22a1decf94f7c93ed4d4a3897ce6d353bc98dae0e8979d1168db554f25189eef2fec9fc790b2aa03e38a1b8c757e6ff6b388a34531f8874b36961fb228cde6014ec863c42c737b1fbad177a41d2cf3829839e17fd7e0cbfff6834ed171278b5c08280ad57be317b45731733138b012a76c8919ad4ba76d9a690e73de9f561387dc889e253ca14b4a7b5c21431c74ae60d65ac5a81fb4983de030027062cea6b04d2b99e1aa7c3027ca511c9f0c5df36e3d73efbe476036b140f7cfc67953e2ecc312aa3c348717f9d94ce8d813926c1629695f907215e4189c215f1aff2cfa5530df4c9688bc7b50565dda698d1ce8708570a0de8ec0c82ba1b7cc4e2a7c6ea971876448f0243913c8c4cb0f48a71b9217151221f412883eb27d0c3518da3c8d2562f45854f65686741f65c19c4c841c77a1131d025c3cacb20b13f009bb06b42d0ad3ef2fff44fbbdb3029a6ac3c0ae5f3a6a68b13af81704c8b04b7568632135c4bffc3d5978fb70bcf2c85429d30d75cd14079974a3338f84a89d6371c96b4a15f3451512706718783e53d4225cb003fd21aa24f939c6943aca644b8231ce4b18eaa45252bd270c63595d7e3e77938eea18133769d3c68f2cefc7f2df83e80f67afa535a83a9424739f9b6843a4b7d09e884de523dd31c48cd98cb6a65129bce286a00c406aac6778dc016f7ba5aa178e10fd7f41d69085f1d22cc05b310def0f455c171728622f64a0095f98c4f408cf32c39134acd61d96e81c4ccea654768db0081b6cb62841c281b69f06770c2d4caa7ce284524632d40f91f93be4ef944d636aa556c18105148112149bb46ea93f8aa2f5998ac27c2352068cf902db251b5829a2ae4d5c83c505706471dadb8a1d2fbc3bcbb1a8880727899057b692d2f74db2b55bf39b7387525b984d116816c77caeb7245e20fb6d588f1a29610b645374fda00b3a1c13d85caf7f4461198d6666b1b93e117aee382addebdb53020377117e3c4cfded0a263fd3c6fe420a8890c57317d258a3052b9425cded3d3e99eab00e09c10ac0b2d8f00885afa6b6ba84fc4a0b8b31a3459a0ac3ba09063515d43b87aa07e4a46286eda75c598e9dc5f4438b80dfa8eb573c1bfa166d672f95cf55d7c664c265b232bb20d978132c526834de30bdd5990f628db60f816d9ef57fe105baa6873962c2719220f65d3530d8001944955b67bba0bdd506f4decaad6603b0a97108e680ff5647a88fdff07ef396cf1fa22694634c8eb95b8b52eb313027463cfd60631195a92a561a1822b7b1a0a7150f67f566fc78f2598734113c9e540f51ce9b5e30de15077beb1ccb1851c4c28dc466462ce6ccf98cd36aefc70e8460ddb02755af1d062384429ac50bef7064be1008797336e92113a198294b4917641556610aef770da223351c3c560d4adf62fea795172ab31e643908e225042712828e29d8f8005c22c51fbe4896db0fa6b5343a4dbc7750b35a491f41c25a7512a3a68e86c9538b4eed0ca9bb41e7c95b6d92beb961682c47520ec5c4588e468067e60406c53f0bb9ccb244013032bdd4c45ade115cc1fe17672c6e823e04d03076befac32fd1a8a7f1aa6d8e0ed738704a46ac175d19f36a3acbddd61e94d4a6e44db6357ed71ac402843257bce95c662236ed02ac7c2984f1c8f9e9fc5d6b6b99d6c4abcc56730589270e7839161acf55085fb11a59a51442e35959db9311bc8dfd60be56b72828bc6409a66b6af0e01905ba652f039a5f25457242a3769f6c023f05a820ab2e1ad89c95a50d5b91764ae560f4f056810f71b7e8ac3840e6fa1deefb40e809cd37bd7c9645168fe3ca6498ee4412728cf05e964d6e158fc74aadff9f64a3fbb178e805e16defdde2d9e8aa561b3847f4f9c06714ef3613c4740b8a7fdfd79a35cc908f1d88b73094feac7cc55b62694888870cface81010574c2d81bde3c0934bd730c96d769417aa2f1fc3c28c5a377cb0a2b1e8e5d121c0bb1c9c7ae541e32b8f0a3cd63a4845761ba1de26a345377c6654c7a69f140fbd0e82691d0c75b66a32db66585d361e4755e630f38a0e04925f26a0e0940958d391d85e98c7ca9aa9f611f9d3933f25bdaa5010895d5d9a79500c593b00281422c50e09d5334a32c0de115a7f37ff0203a57cd0434150cd0e4b3e05bc66493234a5f82811bcc3ca21959218421c6a6d927cdbbcc6eb63771344e03a9df9037ad20143174f6ae5e007e7b2415ac96026d34df7ff5886534e3d069f94224eabc2c681a02a9e852e0800a0f1d571c165a0c0dc5c8aebbb945e0aa7a69992d92789712eb5da8ff001563fe59662b625e673803f8ea947ea2d4e96bc2e7eb6a05f4312221d6b2d88adbd3fefe33a495d53618808c25f2d609d3460bc73b13494b71f681a5d42b2d6b022d4a82ce935b77335295b5841149950f90a19b9f64505cc71ad31527daf99ad0232407d5906404cdbb11dac61d1e6120b0b252243618bde155a725a087ce293a7f7af12165df75e2a700758ce70aa35163ceb05c0dd8755230d6ff6e232dffcea296207a9a9a6c8fa76eb0ecb3f13ae2c8d0d17798e5fa7eb65d705d7bb5c59deb18bfa978f28b7588dde571a76eeb9893d0c6326fb73c39c39eb589ce348aaec93cbe9389a3bad34db163fcc9cdf4b74025407bef90260670621d131ab72d1bc35aeedcdd3017035eb0388072a42c0d54b290d4316e959871c5c5d1fd5a75f2c5544f8f8f8fa9ac63d63f7836f32396efd4a568c64ff7e337daca2de1782165f9ac63206d9ceaef9a05675577f2d6316eac61c0ef17670798e4d3181bd8684b5523e0f333c0fabcefc251d7324964f89d81fa8cc4e8114870778866c45a9f0ef65224d158d8858741661d83cd4c01b26085816e0a5d741d2b9b94fc8e669001778cb917ec246218a1177fa59814b21f3dba3ec7785f1d83401fe9db3258fe123c5644489ac33e0832bfa06a8893d4ffc76833583b216b40247ef896dd3c70312185a072dd7dd980ce573d1309b2450936468d016c39d690984ad4b20578688402f6b03bbcf2eec6e1136ea95bae1fff09b0df890d37c3a4051d2821d299885fc07771437c0c2e4ba6486a855a878ce0fb1c3190ecbc6264be13db2ff2ed15b8ec415825be4733f916564a9e72f3ba6a34acd8d8e6b7102a64d8f08cc28261af662c8453a04a99e7075ba0dc45bc52cb28c9de4e96df0cff2946dc320aff01c87dc2c4829f31fbeea2ba011ff59eb177f3c6cb239f41c013a5b3a88af442670fae59a2348943710ba142e34ec9a4340115099a7f2596cb87ec4661ba158d280689c8183532d1047d15399636cd1681b0631a104c4084f98e2755fec128056c476da53c1d9108321b6b80739f879a43551216fcec171a2c29325305649e18c1ad31d6b7c9579879505a0f9dc7fea0a772ea31cc5ee76489d450247558099107f3c4878d70263327a4ef4505a4bd298608fbcedb536be4c8da20bedc91ae0b6927b79b5188377b50c4d59b8b23119e71a7c5eb38b9c27243113e39e04720b4adaa2cdf51d645288cb013d0661e2fee236cad1725950cdd5f91105e6ecbe623f4183813474b0ab349762d5214437b80a41258d102925643a4486020a6e0da06a94463b4a549e343d8f7657404762830e74315d9b357d73ac56debc536003c5c70e455936b4175cb58ab40a48d09ffdb924120eac301cadd2a61b6fd202280a0e4379dfa94cfcb412a2e0934b34e2657ef268b7baa0a0e31e4863d464b4030a7e84c411fb8ea666edd87e570be5040fd2c80b0d039a5159766ff13bdbd431cb2c3d2865835b5b68c8ef410dadf97f002bb45ce608aa737fbe288108e357af5452c04fca5a72035ebebd750e338454cf5e0600da7b4566cba2b6241ace172f172de44cd21ad15809a26e4ffabbd11c266c4314e4b5785933c336ab81775c7e7e69d363729108a96988878297e11a9681aa9e3804762f81c832d2d47e6065f64a2d6ad4781d35df9bba797412904147f27b6bc2d26228d3d8c0471f84ed6eb4c469d12e928a64476be46e60b027bac3cdf94dc410b0854a92ea1645c5c322c003469be87967eb1f6790b20b1f56b06d5c6d059da9c623b6ede2ab0617d1c6c2182943f2f0c1f2e4e3e1fd1388d566c5028c4bac0c7ac96981dbb0654347b671a71c0b964d78ef99fff580d149eae758b8465f83bd1b1b57d1f9a664fdc1b57e3820cb556704f47d16a05e01117f14c7c7c95d4de0e7c4c1846f8553a2668687427751130237a73341862b8a4cc2c4318cb617b8f1632620b9927ccbda3dfcb263bd7532a0add78cf293b0b4c31a651f048b2ec5113b90cda4d896005d0b573063497a7141d922c8b195c3b2c7764fd65a066912de629bca532b3a6b7666b05cb33f82dcc06f8ed6217b58d43d49dba08524e4d871315ac8cd19e99f21179a3c9374ad1bf7a82a19dbcaa95e8ec830265baae6e7e2ecfff539b06b25b1941d79c6f4a71a9c053aad471cd52c816d85b5b078c762476ee20ac54c60b63128b91b519442e7d6af2cbabc8c88e5a6b9fb831f83eb46e23cbab824c0ee97a6233dc80ec25774e08d2973afbb4559ce7bb5bf019a84426f3f6bc2770926937764093cce7c7c08d9e60983103849aea5df564a4d80a61ccc7499f3d2acc2c1259679047e3f8f4313835cfb735003c0da33adec1180aaa675d56e91e05f6ac71eea2057aa87cf5a808278ff121ee708aae6e10af2483c0688f62566cb1fca2928fe65968e719a969e052a9fa5f97d9a9dc2171e26f6726d8c9677c9157fcc5acdce1b176e3ed7dc31f0871fd4fc963c2b0dcb99f88e556fa2691348368ffe79387cbc754c1e8c20df5b44dc3687c890a81a90003a4757050651ba9240ee0890192b8477cf346194e657d7e96148ab30da76923266cc397f3cca22052fcb712f3c05c5faa8904e1d527cf568c7af6bf250964f0234221a9ee48f4eb742181c27121605033c9d1ec1421cd169613c6c2ebcfb384b750360fe7b6ff16ccb213225de9dac0a338c241a3360ae4eebfda45ce366f7f311c8b8417ddc57968b7c1ab25e54c3919756428d30816eadced4b55ca2b692e47193cb4b2707aa4999d95b14258fc8b8f13752ca10e5afa33c0caf02b7509ec5ea413ce34bf3c3c1a457c287421b43913538a57185c289c10c0e0a1e88a18083d9f3ef8682b6a1a6269ed2573a4b60c6e81f34a2175b37013d485c9fc559125a48c1461a5818dfdb15917ecd4cd285902f0171cee89399ea74aba28a8f60bef19b722793d1554d2750d3b755183be24d86f94645a3ca39e1aa08272a2f87e61bf4ff4cb631fa39968c66622d00abe11b39f07e5a4a26d9d0b02f779af9175e1bdce5e4ca3caee6897560bce9eed24b438a4b1e48166f783342dccb793551e5c72f248822663dddeec751bf6ec37018ac7630de31a193c7680250e28d912b87698e2b539c40f66ada6e4e76b205a21676bf297167324f3f9f847e557d5add7872b586c992ca50510787074476756a3499184450ad3f25c5ae2c18864720edf09446b70ec48e50ea9e51371420c80250100b806945bace3a25418542ff764f60c50acdd9b92eb3b23eba14b04138750da3b21cec9ec856593fc87f76d9dfb26ea8c43dc439cd613c607f1986aaeafcdaf2ebbac8a5337ac27d85bce0c713507034472cbb3715bf4afb09720fcc5edc96a491b6d6aa67dbb8e3bc7f9b8d75840a0c88b4aeeb6c8c1d946cdddfa6cbb53f32851d8c3a6841a590d4566de678ccc5510358c6c6289d6aa621dedb20e39c592356ce9d0ae0e00b6238885984f56002f6180e036cf67e399e68472dcb0370b61d9e1d119a2c12c9fe28e5398d55c923bbd6b90bda16efddd5f74b383fe5a41de84940a5ea33aab603f4ada77d211a6caee505afe59a39640f4981ad97e4fdc209a3c753594c14881c7c099c62ee3ebf0c27d716703ba6937b3c45afd9d95c42b0fa9faa27a7106a86c24d339cb9f23fc399bec7ca5e8002101db5b879e4f4997dcd3ab1a930a9f91411f08028e991e21a310a9405a972e8385d6c5744c891e6b30b80fbf0da81d7931d1900dfc3bbb3c59e714b8f615675c60718cbe2635f94aa601021035f67facdb2edabb6dec289e949d75a3ac77f6a6486e68f886c0cafd0a8d3b218f5ff985af4865b1ceaabb2ad7a93666d8c9030112ac80a82160b31d039849defbe00e00a0151a9986eae59ea2690ca95e8dcaceb5035ff8ee8cffbcee826a51d1ae169c73669a8b2443bb1e8bf9f8536ad98fde0ecd6c6a8bec989add078cd87bc42c832887a4b5cc47420ddcb27854ee318e63ec1f94496d2f4e371d17ed4e7301b9e7b8a86121d7b5a07e1ccdaa524e8a29075a66dcea1f329c7e49ed1e490bf4f4c229555d6dd3834499d1f6c242186f2b50b5bea640b5a646db33195d96e561aca512dff7f6e337d0dba016abe47197371a4492feb9dac0a1768c2433b8f379ee234144c9f7372f7a66f01623f6cf3131d30b2caea803cfe738aada0dae9012c362aa38a7bf160d0263abad0c01e59442259da29f9703a3619b8f16bb20f32ec2f001184c40abc6bfa311d758ce78bef664061c1cf2e9ef61010978725cef9aad4efe0c04ee9162b462b53ccb1d615897a1dc94e0fb6d2665c25ba4d9703094d0a943b879f984991d6540fd7912a95239a260fb44f0ee87c174a0b990c3f7057e7326ffe8d198400a9517676814bd82683da582f17716e1a69bc03aa9823103685935965680df9ab5e34dcdd143f628dbc77c434f33489dd086b62d965a0e0466eb72c42350c40406341f23284619cd4a2ed2ffc35f0b45a5c1b873f105096c6380f548eb20f31fec5d4483ab8924ca74ec12d505b11b0fffa05fa45c68e2326e796ecb171a7d128634e423bb2cc30c7062fb1ae19fe80caccff7d3efc9429bfc1ff529fc3e109bd4a9ff2521ef0b6c2187370a8fa4c1467bd1945d3b6ba99361f4542313952d0215b956d3d3e2a5049e74b5658cbb6d803f844043ead9b64d88f781580986a39442d04677725050d2a2d0815ed2d02e218128402f131d955bdc4540153cc323c422418c5c5a3da1bbab90510ea13dd31c31c20bdef1d8ce025b8a3983338d383d80d21b8ac936279948319bcb2e40a408dc87650a33028af4d48c19c879352224a2357d03287c5014520334428d6a710161165eaa0bda62a7a0d5dc4e2cb85849f43d5da8b0bdce38008b7643833da42483217da900ef5f725c4ec36974b8b17248429d07f839aca8f07e13f4457117aec65f6f98f4f86c02110d21a0cd011c4f5e6d0ef2789ac179837c0a9bc0a6b3040cdde9c96041ce6621005ca1d044295e920eebea95f763a125b0a8b739cc8c486f8aa5f37d08276afb70b6f4f8e9c1e8e6aff0eb7462583cb73dc7e12b81384eb64ad1167e530679d80c5ae4cc3c000ddb72b06e8f8bf7d444351f424a1e2c8e55bb7581f1531ee8490e00ecc2fc8c21d73aec5cc2d8dc36d7541d77afb1331a011bb067e05c2dc6f4c5911b5096d78e89824d5e900ded089d7bab98836d17ae9868c97ec26bd1a1e0168446f0707ec3a971f295a4785b9813380b26031578b0c4526230a528cc2a00ab88c12ae2308bb86157d804f0f2343ddc568d86db619fe995c98f7e2b56d566d06471db314b322ffc2c1250b674275029bdf3d6a0559bc44d9f7b520bf4265e60f926ff5ee443465e9bfb7c9adb85280878058866f0db2211601421c7dc618f8ac850c03186b3053132ffcbf4b3a4c56af55a749841ad2781ebe923b10047303ebb6f7f6624a5bc7b2decec5a694a2d506b363e64be7f5f37a12175031128d5af3c50d19b4fc8a5419b396f7652757837c40732f49a0cdd4240beecf35988c930b81e2fa6579a9fbb4de7ebeec5975927a9599fe1e4272f0d830adab971fa0dcec6acd6609ac62be4f95b273af52e8b0e527b7e7e43940c84ed62be4326b1586eee2cedb9ad0b76c8377e8191500931d32b03825c77eda9af8b4e00fcb686095a609ce6822573b8571aea2fc8fa5e8757687acc399cd17dbdeeb77eaf3b579af1fce55a40b1ac680278d0a3f4bdd2ca21ad57482d971d7f13a07a091a937f5e64eacee9e17767dd49c9eb71d1784ee0abd4d88fd6409e5cbe14af4fb7a74de0f7029021e15f569464bf7fab9880741a87cb70be5971a46c6f41ed13032f8962736a76ce6035e6dcf5545a42c39dadcf7024f090faabc44b1895b3f4087986798ef23ee0eb4de089d2cf7dffd5247474130b597645c2363eb6a102177bcad65d6222f81084c8b9745f33122f388717694d0aa6c976d1eb296b72837b64315b192f0e2b503ec738302e6e945a935d67eb5cd82b9dacdc736410a15c058901566b4366e2b1cc5e6df51da5b210c3028259375127716163ccc2b6baeca0643216932fd473c1371d1518ec34beb0b374a7f82d7e00339e2abe7000f7aa651d8d9e3304b87c82bce2d64730b242ae0efb816a61a7cf7de0fd38d51e2714de543f034161ebb353e9c1b2d64d3378688ceb49c9475be861ce3c0413a5f77a88b62bf6634c9aaf0fb6ab3a850d3050741ac07cf369d05660dbba4fa30b4dda321d0897bd1df0a252e0cc6b71a1c5e68e880d411e0ccbd5c9dab6873618471d0caf7dc94811a41830f404f1a0cd70221de8350881402e769087a7cc262ae82144a021cb11dfc4cf97e4b8d04107ee50934165cc871aa0533115ef301e289ae5829e30b658bacdf8444fe3ce905cb7b11a895f2062145177b059a15d344ec7e226f7bf442d3fb8e00df091477d8193023cb618ed1b926d3a2a3b5ec361528cd4b890a1ce85c8793ce460fb6227574587a2bd764fcd0be7ba0670a8428b5b2c6a7c54402393cdc97a54a7a41f7445f76e4b6303783dc5e6e4a96eef88898a8e3a6b8187033c9b56dd764444c8bda58a6c05ebe6655de634d540ea1e6f3562b2be8037c67c110c2fae1254989681a8056f1dea894d3e5d67205801864ae84f93adf79cbd663c036f3653bb269571a192fb9edbaf0209b63b98b7a63a00749de02204baf65ff84d59eb846b89d9e5ac4ddec7fdffe7505b3191cdd5b78169c416de7f4fa8ad836e27a6515bbf25376bfc2ba5ba1f92676d2420df0936b80401e98b2ee3bc202fa29f1876427b49d6e8c93c8bc68431ad6262712a6a3c7108e4f754711859103a8100071670090f69f3a3d05a216964db1f772dcba9f879f618a19e1e01526b7cf3868a8f91eb666b2bc4f729bebd3b1511b734f5858698653e05c80ecd9e11f23b2ff434d8b5c7c7315a26bf4a8c112a3aa2ad57178493143da74cd95af2f1150661ca330ed0e8166390760ccb415df9290f5623f74f76d90a84da1623527e723813f9c3195174436d4b7ccacf8bab74128d328a877af480a1c171a67971f5858097f983da71acacba030c6f8396a6d8a52b63df139f26e014d109563b9362077251db49ee549185fb43cd90a48f60e5cfa166bc08c07f07073fb375e492a578b8aabec79a282a38b1a791bc7a26a1c6fd04d0d525294250eeabc112f98d7c0579c832fc8a6a9a0397d239bc45e68a29cf80a3face8f6b4baed22507ed6aad68322ebe3c163ee2ae32ace84909b6c8c94319e92af574bc3be9d84371c543bd5e306429c0a1cef03d91daba8007204ab7032339672d10c8fd412609ea86f419cf1386b752933bcd1ba0745f497bc91922ca56fc6bce88b4af41ea54f40ba1983c4bddc8c6457f2a16e1c43bea86f36c5f9a371bc585d786dee488a5f986fba236ce066dd6980a3a691574f43b51d00476028a87724aed051c710f3a5bb7c8d8fe8dff51c7e225a924f324a12b18d4aae5d5c9974cc47e6fadc184b439a0cf754ad2d01427c04b4d66ebcda636b6d2602c16d5ae266839621efe0ac0d0e377d109b4f75f88e5428ec46d58739d04dc1cec1b82dcf8f18df90473ae93fa67563e7ce9f71d100ae7a8168fd44a4cb9212493e9af9354772cf828a175fdd143fc358d3fe6a6af0b8eb42f91c06654cfa21ac2b896ed1fb03a5efcd1d9115722ebf4590df3497ae85fe066a01e202a91ddce018c639ddc77798b2e7e0cab6303060c705b8e824033af98e70064929a2b39a7d554caea3ab8d9990dc345810f24c1c2a9353e8de10f3f5465f60c7e98e8b77cb88f8eb32f8c782c757e4d0eab89d09f9971a72d383954327c83bb3e09b6db670c31e3c7bc063368ed9e75dd957f3c8d0dd12fdc30c1193ecebe95758af43ab1a17e149dc48fd669b001f4d63a95754878ebb54ea867a1c26f607249155720e50b68d8afb432b020dcde4bc00a39f441feabeed21552d31d80977431a89977f62eaf07d68052cef71adb0153d0ee4e3c7b066b14f51d7f9ebb932e6791be36307bd99a6aee847e8a678b8eee6443b003437594f0403cdf995ccc6f07561b957a7f9d5fd9e5a17a405edd69cf43b4d8c067a68d94c9ef4811c8ee0c42a49c3c42758509fb37c278da8a30376224ac3b19e028eac9401615977a101630a57af3c3ab3b39152b3e1f4266806ee85454a0b5d93e8efd3a893937281d47d257e5b9f41bfbcad5ee72645ac48b8f3c45afdd38771c3a075a2f7f820f774cc322ef3bffb6f6b8132c93a4701bc0514822e644cfac0880121d4be8cf9def88400c7fa9a2eeb149e0c43a14496a417af03749aa04c68ac2fc621a5f51249b0b4aa2a4da164b4aaaf013b74a85354aebe1f1c41b38cc55b6733bd0f5d6a4f1068e8aed21b47a8ec14c8de92ff07170c89efcef0dae479ccc58534f85050d542b671237327722a615aeb021f74ca879444f5c1115d144a59ae5940b2720fa78adcb7c03cc36800b632b8c34568d20bb267e80ac2644e2e895cabe309bf38d14d885aefb9a642398bec6e14310f76d5842375405853509ddb9e141ea76be3f41e9935a397746714941b42a5594cf340251ec18d6d450f7634114578ab64b81eec2755d915c57976e85eb0f9b5d81b66a7b13672ea4dc0a7ffab7c2273fb3181579f19c8fc97b86e00a2771e2388962f6faf489693ca308707571c599395653851c8bb149b53eaec1d3816f85a3bef2eaea1ad3e64a29d19d4da4eff7ebe4959c08390d72070abf39f64424baf3c18c2cab857ddc97d5976f2723d6cc47e76c1a4851b95b08b39fa761ca8eaa500477da423d83d32ec0f1139f8f02503e91e2c28cf5a402aeb24dab7f81d9bb14e6d4fbaf2932b1708c5a74a9cca096e6ea8382ae148006ca6dca2e1bcd1a4392402263b9ec368deac5a800e3ce4188560ce8c319316c5c620b8af92a48d309e76b37c586da4d512397cbcad6efcb4f60784d3b892e04ae6b0df341322b08240546a5666fb945bfe27e715a1262161503fa58e2c90d1065595402178095aa8f633352dfab604827d962792e4ab93f71dfc58dc138790d1840d7a237b7c69aabe9477043c6d6b78e9c00b6e96eaf248e75de27a0b740925f5c751fc4ca157a07522f02a1610bdd1abacb0969f5dfe6b852403e88b73752bd5255ef65bd560f3d523d9f6f3dd311c47a71a1e249677b1f60b0b390af6609c5c3e38b87e812b5050528c568b391c8ac0b35cef574a1eb2c3f516958344da74e3273b2c5565ce42cf3cfd4a2fa21ff8b6fd3053b93ab8103c37e10959fc5ecb9a226cedfcfffeff2a672f011085093a6584406f137d2a56a7afb82f58ebb9a80326009ce4fc7cd290c1dce9c65d1472482561141e3a6edb79729852f86fba50fb098016852d662c832941ffb426bcf01c95714726d94f828d216c5dfca135e085e80f6db71f30f693e4bf12d2d478c76bfdc91c9495c10aadd0ea7b97349030da502e8ee0d32696c2078536c2e5a5adecfafeece7249dac9d5e93c6822f9b98d99ed223e157693d810350e531e3587298cd20194a2ce11e5a36afe9c88b787abba1e30e7d9904ac6f083ef91ff881d5129c2aa0f1717ccd6f17b7f1b0e56c10cb28bdc7514ee9cd268cfc9ff2293ad799f73196e2fa7d315a5f065a005df07dc503c9369a7ab25a13d8cce431ffdf633751ee96cde57ba2ddc7d6955f01311d46e39c971c1d2d349485ff90dbf691094506eab0b7e7251d0063994a8726f14dd013d6856b8cc5a3df1f27dfeef56e04eb31a9ff0021a0f19ed808cffac7c46638cb2e07238fc329b83a0835ddda3d76421d8911ff055082a52669dadf00ff4c140e8f1cc31f5dd567a61f4c6bfbcd6248387e3bcdc8eb8f63238be3bed2673bc2605ef9074bc9f4e608cb7a30c666bd70a32e5b595e6176dd5a1d5e42bb2a58169b5bd537d600f923e3406928f7074081a5870a45285a3fc678536db9f6809580fea48f71e7552fce6f3a1299efbe91e5d3347670c1f36a88c50d5a7aa62a918cbf3b5c8103e3d0e737f83cc8d85ce7c1a36ae7713bed62dbfe852c20d74a620ae09248f189ad5ea6487e15d842992babe5c8196a88448cd7250ef2450ba1341b566580e44a50315a75427de60578adcce6a73eb15d8da1bd0122bd9f39fe4cd4f0ab2efac94a38118c12ec758d6f3e4ed998c63b32cabc346185a64925b44d63b446632b977c19d6a8ab561fbc122cd5ce309d6f4aeabff132d69474312ff4f502de0f034e5034e3e156553fa9531399cc74f6c1c2467fc20c1c4a7a9f8725a9681d3f4dfc851b0194b45d5561542c168091ea0643167fdb88a0f391bbd9654ff27171750fb914caa7fbd020ba8be7df640a691f8470884cf41939012d43b8f2371c7129fdd65de40fd554164f646b564cdac9318ab8603fa6ea73c45377fe2555e1fdb0502410d87ce511113194c159dd9329a82424d4a41ac3fc01efb5d2d8013c4ae706251e60f4bdf57c4035cd61b8f3c0509c4250ea7f47007beb8efa2e8a8a2b57e83fcb59a880042b538df4d42c58005d263a14c80ea5e1a1e0f2181ec10710f3853f9d7c998731ede05188bdc9d23979c86905204230078547930fa01d2511ecffd16e8098c33a4297247dc0bc21d18b32ad6fef2ea042a98dd33eb6cdc9950901eb08f048a9734cbbbdee6eb8db22514de24195f5ade531cb69b5a0e8d003386910d06e07bb280353a392f185345b32b359fafcadfc5847a9a9ca4827137812f44f7cdc41cc7743578c22917dbfa11217df0a8986fcd44d9a1962630dcee10e2fa1012fd0e80715869e91cb35be814be2a8452f60e9908334b11cf096e9ba3373d9944f9063091f64b39a72a418998ab4318d823c9390bfa2f6f7c89eedd710885c2537eb7a76ee37a76435c9ba21447aa5aa56cbd030104f49f952e11242ad8a1fdbc4093dc0e418d26bc520433c6fd33da3888c9479763930efe028707ad342e66db5c4e06cccf4db8d232a65edd6cd66a658b024de4a90f5114e72a5868462c6169b2339854e7018c5b746d660cc03eb8836edda2d2e033c9d3bcfd0e63697fbbd6f1d10d66dab557abf4bae92a4994ddc25569aead3b850bdbf3cf0f40f1330fb95d9c6fe9a5122748583137a0ad96e2bd07f1e5d063f6722f1f8dce184e49e96f0b636aaa1141d0245c3c46bcb008082e2e596085f9633253ecbcc58219ba5c48062ae9a17bae4ca7fefd71efa07e0d6d97ebbc55701a10afee21739bf45f2d8cbe8dc8cf4ad0bf253442ae7b6bacd2086fcdebb5bbf8c0aeb46f5d4a6dbdc06d6dd17f47b9e0bcc2e148d095815482c74b3f45802d7310f14a46412c4296b993fff54c8f1ec7437b25860415ce654deff8383709e253daf61481eb16f984ad04bbc35784dde19521e686db8efcb17c2c31604f014aba30ff1547c0a28cb1d9a9cbfd17b36ad2ca071bc4aeb6afd831708bb0223159441759354c42224bdbf4102eb7991ed3bc1c0102c00a1a0ace92240557cf82f320418b65cfbe47307fe338bd8a2dbafb149ab93ba95732f577565bacf15ccbb17a482cbadf9b5b8c9edcde71c232f531f00d6e14b13fa6739cbe52433969c790d65e48950f783240a61b7a6bdfe25a89093e889f99d00d86eeff554fa046a220a2642033841d573bf4e4a8542fa8c2f8cb9d4845a0fc1767beafd19f7eef684996d82a683a37dc3f9dc4768e1f606cb34e7e8ce46f8c100d8e8c0c11900064de651e3290f054326d4af5c1fd04fc2c1ae2330b135c91a2abaad95ad8671a38447880220a4892608195970b060897d8a00321ea2df894f20da0ab587623269239b43db32104569836af0024b5068453d18b866fb33fa862263331ce472d190022d4cd55af0b957d4e84d1d74c503541fb4e680dc3666d7eadb7c5ce3405c9c97716b15a509f4766ff1a2681955c0f95076e8915c138050ddaecccf3ae39e70ec9349580470369ea2abb0834861f67edcfadb68d40e05645c9815eb483c349094cbb2dec663ada820f0a204a504b036de6dd66a8fe325084d87759d185c2fc355a4c6f7d4e148093a11668edb01b34733faac3be3f8938928076e9daa2e996039b0679e989018afa0a73f14c9635e4c57d6de821e73f5d7d9f39b1077badec7f2e13e5eccc8fbf8d933467aabe87aa0515e1b049a7935a10b9dd44ba82ac7f4bf8b02fea7f8eeb0f3ae928c3a98bd2d17b3a9acb7f129894d643e177a6b9b0811b90904b02e118fedc775a49bd433639705ded929ef3c6f062943d3dcbbd8085df5ca977ff76e3a85d770712b9ceb151d973c7f64a66f597a396f778a85e92b39152b9fd4388dda10c9837649c59243b90b367569e16c669027983cc8663ae3cd1f4aec3ef96a1b0071d740ac49b5080656e7f7025fc29ffc04c989d722969dcf17571173c7f2bf5f61b763c1e422277bcae3a7752f6a202297c9550f0c1a610aefa1ecb79b687fd13c4fa1139e7133ef2277c45a71b07e1a9870de21fc41e0c8bcad4fbc3037c0450b81bbc46d5164f1a945ccaeefe8a3f2aa7baa6984628a5e92839f471db753e5bcb2d9db33fa08a4bd08768c8af2073c1baf44356f8a5888dd5bf37b9ed60c1a433acf3d05f353fd19b1ccb5ba6e2c4357664ad298b426e257dc0891bb9233dc410f7808b607716a1d67b44c3d985dc72f881409d8bd40f8bb2c2a8dc431f7a14b42bb78bf507474c24a8af079012217f3b10877431b2d6650504695d8f379185d415e7441d1e8ef8f3a988040aaa7dfc918e893e7b8e7e26a0c375257a1f33c637b790ffc97b42cfc97bff8b150660ea03963cf96bc52959618882070f62b3ce78ea2102fc666905c330327a74220587faf070e30ca6db2000676be276a591772cfcff04ed0303d93c11b902aed254a4ccfd4aa9de2f804b53707b458593c7f0fae2951c1210f7a1406fcb2b575cc7df9168c725ee51e225092df283808c94916aaf8608fc567f7629c073bf952da13a55e5b3ea3fa4bbda4db250e570c5dca1fd565df52f8845a3d9c09ee9af92831046c06efe4cbea47848c20b662b4837ba14eb4c622bce19ecc4b468762972bd0dc11db017a2d0c5b0916d0e67845dc845573832bd770c20585ffdc8c0e232ea21305ff5d085a8a3e075e326b078c86905dd6ba7c7711ece99de3cd31159c40108f88cc1f954433871db39cbef0c4438ac198251a6628c07b9847a97130c8522fb33f685bf3cab31951206c7a7fafc34c3b9b8a4b9922aa012138d1d2ba67b1572b8a45ed0bf523211b2e6a16723ac6785f66a5cd1891278560d2df4ac379ebaa50c54cf088f38f7ec8438be47049280a27d70d627e8b1652a6c4baa9b98bc17c6253758e0c270e617f30862075d0de2f80e7a5671a2911268c47a6ecf0b45e852029392e9486d7b5c3b67ee59d46f32769ca6d3573b15082d0c8b00a654d3f17b00ede2f09c5c2cf8954b5a33810d178f06a5297a80d4b9b1c08ea2474c049fc54a65330231c38490a64a45a272305181faed90bc34c996969b26284d7d988757c7c84df9818fc9b7dbf7899f7b17700a666b872cdc5742242998042fbfc41192720a712046226dd9c9d1f8faeff6888a318a739476be103083bcdcaa834209275c91cfab8e2e9be923decb9f855f6e4b7c737357b3e0708495d5878232baf49ff0b152b6221a0c692c0ba4cc83d0134a1eeab13d45ef1f4b47a844bad34358270eada079c8cac86f5907209a813a563976a966624c1793599607d59a5df1c2e5564733b8fa9cbb6d85ed64d40cb5be96267a13b3cf89c07937a36d5581ed383153fd4279152bceafdfb622bf9870eb2293ae1cf2b78c0f6370925588bcb9eceedf723f4def20338440507f6021d6660e76929b92a3a41ab35d77fb1d71e7c02a4ad0742cdba6e97221a549f45e2ae0137095b6aac3e7c682965ab43823eab46be25b966c80f7eac5f19f1d798aab552915d0ee3279811349c4291c20d812b2a9ade4de0cda81be4cae3a14a06e1ae42e026599c890c69533f25fd1b9ea3ace255527a8520e530833c1b3c3626f546a12960e725580f6c218337b05682aa3e5c10a91eb1826559cae0a2cf034833ba65fe15902574d605284086d33b0b228ad667718eb9f18f7ae419d756034cb271fdf532ba7864974c5c67e895d9cc20efe65244050e75dc8ca56f2ca6c23a4ea5d297e1e5240d25bc8a78c0b0a5fbea97f6a52c157dc2dfcb3475bc895f7176f09724813cfa9025b96ccc26b8708b848df956862f82d5c1201d005afe14a86fc7ec2d433183abe78c0661d6f09ce610315e6185e3e93fe325d2a3456a4e033c227304ba53875c5aba4e80c33749d7c9084fd57b8722a7962e8e1398027621bf78da48b9aeed2fa909351514a69ba27e54058a772a13d11564a75c155a7dafbe59c5c744e018842ecc14c915f2ccca37bfcc44d0c860e222af4d02baf25920b9d5f68fb7b8885b1308906499b74303173ecb06873ef85d3ea9927c83ab2bd2b74390833d3d962d5eef029c173856786bb86695c6fe76486a6e043ab152f1063d5d1d2044f82c799338b3a2317f0d6340d33a93eb4758968344968888098d3100c874e3a256d79f1c32697ba8c8e891538b3b93f27195db08e2cba1daa723a15f40e300632862d9addaa990432f9ae19ddf4a098c87bc8e6f88cf28202ede0a6aa48f509059ad300100de2061afda4409936302961d889c5116e61790a9a5270c52d34e36928d6a0e557e5d9b7444e89ef0d5a11616a3d14351383edc749120eedd4bd3d56dd1ee17a119cdaac4470d2a858106ac7b31b791fb3c6a336826c41ec9d31269631b44253add6238608eddec4f289f462ec37cb7e4efc08d87498f7a9f547d9d24d49d3a8b463458230ee7a33d372bd8443db5d00d91120b276a44d482b92ece453e45385cdbe32ffd9959c1f58209cae39140edacf4efe9078d06611f285d4569241a6ba21f07c27cc29b6824a61287685902d81cd477b944f0e49f30cca08262fa74f1baf4d49375cdd0b8b3ada042c43f7bd0fd5ec1146d73e5b9cd78ff07a639c38bbff8a5c0ff37ce9ea1c4616a52f3008d2f50e6cfcc2e7309359a475d5fa88eb7ade20bb7467bbfe7344b494be58ec95396f1b5fd752e0a69d1657a83bd5a83f9dfe3b588dd8d4e6d9f8d237c9f0689eb3c5e5d0d48865e46c2a18e118218ab03ea6c2d313c9c75c67e8c49e8af8b4f25afb12d7bd2b3a1fa646623cee44372cdcbd0ace5f061db6b8099abb3c055afcd903720b362bbb0c96bced173ad7e13305315b44ee0b53d61c9512067c725cc58ba7a6ae3cf1b0b5969963f0859d5d5d3b6fd0a7f97e9a2c33da6b7cdf3da32fe6f8fcb6b221339c97c07cf4e421cede48a0224634175dc3a11ce449cbab1217f6b7ad690d4984fae28ce1130f167e2ebdf9f69c0416195b224fc810ab94a29fac3399fe0067c47c7329b1316ab92258e71c9c078ebdc8b41ca02518d79793d0394d85a5a6cc231312466c1a15aa17a0a4c1f7de0b2708e752d6401a555f55ea4c1298147d5c753f7fe56d3fd1a86476d182fbaa51aad114ce6c7f4b0e2df8d8484e9ad042cda153960bcf910c1ee50f74f3c0d5dc0b20bbf382ef50e1c9c2d3e48acbbbe6d5808d108f92f31e79b66d0d08248c6952810b4ad1007d51270a919270a801d3f73ce6f0786190b6255a5f34d0110948bdbc6fe5cc6ba72fe1343d26de315c9604459af5d0e7a51f00b7c151a8a6eff22397ba569229e6894bc9fc5a1ae644c7ad4ddafb77a3cf8bbb63de667e96b7a7d270c043216b76c28a560e949c7059b2b41964def3274581e1513dae82258eb37374373bcc5414e473704c54a7273775f005195f4d63436c1b70d189bdf622105eccc5418ff3222ce4dc70fb5ede7db4d2eec4a2cc743de6a7b6f090e8368c21803ee5bf749f8531380e3e6877c6a938861ac6eb4446f5894c4e0013d25276c26850a129f043499af14bdf1e48d278f120e8d061c1233055e6c883988ac12fa3615b9e056ffcf23ce19898ac71c76455149f383f561ac12efff253140531bf11a5835486f303278fc19344e1f8e1ab6d249faa04376d515ba5f770510fefa504674771695065e7ce96f39835c9b3001528fd9ce42fb355f2d9aef0900b387cae5ef2435e286009700d5c2e660fd962c8e11b3946b27bf1e9f4eff08e85369663b4a1fa2ee1fe92d2c07145dbb8109cb02d698ec910a7f6e928ab96b1bd2a8c58ee1e3dac7d47ed59774dc67098770e6f17f463c23711b88a1f7bf5876c6e8c471f7ec3321de5e68d79e5a188704d76ca92d0e18f2d8acea666a2c7ce94253a14a2c95ce8e96a34a30e95d3c8b750145abf438cc6a14a5188341615068e82701a13211135c7e434c67c83625c97e1a56ba0335c4330c8120b665a81dc05240a386a74db2d391ca01380318ab70f86b131519335ab160fd8f22d6386164fc833c60f1c8f0d1ad734035dea0fbf9abb26255566a614acc0bf2319f9f3ea32ebf86a40682a6ede8d98f8c01a61e47eb5a1504fba73223e6f84ee67b1dadf516dd25e6060b310a0339168f09af875b6556516418869650fb49838f0a72e0c9c6e454db16cbd865ce7a29a6a14a0c48a55090d9cf8365e964f5183afb71a9dfa20420ee7fed997063fc49a250dcf920a3a098db1a327725a78cea57e678bf62c560c9f2aa667f1b7f349e975e3843ee4041a7df2499276a30628af90ad5e1270bf9d96169d1e605301f4d30d93f52a50bc5248706f3833bfb81baf9ef0140acddce9a874546c495202c90630c86a1cb02bb89fdd29926ddd2878d282d4700d022fc2f9f2dd610db1f2917f24f2c80fc1031bc9ded7182ce5e38fa0445c8b828bc907c9088dd53691f686634e50a00f00bb9b9e459cbd1028c536f8bdd9148534c6c6d1d372b9df60ab7a969b0ae5b82ad967a4d22d29a568b4ed407c6a4e3add36dd2da774bccbe43e20c452612f898e813bca91f51b22730fc581a3c4ad99f829fce2c8ba7691f5926f6a14733d10d9bc809e9a0c14324e9c4693b85de9aa726fc50108b3581731d013881b5499068ee906a1183b1020b6a60323e301742240394f24f4c076109e6c225147a28fdfb63d4d76c71f7d22db9a3c5ddcb3d99e87844b73e2a2cdad6199fd16c67edb52a670169116711aea70e3f5dc1eb7dd70df993db9ed3a8ade7df48ca64596e7bed3616ae42b1f6e95dd68363b77ebc99d83bb25770e1465eb01e46e38035cdd9aaac2540e55d322da94ffc7f25e15d47d70a76cbbc26ec85531cdb6796a724124ba571e14a0a2c00f054528d060b662d52a4eab216fb6ca6f3e2a0e59bd3b2b0258c1bdc9ea1f6ff77455a2556a7e958d9e22d7b45df9b0cd335b55beb9598e6606363606238017c4b7deb21acea84a4dd7aa8ae6aa8d7c5515a6aabb6fbc5497525d4965232e95867f93f584862acebf89c75754475426fee5da53ac4c55f3f97f7eaac913f9a9b5531cdedcc63605fbea787b3595e7d7b4ced4d1d49a29afff2f45a8540b66aa9c6df5fa66969a2bd5c024f588549c374935e1445274a4f2ff04accc0914fd3faa0651954f206602b809be463d4237918f6af2ffe628ba515b28ead29ba928336f8a6a459d08e5e9df044baac723d7624475f7dd75a83f7166a19a84b2f11faac1b6d3a03869bc20aa72504d4c50638faa5c0258ffa6047c246822019d045fffff09d67f4af4a909aa3f7dfdbf2756ff789b0fd05313d6538c08489911a8f9efba18411e30021b66274fbdf361272a9d74fc739ac5290e8ebc8b6dcae9680e819cd6fe337013abff38550fd34d7cfe3751f93757645ddd9a0e37d9784db0d6d46854b5a6479ab680b5ac3eaefc0ba809eebfd52830132bde02333d32d17567de536026336fa29bdd2c47eb68dacad33a3434d570c6ddb2c5adc8ba7db6fb56b59c1abad58dea3654e51c0edd281bbae1d0cda2972ef5f4529eff37f97a89eeff5fa857249af2bb4b63739658fdbfc9f24c96596e49cdbfe965892eaddb96c686793bf734674b0dccbc8b739504fd7f9b2b9dffbb1da66ccb2be59eae404a829ead5b53232531bf724a474a3526917ab07627bf36cbd1e626f1f99f545cb7ce6bca8f394f8a51b7a67c9d9494c4e75f5293779364a338d49b9514294e3adc2d31d2954831de1ca6755cd5b9ef48dfee646ef90ae9d19bc916d20290ceadeadee6edf56bee51dba39f23508ef8fc9b68aa8feb0accbf3d4c5947261a496ad4a4d157a35e52b5e35ccdbcddc2dca258395e4eea9eae6513e5914de36e5334080a80002ec90816a3358cee189158e468511cae0e397f3fa2bae5ce2067ef99b7fd776af6a8723617dbb3453e16e1075c3dd0e6011f0ff47227f29c6cd38c6e231f1e91765d1b73924daa2a2fe229621579487428119b4846a20b8818fd9bc654abb9efb0deb7aaeabb146964e7a4aac78279da9822da8188c3a15066de45bc43560ec138e4c0102243584c30bcdcf262ee28386f7f67521f3b32c3191a61a8f79f54db75afdac8ee338b990ee071608d030238d0fb67f9902db33c6579deaa962bc4a690903737aa8d762c5f0b3120e488909a3783787b3db398d939de5e09ed2054f66716431b4469d097ff9d55cee1066d2024680141a77a241279cbef52deaaecbee31c4f4e50d9c0010d0869a046031fe89209b65996af38a6ab3682115375e7bc857af8d5d3acd9f2ef4cb6695dda751ba84ca01b811800d203e8f86fee3add9148f4cc62783c178769d61a0e50095b7f2e9968baa67639b9ea2367d3fe40f913e6c7023f86fc6cf123a38facffcf2bd7a9caea9df1f89ac5cccdf2a15dd7ed99d5b45d916752ddad31ed29ba6a3613dd3ae7e936b3a3a179bacd64f7319b288743a27bb37cc872e266f35633cb73ddba893cab9cc3b56bd747bc76d769aedab11ccf98b61ccdc5a1de79c5cb762cc70343d7dd57e417da998969cec8365dd31cb655dd79db85c8bd6a55ddc39e1a91ad6e7b72a8ebb68aa68d765db75fb9b71cd5762a879be36e6c7acc53e36e68aaea1eaa9ab672edba8dc0ced3f81cbaf200b6dc6d1bd3b9ba95b33e5ae41baa723994dd776f7dab5a8ebbad5a3ddab1dc477b44b22c1a9af21e796a4995d56abbd53a389f6bf95d0ac7ee15efade56e2b4febae1d2f0e4a3e0e5db571a7ad6fc1e11eaefa0ed51b45b2fcc8d963d04e450acd36cbd182472352184c6a75c70555bdd11455790f22a0094dd115aaaab51adcaaeee8ba5b2457d5dd1110f22aefbb381764f5d47a13ec2baf69712ea8eab1f3ffbaaf704379b06f960ff59c794d51bd75e496a833cbd1b29d8a3ccb449e87297b0ca2ebe6c4956dab5ace0c4de3569ec67138b316c993fab85b7d97b2c7bea6720ea7dbbeb2ed9dd4682de771783aee170cbabdb1c9c5a9f508aae100c6bdcdc905833f20caf6ff15deeb80b4cf3f215785d9ed4d4e8077e1954750674d761fd55d0c06c5f09bdb9be836924d3b96e365dee62299b7aa4ed9dd1ff9047c171f2d37ffb8ed1febf8c739feb100ff98ed1faffd631c0d3eb859e471f3221ecbf1087a138f1a3c50fef1bce1d1f066b14d0a81772c3d104551208aa26d160987ee3be79d18ac1d582ae76d98aada79d4a4a3d8ea9a02ed8cb13ca37bce4e83645e26aabb6714d5b98e02755b4def52f6987b9bea61df1aadd8f59aaa6ab43c7e663657467c3b22ed38d761f6fbcdd13944e74867ee59649bdc731ccd49d2bfda751b93eb11b5f79213480e52ce8a38a9e2c88973c6710027d1a9eacdf2b5ebb3f3cc44ae8da93e8f69b1ab9b4d87bc779db3307774b3c81649cc5b4c1522e1e0dc80e3e08da037c53765ff6fa23b27c1cef3abb369abc1d0af36d300040c2862808d37b72ad4ebde6eaab8296bb3aa8d016d8ab4f1c0a6111b396ce4d8dc586381d71a465d17378b3c9e70dc2dcdc1366f9f59ac6bbc62f1cc6270d9e4ad3ef236b36716d36b9a5c8179b3c86346f78aa78fbdc7ad515b73d5e431577eb2bbb7a9aaeae1302ecda83482d2a85cef95a62e8d85343d3476d0fc4033e30ca9335fce24cffccca0fa1549cc6bdac37658eee9baf779c3e4ec3e821e7352ab6367de1d89d7766411244fe673b328903cb9f2c826d8591cb65af8fb9d611a59970a8dbaa7e899c5ccbcad12799bf7be4b7b4f8dec3eb69d1b55bd8b6d163bcface3cc3313799be6ddae9ad5c8ba62df2c31ebbce2e9a39c19a3990b65229541e455c64f99e2ff9b6226baea2c977fe799c54c22cf445517b730af3c6b55ff3a9c799efd54553d3cc97822b3c58bcc103263ff295270608a14d9dcaa704d5b628ac78b39ecdc2cf278e6a49007e582665f795c30dbc8e5720fd6a545369b678c65b33c03ddc58e3ce6b623058801e32c76bd76e6b0d3048341f695cfac7351af7927b5ba51b83cdd66aa756bda891ceccbcc2a328b1667b6d327cbdba1be11d691c5b96c8a6df698f5796622578f99450b14d599ddc7aecfcce4b63bc1be887c1f49fb997b9a358a6a961393c3e4b6fbca467517c336ab733feab63cce1e89ea96e36ee8ca63ab42bdf268b95b090d5efe0aff7f7daf2f27589863621dddc1607045f2a8a2dcb88fc976b7ba6eab1bddc7e4dcca893b2d99c759b862d745e0461a495724fafbcd792902bdc4f8fff7a2e13c59fdffaf6f5d1e1d351eb04b9e0776b1024437bae3b6aaa639f0b61bba5b8dca7569c0251477e3b2c5a8b9acbd39145bf99abb2e6e341da648b92da4d89e8a9dbdd7962b5be0fedf64793e37cbd154550fd5b67869e1c31e39bb8f88ad735aa0fca3bb6d53a310a9e5d4e265eebd72024efdcba99925b79d5972e7281bee1ec99d830022077b2f02e8dad4b83fbc170163ff6f6e782f021a64b993e58b9964f7dd4633579eb79167299205c38df7c2f2e85f44dd9a8af1b56bee669e9da3781b0b112c5e8cfedfdcad6691c3e1c6cbec4657a410d99199b799cddb474e9122450ab31b77abaa698e2b765750588164258c15b52aacde5455a089447771d8336f598eb7533427b5f1dc72ebdb993b8a3413d5c515b9ee30bbe1de604956afa9f02bef3a6d4c377b84aba2a58a1c955a54de50514365edbfe53c2ef7cda6aaaa87e7eec8ac6a23cb5774dd46231f6eb6e56ed9ece99a87fbd88f469db793ed26eade22f9b6d3fd87c38dcd2d0ee8c36477ab5574a7aaf03c75e6ed11b3f79a32eb77317714edba8d2b67d33ba171f70e146e1f7270315ec8c9b1bbb5d3c68db23cafa95077bde3b2996cd97de4ec318fb811e345ef23369b63bc9093cbe8dea858bb7164619ab9edf8dd9ae6550f75ab512237c1dacea6c48cee15c91e73db37aaf2af7cdcdbce98dbb2d9b530fffa899ccd731bb9dc99cdbcf2b4786e36b77ccdc434837dddc8e6c97b4691ecb98d7a3dcfcdf2562e9b6de76a5d5dcab63c6faf7c78ae1c2f13d3132ccc6cae8ce537bece5dec9bc889dcd8d3551787db38d46cbb53b6cd7dd7ad759add775b4dd5a1585bac4bd5dc8769db53550596f5add7dd377bdcec57ce3aaf7be3b55b98ebd6f478d46dd96c7baae6d36caecc88eacdae7c4d895cac45f2ccebf2f85936f791b48bad69de75ac56b3eec1e1886bca964dbd594e5c8dbb4dd5e6dedcb299597d0c4bcb66d76bd93cdb2d97cd21678f601dc56afcca3d9bbaad8eb7b9451ec1f29aa6eaf1a85555f7acea96cf65338d4d5591c4beebf4f1a877f1b7f24037775bdfbaaa6a34ce6e202b77db468eb6b643cdb6796a7caeebcd0e57363ed7d1bdddb651bb0df751aeec71be570628c5ae8bdc6da37cf84b1e39dbae3ba8d9764d7fc5aed77ebfb962d7451e1be5687d981e79bbbb2e1edb75dfa9f576dde930251291bdb3c41da75b9dc689ec66f791e0cad7e13ef6bcd6f8fbcdede1cad7ce9a9ddd29cb6ae12d7ce33e12b48ed6d936ab6165435e471ebd972362fa568fb9ce11f5ff55bdfbc739f23b92f32fffaf79111bfeedde6b1dfb179155bd7bd67148604e898f7bc9afff37d7ddf55673abf316268b2da6d0a1813c56ff664ffb9ae67facf6ff0d705ab04d8bb09be545b4aacdf89a1a83c13535a26b6a671cd2a1a93eae3cba4e55e0303df2617ae473ed96db286feb7bdf68b7dc56857ab7dc8d87dd90eb6859aeeaceeebb74e501c7e66963dbb931d8ea60cfd3c6cef294ddedcab6f2c84bd1751f8fc076cbb15b4557bdb6e53a523db24839744512775cdd9aa22a5fcb538b43d73dec3b8dd55b45ab5bd3ce553474b7c73d1c028f46766dddc737bced7634b26b3bcd87195bd970f768c1e2ceaccc38a4fbfd5cdc19877441546dab42244f4b8f401f6f667ad499dd762cc70b1289dccc765db7598c48e4b95bfaef3b35dbbef2e19093e3cdf194dd7046a4315dd3b63348bab3306d7547598d6737d4c2ccf21beb8adc6a5ac46b379188ae7cb87baaaaa9316c45b29a450233120daefcfc4c5eac1011e83e92fe268cf6ffbdc733fa4e5b609e6efbfde6f216ee9e932d9b66913a780930c2c0d972550eedcc2d573b67f791dcd3bb14cd3ab31b5d53bd5bad2287aa1e66dee6ff12ff577fb0e0bf1dea75a3acc63b2283e8ba7b90a3c81e6c77170b06836247e330ef681ce605835f96f9fffe14f0d81759d3dc931a4fd53d80162dc8d29eecbf5f6ff3b6d9ee9e799bc34e76d7e955b79d392954b9db1df4ffc2f7f230e21f9f59e8759a45ea366f8b75a48a342287c1ba35ad537b70b71a3d225154b7eb6ed734b8eff456bb0ea22b170693c356a329baea6150156a76af2af26e4d83edeebf604fee1c667ad472d9dcc7649b93db2eabc8789a4dd56070b7da1ca6aa107954d30144935b77ab872b1fc665b36e4dd9b6e73306b1459e194dd52d44f7ee6d9ab7757ad716db146ff78ee4c96d97778b23b77dd7a568cb391b6c91c762d76bedbad935b7a259d970f7e86aec46f55eb31b7255f8fb0d5376afbbce6ec8d5b3649baaba65795bb1eb35dd6a54b7bb2d6fce474ffb5e370e76a3eb96e3ec0602b7eb585d44dbc395bbd90df7701fe538714d836de7474e5cd93871656b3b872bb6a98f31162c5ae0e9b8bc8daefaf70b1e7bef9b455160daaac8b656082b6415421422142244c80821ff6fe3bf818816ff0dccae07ff1f7caf54c777d66477077e6f0b99e21f176464e6984926b6f25e24b85176f7629bffdf44ddfbdec460304f1bcd548d6c875a55f5d038a47bfbfd7e3fe3306f1bf9b045f2ba354dea63b1eb62b1eb35e3900e4d83754ceabe9ee88aa84613d31e5df736b5bffcbfd981669baebc8887ecbab8e7f0f6eab543a00fa0ed9b2d9267741bd396e5390ccd3a6dd31cc6eebbbd26b596f15e681e3e30cc5391bddd752892adcb3d25f5ffec4e85080889081bfc9b2b3f2233badb216fc37218abb525dde44d55d543be06b79d6ecf5c6c533bdda25adb781da71b68bcb3d4a9e6cdde91fdc8db9e7d9c4762069e1ac0d62d77ee96c8bffade37829e826f827066525591c4ae8b41104412ed62cb79ee1a4f67541781ff6fbe17112cffab9623e23604d610376f9e60390c653b378b6cf59d2e22d119a7aa6ab433eb2d5c91fb8e1b57bd63887088857fd3987566f9306db591b748bd6aa131f794c877714ec824214b84a808a2290895202ede1472f6b8ee3b76b7ab5ed97dc7ebb2aa91ab6ecb26ba8f47ceee62db919cdd75ab46c1789b51bdd935cd5f1969a29bdd4634a976244fef367bece950ab47b5ff8ff15e403e0111f3ff269830ef95e5c2afdca67c050241768d074477b1a76bda530dd7f7beb1f288d3addc0e35d537dae6e4d0e6fabaedf4dcee71e856537d23ed2d892651bdfbca8fc37dccdb2b8f76dd72403a28ffa6dd90ab5955d316dd7b3844f6159977718ece06ab7f93dd44225238d4c79e893cdb0db59a75ee475d64379cb9a619ccdc66fa9537b2f774b31adecb47221f792658dfec316fb7a95967eeafec23e8a3c1ffd7ad698be46c26f29cd478b9a76d58d778e866d31e97de2492bd570f31df23469fe341ca4477bbd9a32a3ce7786ce1623c62982b2ff6144c55f5575ef1f81c296431b769dec335ed7354bea37363dbc8f256a76d8aa6fa88b27372acde44777a9e794e2ed1bfc972a25c93ffba35dd7643ae02e5e8cc647b34eebc59a15cfe7fb3a3988b4bf45babc38d66de8a6d16edffc7de2b6e8b6967cc9cb77bc5e339aef8e6ffaf78afb8b137d115c9897bb8eeb2f7da412acd613bb6b09cb8ceed101a91ebce27cb5956edccbb385796c7cfcccccc5455a3fd7e6668b9eb3c57666606f655661cd295e5f133e390aeed1b35eb66285b1e3f7b0bf356e4cdc2fff714cdbccd756b7a663113ecb88743ad5b5dfc22ea2de7c6c5cdcecd425ba9b62fff66dec522de0abc5d6ccdcd6ebca1cedb2b4fae401689def196b8d58ed4c32decc654ab683a08707b2f1d36ccdea699b7195db55d07e62895a3ceff9bdb88ec9b3d7318926dcffc3bd18d9e59ac2eefe28acc2ccfed961340150002db1fb6edc51684adc1daa43737aa8d7cd5ed4672a3ee7dabb9a7e919ccd3c693bb994955d549754daa75dd78e2e5ba155d816618bad7bab4e730adf3c691074790ffcff15e38bc6e84bab1e5ff5fbcd78d20377a6a76d4c6a8a1a59dfa7fdf7ba5659a92e6c22bad015a1eb4241ad8ffb7bdd7d92617ef7516e7df04fb22a303fb62419773ce62d9ecfc684c734f8d69d8ee291ecb91ea306f7fe50cd66e36efe1ba8b5fd9ec672dbc80b5bc80aa8066deec67163337da864697891c5d9146231f6655e56d9e9aa11abcc607334a6652ccd868c8d2f15e34eed000e24563c6ffd778af325a5e6575cae650bc5799d88c4d33a0cc30ff7fedbd7ea37e5c7eeff5f3f1ebfa7f13ef25e3930c2c325c748a4125868bff9ff15e01c40aa04d00550270f1bdef3535b2bbdd68e65dd5c7ddf6be37717dcb3f31f36c43a3e3c35d87a6ac30ebdc3b67873a838199660e0333cdaf8eecebee2b47fbca612cf2af1fbf4c76175b1649ccebbedbf9ecc3b4e579bb6e4d8bed36f23e0c3bc1862809030303fb5ab76a7ea1bb5d911c4d55815fa89afac83f246941440cf7dd97714877e6fceb3af7dc75368fbbed59d56b4a5c7946b26d1fb2bbdd46cdea6370e546de76e4f0cc7be5684e0e57ceeee351175b5dc4cb4974ef75987771ce3c83e716e6550ffb66f37a4cb699484cd1a4369a44def654cdebbe4bebd0cd227bde76bacd3cd134d8f72606cf34a39be549a490b767127976e6b07ee4c59c64758a6e634eaefa6d45fefd0218c9e34eecea12fcd1459612d3acae5d6066ddca59bcdcae1b4f2e9b5d88ec99af79d5ec59579c0a65889e2bb0af604fcde4b60b8ac07efe719f060c7c24578df27fccc03fe6b3c03fdee3e692578fdb80253ee68dabffdfb02dcf4223921d669d3b9608babab23aef3b2e64db28773b4bdb293b97d155ff7e4223924553a4a69a78b64863b0ed5a3df71d8a8d378512a9ce9a2c6f35bbdb3492290bf851a1f74dbaf4c955eb24cb8ddb98798b6aae227b6a6ae1aa8793dcfce3042c24d16d64a8db56f7df0fdd2bdedb46b9dbd0c73f46e01febb96d6c8d4d71abf024ec7fa37ccd7b4dd535bdfbd28dd8ec312785970d96e08bb4e9807f9c07cf3fbef38fedfce33aff98ce3f9ef38fe5fce3a8208cf38fe3fc6338ffdf7bf3f745052669d7ebfa6cfddff24162f4e84ed956060b734c4c888aed946d7f68409b799e596d75303d4db9f4a8f369eebb6c62376dd85c07be02ca72bcde355c5e4adc2c1c5e7323c68b2e87eab3182f76d767a83efbc76ad2a03963068247ff15cce6e481e04b86c04ec8fff71382b23febd60666b828a84024c8604c67cd610ab8ff7f01e52bac78e85675ab6ffce3325db73aedff376032633688b11026022e000ccc3ffef28fbd74f97f1d668b3caee9b9f78d13796c917b13c3f2dec4e0dec424abb679482e5bb01603c0beec78b0a72658cf49c08239ac7f9d48f295dc76c1a430183483bd6fb66e4d7ba168d0c82c72d8abd241b3fac8852287b528d6aef1b8622042f5ff7ce5928068cdffb77c352b76bd6644ae4123461451e6ed214fa6ef509c43c77cc8cbd02161bb6eb9b41b3e431b1c4864ae4836f336c5eba990ab42d401eee6c05a2158ffc04262d44267a19c7237428a902ca1b741af7c83d49861c5ae8b72d934ea55abc47c0e82214895a03e82c4085a13f4f5e636f2e17177b403cf73351b28d4806a668db269569bc536c54b63c30d02917afc6586854515b7018af3398cbbddc8bcce3c75abd1dfefcc44de816979173bdf78a62ee2c1b5f138f2303bcbea61ee1ad55c453b2f1a8bfda8d7ae33efaa6ecf6dd6993b871dc3e6549dcd76f7629b168729bb8dbb78dc756b9a1ab31e729e41d4be026bacdc473677cab628da21a76e751aca6eb83348de5ba8aedce443bcbd0e514b7b43b30b065575e3fd7ebfdfb1071912242c8bf1babeefd2ba95f39e3bb0b76baa5916efb8b7dd90d38ef3b5649b0ef5510fbb9890dfd5ad28cbf1d0ddae1a5db78e8cb6dc0dd87237ddeab4e419badb549816390bd7df1af5ff1fe07db77aff66db6ea8d9b9628ba678e8aa85417477a4d95ab1eb6250b3fa6886ea5d04721cbf5f5045f52eea20af0b0e57e03f26e01f67f9c758fef1957f6ce51f57f9c754fef1947f2ce51f47f9c750fef1937f3cc03f76f28f9bfc6326ff78c93f56f28f93fc6324fff8c83f4e62bc7f7cf78f91ffb8fdc7fd1fb3ffd8c83f3efe63fe8fd77f6cfcc7ea3fb6fbc745705abcf83cff23d511ff2be1ad0338e7f606842dee6dad5df99194e5c5b38d875b500d07504e4ead2d981607f401c72348076c3bf31f62fa9fa44e026f12b9b23c852fab33e5bfd835aa373f1af92eb88d72e35f1e672e2a743310911e817bb89abb38679e449edfe6f0f95173dab71b725578e5a3ccf9c9cd6120e7807f33290cdb9b988bc334eccc7b138168aae1f6be6176def663da1e8da97a34eaf33c1a91c29c6c775d1897cbe6316d4fded5e20c301579464e9155e4af4c231fd336d86e7506d8316dbf86aa1e0669e464bbd5152b7258db91ec799ed93ca6ad3087811dd336f3f52b8b255556aba97145b262edcad953d546b6af3c5d577e4cdbac75de9b184cfb7976ce471cd3964def46e4dd228fc653d56f27706f6270ab5d0bf7bec1b689c1b4bd6ffc3f8a7f7ceba8c03fba72a11b5b6ef8b841bac96bda7672166d579ed3daac6993a34d053685d8a82bce53ecc8db9e666df258e36acd99356bffa6c60bab43776e35309b9b45d6a5c5bab4b8c556beaa59a5268e1aba7fdee6134cf805a6bfd2ac4a43264d5a1a086816408386a52d1f26856766b70f8d893391ce2c3973fea3abb6d3be332ecc6cfa6f3bd2689a71f3959955064e99b7583e3276c8f420f363188d69f2e619b6a6c9363d4f60364fdef6547722cfc176f7e0b1eb1c4caac1dccff6b86788ad7c0de32b47fb574ec784f9c48812f32569f6b39f28901357b660105db9104d8362bebefbc4fcfbc25cfa30793ade760b534c0a7d61d6de44f79af222394ce66705702a80ca9bedee3db9ed523427fb16ae059c0f2c800610ddc7636a2787b7ddf0b61b8aa240bced06060e6f13d754c7175847e470c8bf246a77328d2da739def7c5c617afff7f2fa4bc88f102b77261c6db6e5ebedecc6b17586f764924c0fbba1ceb589d7689f1dfa5c13f974b6fa271a9f31ac2f0ff1fa7fd9b76432dccc933f715dd79e98d6cf6216736935debc86652d5a8ce6b9aeff2768b34f6339b3d838525db74451a8d7cf815569c1b763674e56746cede687baa83cfb1b5dc8de56a78fb06bf287c59e2b29468917c05b26369000b132c61a7d1a88dbdf55d497465ce15baff1355796bc5022b96ac286005fdd3d4ad4edb6c1a975c75c6db6dca5aa902c93cb7aa72352cb8f23458ecba5825c49b7d05b6eb96a3c2eacdba5498396f7d54d4acbd4ddb2d47a5a8a9c4a022e14d344fb7994876dff19eeece72bcdc83c389a1ac2e265555b7c06cc2b5c5ada93a4fb799690f0ea7553ee42d678f59e57dd7addc980e39397165437751efa4aaf3749bb9553decb9d543ce33485ed34c2472b18dce486a3b3e363646f6c58b68d94c6a63525591c6b4278599b79a08d79ac3d4a8d724da856daaf15ace8579af75a9985e67ec9567f3458b172f60f035e371b62e7723d24736cf73d548239cc88d5d8cdd771c5d75cb5761ee31c79e5b7335a98f9aa37ab378e751b7b57cedec2eb6bcab7b856b43a3cb291aa7cf463cee42b791197af865ac72964f49a25ef92517af23d73f27ae6c68c1e0a9d1727ad42c129816ebb8aa730f6b595eecc462d88811994db7669140363860d2c872221b1c30b8eaa14954776fd3ace6aeed869c3d5b9d69d0387358cfc8aebb50ed61c2a358d749d588ec5de730d4cca63e7b703855ef8eaa1c8fa7783d6fbcb64dd9cdea3417875a2ef7beeb345c6777daefd4dec27747f76fbc3345dc5978145df9d9ff47f84fdf8b8305a403ff2f620e096c2d68d9b44df48eb6144975abbca7f0d19e2d8921d298f1f81aba3b32d859fdff3fea797cbdc99f305eb00876b663c76c1e568cd9b7b06bb64e6caf695b3637d2883ef29c6cdbcd1e873cefff4d96eed9af37a2e8df043b597ea2ac1ef614cd71bb4ec3a9c11969718425d6a643ee3bc6f93737724df97aec7b4d81be238cff8f7b1f47559eb67ce57cb8d0c76324d976b37ddfa9bb1bb7cac3d096afc52152ae5d59997c5df9f474a502b7ee3892cafd7e73bb4e1f8d89dedcaa91849182fa473da2aab0eb64a7c8eece6747678265155f6ff9d7ed586d2c0ed77d66d6e9e35dcab23ab8f261ee1a6df366bbd647ce1e9366e791cd73ab689a0e39f374db5651b676f7b3efdfaf9f4975a7755b4dd175e7af8c6e644eeeb0357dcbe6aa87c82158cf449edb3ee424bead5b479e6e3b83e07135275555ab9a98ee2369df48ceeb32309bedba7baa1e8de9aed3c314d54763daf1c0c456be7ea16b4fd3f65dda59e470c7a1bb23359c2a545356b3fc68dc732890bbed3a0dc7dd765ccbdde6a0fc75f237c8ff17df0b84dcdf6df698b7d1956dd7693833b332b7ce22b9b05897ee359844f566dbae5533b37d2495cb61652c5af0e0edca96c362208f7a17f79154eea8f7da3e92caed3a0de743a93cca8666b2bb25a2bbae4ecf9d6652a8cfb6afa9aed37965f71d8bec2babb7ca39ca8b6dce6bcde6cadadd837bb3adf0f70ba27bcbedcdce9df576dd461fc99d83e537ec383febc99da3cddb6ebdb3f5634777d73afa1cdb0e35bbdbde53cdd74c64cb43cedf8fdd671036d8eeba7c9e794dd75ddcacde603062b408604864bf32baebd86dccbbd8733e92b66bcaa65a2cb9ed32d89a93ea57ca1e8d1ceceb348b812c1bee1e6746772f0e913d13d3368cada7e79a8ab53bc8d71cd65175a77a85cb26bad5541359ce967b8b54d58daab501e36e64bd93eb997d4084986d687440d08058f82142f28f0a7e101a222487a1690e22c4fcc7c21f7b67b29ef6fedfc80b49029f1e819a45024dcd2275b0e56a37d1dd5b1d9654594eccff58b740d99daac77da3cddb6e5b3da6ec1c9f438b5dafd9b1dac8ee56fb381ad9b575ebe8aa0a5cd958b4e0c1e7d024bbeff8b0a7691d4ddb2847db2847c3db6eec06b2b59daf1d8d433a23c6b4b66b1d289b6e755a8b7c3b1ad9b5347473b476cba169dade37d23aaaf7dad0489bd6d13b57e5d49e866e54ce38a46bbbd691c7852b1baaf75a0fb4add556ae6e4dd9ba3a60db531d3db973a07b6593db5d17e1d4563c9e0687c7dd78b01baea329cbeebbb7a3714887eabdb687e91dbbe5da751bebb61cbaf233b40f009f387c7236febfe56a47f7abc959280e5b6deca9f1ff1797e53b1b44f56689e911681a91ebce4382e4ddf634d9759bb62a32b7bdcd2bab76e655f76d34793a62b37b6d44988a62c488bca67984719837c238cccb615fb9abc23a74d5624916350ef356241b4cb268924dcbc6615eb05df79d5a709b810d778f1cf6651ce605d11dec6a2b322ec9a6198774e911681cd29de911a8aafacc6143cedfaf67332c450e4b917f756bbaa6ba1572930d0e080ce6bc3b322d9bbccdbf1439b3baf73ddc6b0e6b57146b4adce8b09fc354dde8dec4206f83289b8f13ef44d936d138cc3b8d43badf79e614d96c43a3cbc160d0ec9122776026a1d32370ae23896c70c06cf2b6a365adc48e98f7ffc5f7ad39d059b3cddbc17f4ce4ff7b8f87fc6321ff38c83f06f28f7fd0fd631fffb88717e6f18fe7e2fc63b9ffef807c1f8e263f5c77ef3b88aedb6ea8858fc5dcc01166f6235f53cda6bdfaa7f5ff49be42507dfc163c0b29aaa409c75d52bf59544d8bc39beaffefdea726e43f88eea40b734c6caf6971a88d6262c71de83692671c77dbedd2dd373a776ff868a130737ad4f93c0ba2e56e26597d6766b3e56b07e6751b79dbef3777e6e2ac04d011f00950089460a38f8d2a3e1bdc46881aa66a80f1d530cf76f76031673bddfe7e689a7666221f6e940fbf32d24896c5c0da2db7be9d99c8d3afdcf39a6eb62375316f0fd36072d56dc1aed5610eee95bb05879c6790a4306b5dcc5d178f6cec3ea25d3562dc06668efebf9b95b1c3353d1a756bf6bc86a9c2b4efce8bc3cc22f98f6cf68db2fa78d419e5ec31a748a1f174de67d9ec769c17f3b98f0457bed1a45045128f48dedeede1ba914676cffa1fc3b1c8fb48da8fc836ebffba7f21ff0ff65fe57d33ca74d6dcc5a11ed6ad69dd9ace589ba1e17f97feb79177e0af0ede76f3fd623cde26fa7e32f2cc90d1e4cd0eb6596efccac955cb00fa625cfa17e616c5ea8b212606dd9b311a70b700f27c0079be00341ebb51be866560006ffa60446279666166f6828117575ee47041cb459c37d1807cbd99c79415d36c2d78b48860e17d2c62f9581062616491c1470689cc8a8fac4856668a99eb56c5f2800079539181641559eeebed3ccfb094e5690f7bcbe64976e6a41a67eab0936d2ccf37e6634cc60a09561cb0428829b6f21ce6334b8841f28919129b2216e71323111629ec49185c180e9f0a593e1588547451619682540a08522849f1e54bd1018c11981330b42f55ff39c526a66aee6d6adc6dff89ed968c2cffde56b081c9651559ae8eab5f6fe7576ed94de427b2aa93c2a1994fb9338785f5ae8f62a6d8ca796431f3049353d554d52bba7ee5b4a76c790b7f67db964d5ec4cb605fb98ba52cefe9d9cfb0f43cf3cf3c832759163b339113539df3749bd9dbdca62bd204cb61615f44ae22bb38b3d9f1b80ecee77ce4e9361edc6d656337cabe11df5ae4db4e598eb76f703ee7c6ddd03d07c84cb3dcacac5d537edcc674aeab1a98a7dbf66689bc0de2edd5c859a20938d3c4d897206582b13d359610c3ee56ab25ea7c2534fcbfd93b57738be4612d52d8359e8f8403ff4f0209897d4cb62db28ed5374c58c64b663b22ce9b422ae6ba8f7096e81bb1362229cc2d70844f8420130cddad082affa64fc49830c352552431e7e1ed1d377e6554ce97a9f832dc87a8f56fa2c28d860d597e03dd78ba38876e74a36b3aec3b8e0ddd7167e86ed7b40dddc5ae5539b6d5c71dd76eb9b6731d6d47a6a11b8fa3ab9a2fc4d7992f840f049f7ff3344355be3657467ceb01a2098818ff0fe27d20bcfeecc3a357f507356fa6c5cc79ebfb70de65a0efc3d8bf799e1f56f83cf03153941bf316ea56dde8ef37b7374b0ce2ed75e5a959d970f7f07998eb40ebffcdb0dc759e9aafc31b5f871e1dae8f03271f07413e0e6e4cb09545ee627ba7e6ede32b8f91ed7d976a3ccec3c7e1c40659be0d7b7c1bd20d2ede64397be3d4a0e7ffe7dea781884f83075f06495ffecd20ba874874b32b8b149e614975b3db185c59a4309875188bc4e3ea16e62312c5ca22cf7c19b42fc3081f060bfecd53b7ea9d4e6abc5cb7b6ba238ba0400c787c18a0bc79b69cb367eee905092e60b920c302280b7e7c16aef82c20f15948514192af02920a267c144221d9f62befbb54d599b7f908b6bbaa895f7a98c3ba2e66553df556391733777a4c87a6396c770eebbb4e278f9b3d86d965cd0a7318d899d78eee5617dbe056853a9beddae17d14f25080f2e6dcdb5b5c4e0a735298d9dd9159f3f051b8e0f369d2f03e5f219fcff8ff26ca8bed3692508d6dd134f30fc9932664c830533b3de43c836839369fefc7fff73cdd6626575523d1f42e45d7744ddbbe91616816b1576ec7d3f26fafa99a8dbc6df95936799b7b9a6616c9d33b5ddcc276d725b5b1efe1ba913cc9964d399e96b3ce47645ef791ff56bc6447f76691c3ddd7f4dcc4550fc338bb8fa429d81422dee2d4806437e0da5c8e539b03aac5e1c86a67794e8e475b36d77de4d944555edc46ed96cf15642f64b0205ba1aac8e38add6ab6ed42645f511cee36d9a6ac3eae30f2a1294416dbb46ec58a5f3fb66df1cc7bcb659388e4779a98d73413b9dd6689195dc38472aad9cfb3233babcf1ed68944be51cd6ee3d989e43ff2799e5949ce66bad569398ccd616665c6b42877d46dbb4ed3c8396724396f353d1af990afe779aa6512d9247234d7b5bd357b9bae48ce6e20ba55771ada0ef54e63ebfbc8deec0d54e56b7aa7c9d5ad69db851a5df7314f2d4d8d2d2ed9a6688a3492d6f538b2e3ec865c4767ed865cc7de2cb1d76995a800cef335b0f1ff3a63ee6b00a12c43f13e068cd2d27c0cb698c2943df33180932b5df8ba36fdbe4bc57c5d6afe4db4ff2ff967f24dfe33bcafcbedffcd7ebfae87bbc7dafbb88270e53789c454a8aa9cdd2a2fe21977b135f256f7b6af3796f69587afcce22b7fa27c780c4ef3612fdfa5f4ff3cde779bf82e0bdf6bf23d1edf6f314f34abeadea836869dc561abcf4e24bbea1c3697cdb61b9143361d6655d5766f67460343fb7acb6226d8b9bb3efbfd4e7d4c0a73df684f8b735fd9447256b79cb39960c6fc95cf1fcf2d5ff37672d5b74edd62742bef56b6e5c8961a5b396c59304f21b207d1ad4f166429e0ead27e9e2b98d8bad976d578489e1c6a7477246f339227cd6da65f44b6bca2752b57756ed5f486694cae1c45ee7ecce8cedb6846d72d572b53ad38645f6fb570d4da40ab11ad3366b0e53c2ed84d5d6c533cadaa3ab8efd28d06bb0e9a1ddd686f26c41f2d44762f022298f5c82472ce1e6789e16eb38a9fc696dd66a15b16ab7fb3efbb54cd2d5f770e598b4c59b23cc422156b4dace29bbd4dcdb327b7dd9683f50956175851609db020bc52e0d5f96aec025b6fa23b2f259e605f1d4d83c96df7fbf5e4cef1fbcd9d190566b383917dbd9d592cd9a67999040913acd875dbbf54bdea6dfcca3ab7696c39ddc2dca258fbe628bbfb9799dc762af289b2ed1c6772db65adcf5d64f50e5c283a9d9535d40cea655f119b4778d9664acb28734fca068670d7968ebb58b8fe5c380b6a384d038f1f920942e985499b4ff381d28d206fd56d05098921180bf4647a10c1a74815fa220b9c25a52c653753b90abb4283e05e2f87ee982fba07258057496452852b248f9bde5c9742cfa481578f2a93a81245a60b7a6655f48541875489c4520b8ce6e5761d4bc8bd9951103e0b9b4a9f0492959b424cceade5075229378470e99888ca1f7acefed08930180a193817801568b8d20708d71432c69f9994dce2213ccc67ced5437143e133f8f972b75ce9704ff432e04a6f60bdb50d0fca35305e5d17a08de10799349430f8dcfb8892182eb916fa8ae8da509e8c9adc0a02387bb4296afe7c65e3baf5b8f31e206428d180e7cd95ae943a862cde800b3860240114efa614e1924282bc3b0241ba193a45b8e55d18de70409b3f41c74c294104432e223255b0ddd7bd665d8dcb0801164c8695865242998c3d8cd19520baaa604873537a0a24ef94198c2429096df9725dd9aa00dbca0cfec69d8bbbc8003cf7876e282d8469f03e30a5dda01787471d7cbda51010fc025590df67a8769388992a8b732c782d42f460396553be8f8350f791d1d1a78a93e21c87bc2c4eb195050ce76011145e6109ec54702d4805567e4a6078f11c25e829e923e1c23021a577a7ad712dc060f0a5ba665d1b4756fd0620679e78a960628a083e8810062e6127ce15a476044b2104e119fda9742dc09a753321cb1e9104a3fe8e04396e9c4e767749201af8cd29bcd885a63b6499e0555d214a374bf8941d3c84e121330ac0663048ea8a61cac63ddbd062427042dd0a02f8c196e21edc3b266a17562726df6580347c632d797d44f6dff199f2b05699c25f1ed8f2694adc9477e0eefe8aa827f890ac3e5f2a89a4ac4002c35bc0f3058f49cddc07a89cc2b0c4d878326a6fb892ad1cb8142721d621e5014c25142cece7b7e78b3f2038918d35586d0234372c0e211c659d543e2a0bddb30672a5012da07fc1e4c85d5355e96692ebf2582ea0aba40957f9b7d26ea5146fae221d58feed5190af8443f89be6c0c16ee856dc1225b4945ae0fcbd90628097a0ce83cfc2460b46036b51c975c8cb8df103cbed81c549a94748a10ba882235da172fc6e2b0352dcafaf1a18ce2b225e6b8df92ba3fee96c68f46e369efc243461de83ad27d7850d085e0107e6674822ea2558d2029379e1f4427208296f50a9721b58f8f05910b1dd1f48e4e00be038dd1d423ce1fec5d56de3baa09c40023365180d4fbad8424430d8a5bc1e140130fc9b56b6bab82c404129e1d697267e2c70a439b7b003248d7e02455eca4917fe0e378dfa1e16316f6656056b3851e1ae619254961883eaef885cae020ad47a2686227853183add2301482a9b78357aba5043f089afba0ba296a38f23a7f85b168dc04728b2f0874228f0006e1bca1a6316ba9878fadd39176a7131a9e1f5b6c8807701384bf098903d65da123e5fcac125cca2ce86d734e7d5e5a423d0bd93e9dc3e82f6dc178e1e3c1f55112e01a58e4ff3e9caaf9870f61b0420bb0a0873dc376f1cbcaa15661ecc9bb8776b787cd20ecb5f5f9b3f9326d1bdf382515e70e8826f75f703df0e6b6e059896743588e0e456606115035f0bb8d286191e8c47d6e7a7b012e0b3c80017c3fd282da91d793ac03365291376bec11d15d8d511b147d52212be0137f66a408df8212041f70b098bdbc0417bd7f37f8e2fce0513e996c641493ec570c14f91b507cf12f4a5d4a307821bc294a99e19b731f7347189eb018a001e342a50ef48cbe8763a0e5fc7024fb702200d4a3947b65eed64e89af0fa7143f98eefe128e9368a73010e9390e99f18cddc3c3d71df240b58998706828b6602a84f53c09b6f3441c9079b00ba3e861029ada03a71d750928323d409e2524a12e0ea0000aceb684a5baf0792705b4c28731b18740613b9a256f6803b551ae1742aa318a2f4775c28aef440d2d5a0c9d27312222ea43f4ab01a2a75df4101f3f270e2e046e2f2b74d835a17001774f82741c6036972ebc69900cd7da065d235b601de05f28cdc501dd8fe436a0086f0000bfe214509d6e4630d5eb37301179b5247aea2490bf43208028181b332707dd13104775923eb81b1b0e4668855a8848b43fb086736fc86449afe0605284abb58f829316077d9704df90793325dcd16e376c043a574206ad5253501052ea005a99f50e6cfa39015e65e39cd4a281d5a5cb8bb4fa55b952c78ce4a9c3243962d4f050f2b2e589be04a35766e30de124db8d7294df9e600f523e51dc262274c5c285b2e954056577e440d2f980f21132f844b74816a1edd08da09ee982db56fe4f505e7f02abe033f5a613e3322b80e941dc16d84440fc4e4011f3585f80a24ccc00cd86273cb405a52be353984ed4c48b9b75032efe878067bf244e5367955888bafa882d16cfcae9c038a708e46702e9235f38c8ac22b628c91eb9629c2fd9445cbb379824059498ff16a69f4e67eea30e1dee92ff7b702ce6399400766401c75a7a4a995a6d618f80f444e94544c4979de96a3340f69781d2568fd8f1f7aee26e3e330101db000410b7a9e9200cb38b528e13152e06ed7aa065c4456e07bb4d5aa572139c18ba2c0d44b2971fedac7cd654097e14db889f34243266e7ee0e93f82399f2613a12fcfecb840347af01e469c3e102d18f7161c047ed487f52d999df9251d28941672607844434d1f81060db84992455c3242f5c01138ac612f3630e0092734955abc12c05c77687a4d2cf44a2f4460b70047f7269da0c23baca03d051a309556f29f777441f1d1800c5c595d6ddfe4d3da5dabb3402908918def7266813765e5d42b11f4df458f939760155e9946908d47b96cbab1c284dd13c3da3b90a40afe936356a9c3109ddb674e9c8b774502fe308a03de2087c85780670357188e2498c9017f7e025632aed18991c7c18ac5eddd35c00e586af31ec87c57102709ee1d5100940974a5ba502285c23b287cba6c6b448c4306d7b3ea332c6da532f1381060722bf8d37b0348d8c29d5eb5b98b2001fa4cb61c6137b08ebd510eb0b988e48a3d0a2d36e05aa64c2e11c02ed8b784abeb47190ee55b724c979c0d2fee9b1d6c4a4d98ec2e72148b07e1a7cb5df22ad60f42d5bb52f284e5841522eea10f444a39374a9eae0d53ca199a7a236aee7c143042eedc0f07ca09b74e5d31019c17c3ac523fc3c924eca891dbe7993009f72094e791cc68b974b83272313148833b4d32f377161cf4ba9be1b7b920a8978756761f050086296991f01cd07a37cf0c082e251cc7af9067ca1fd0e0013c6450d82bd9d00a3f200cf43a4a797a063100b879544b1f24001457100be3d6b2b3e3aad870e7c75654f0096c015e8f0e42778d40bd24ba985c2e2f6d4a10937adc369684e5061627ee05556bc9c0d802658944695c1324535c3ee4c28f3d292f036862dd364a1ebca0178dca0c65487c9241246e2b06c07e519b2edd0cc5bdd78404eea307eaae21176f8f8a45fa0c40a9f9072eb0ba3bc59292021b8bcbe9961b1663c4a46464a25c138e74b88feae49e78b0a25c6401904b3785e319957e5c241240503a90e0d33585aac30f203cf26924689f4e92a79b650cc2d7b900d25504a7f60c30845bc4dcf12e90213e03bae7d25158833735f0e636ea42770538c57c92081e4a2325f2460145022b2152b827a08cc0411c4dc21ca490e97d4969b10ad6c59583e4ebfa189ec26030385c6218b0476a049d4bd7860136e3a3c345614955e9a68045d7d204784a1e1560b90fe69428a35471bb6d1a287021fc6e3c94076ef8549755ffaa899f4f12e7d54dae6ebfc1d3dbb3f240e56e516aef2a98e672b961e216c2913f8132b2ee08238d5e5df978879a7b0dca9c380d57aa924b983097120461ef89241858dd4464a04181211735a6b9656ea1ba1922a8282d3c09fb392ab47c8e0646fd8914492906048b07fbf0f5508adcbd1e0da81e0059aefe889e31374f935a1fb78788a7901abb660c6d287bdce9f1782c41b925ce5260ae022ae1b5558af0963037f8041302a34961414f0a9b4c78ad6ec41d9587049673b2a77450c480dbabd0fe8a52877e55dd8897b3b6e59e298bc36352e2fed35dbb5e30bc78320c40bd1719c4bfb588d43d4549d33d72e5cb77ba9076b586b9cb8148e1be4a205f1e5f936e094068ae0b5628309a1a77f02453a570002d62f5a2ec41a03ce360e10a12e142b909c718ae09b404b88b2220e25048201fd7a2d24bd281e66e9140039335a8f07e9cf8c06dac483fc94a9b3bc194599f001709ccaa92a777e31a82c192149e0f0e2fbfe88ea2ff34e501fc746a0faa015d49094d9f0793448195545ae0ba0ac10187cda282bb2c8460b04242abcba5a22dc37420851779f8d4cb446a0946b3c3530f0452c3cf3223e829280e792c4eea7ca556102e25346517823b7c6ea44ea65e47d7f946625cf9a74a95fbdbd4e629d0d27403ad9a72d520c0dd19a7d92349d8bd1a3b722e1ae7c387d8f1e1d16e04c2293ce0f58e87e4dc427e8270a152385c01648de2ea32a2bda4e6c4f0980985b0240cc127722973bb548928173801e9cf96a07aba245abe0b938c32068d52f713090b250c2718de4889c47380c5ea96e892536a28dbf166e71a962096f64c1e45fa2914f4b9be2c767815a32ab70758976b0b44daed710a7589b6e85c116d627a650d1b1882081dbd3a0d645c37680e95837004fa04e01c7b25dcd4bec68cdeb302813f45d88bef0179f5b7ec88652f0b8e9be55589d20b89530fc8890afce60ed78d64d45416308bc247906503cb18bb75bf0ce9db6894833b84932995dc317bb35626302d506d17902cb75e4eaaffca37a5e7e6e9773d5c21c4255646c7e5304010ae114842e1081ccce1e220babc00966061593d02ee1e4a404a52b41e3d170aa23cde91104fc90d03e50306597e6dd1c63434b9785b4dc0ca092a0c70cff4c1f285d282f7138346d86c78e63d2d9155ea7800c59d3500217c26ee0bfe216e5c6992502ac55421d2f39d68f2750610a06c2341dcc592a7acfc5169c4dd90e843b98208a477b0d6cbba372fae001098ba769bda9e0d19b8df80995c40953efcaa4116dc4e65791718c4ca2f910506173a40cb6df1a55e5860005c5633ee60461e7edc240bbcc13d51affe0d13b8876407d3c553c9065777c0e072f172a80740175ae9f5e72cffacc1e1df03007e53a70e9b601de895d85b7e13a7338f3cb4e37a92d26222c049379013301f8907015f9762c21b4285f9b0535f309e4e9cbe813a0f57d42bba37fb206ce641f351485db907408a71a3c4217102b7487da74f264a4e6008e10cac54f5d45e542ab92554984e1d117f786b528e0972771fe0228169a16597d0a2485d465019f07100741ca786a16f8b02ebc60a42e3ea0a202e1aebc89f70b3f1004c40e6428a42e6467935e91b0de16f00aa42ef874401f802060d5c269134610a6404793b699ddeca8843b7148a097e500317584e5598d245f287d5daee359263941c54d1dd03b6a45d1f62f4f460b5214ab8354630901580ee0f053e611774ebab7dc15082b860d45328baba80b4a3bbd92cc25a1049f0b00468f174c4de8711c3e24095f81d2442d0f350247e4c6dcb8f419add220476e5241d5dbe9583086ea060272cc1d68a5dc135794eaab0e054d287eb4bb5e9c671a5e27680847fedd3976b254e2ee91051f0681c845c0aa4d07b878180eb28cec56dab71a24cf506e47b5fecca546254ae8f2bd1ed0a85e80b0d5100b3a241f60b8cadc1132cb073cf203980d784a1f0b088b8781c43e8fc254a14ee9c1d44ee026d16f4c8bcaa3b418a20a5260524e1e1111f5c38ce3061041258eb1a6100a61b009a987285990f4f06d8ea1242e5c08d5447c50e204cf93d29586009b218ba0e10d92f8b16ac5772c75e365c38bc291b5d7085ece9636c28a59a4a017c19895998ce55a66710614e19a26cee5a4292a4ece202132ee1e30ceeead0e80db8c00457a3162b1e53a1115c7a1841281bb08086b9aebc282bfd70f29ccc925c534b50dd436a62bd05537238730a7b3c3b15604611947c0c3057ef85c8d86b62fff24180cd23fd206f2451644fab02a03b0385a2df10f6bb7a9a44f9319c207dfbaaed2e3a3107b31071c0d5c0e5e02e4f12e1b7462fb8c08015f09cb9c60549d0a44bc94caa1e8736210ee3a244cfad4d037f06cacd95c3aad297154982779880f23306a5e158859a3c870811ca734c810b618c206c2a8791970013d9730895e17a85d16ff62daf26c7c01dc2adbc08332e3f01588bbf76d0944ab0b8b938c0a4625024acd0cd90e7419726172afe01e745976aa6c7af75a9dd560c8077461f4ee505236457c29c58b80004fcbca41e4f2e0251b25c4070c4de4c8cfc665154e11a61c2fc05b4ce9b628028cf032c56697680021f28491dce2340c8a36aa694732ff7567ec8b8305651f0679e528fa3521f6c4006160c2cac213027246cb85c4909c30da04ac32f80233088dfabab66eeecf1f86ecf003ff9da6a599417bc90bb86ecfc5c5549a8bc8ab42fb74f9ea8579169d49b39cede962c237f77cbc1a3d151eb11a501792f744adec8a827af06aebf225926ae150418943c5834709d9c39b9450d0d5c353fb41e0b10741f2a965ad70d34b52e005df63d13a6c36db38a540f0e97849fb40e312d07696e510d19dc4a45991793c97c96cda1a740938e2b84c372bd3edd29759c81b96e1c15ba8f2e55f939a17a3c9207a0ab03eec4c7cae275735402f4abdadeee2a3342f93687d1e39005f6b422ed4bea478ccb401c0925083123b8e2dcb77bebc87a0e8a78dd29692cae111ea1ae9a82424f5598b12bc8ae5259c114dc5d8bdcf2d119d7a55184ec3571e17953a82add550d98fc9cb052b7cc91e0bd53a907269ae9f273fe54287d0a5cba6b007370d99dde0b3bd7450349ee533a2f9c00a538171dd350ca9a12e91a5a3457b6b160752b31c0580987bb47e01113bc470f5a29e35689b2cc88a5fb472d15aec2ead02bf000ed0612a5eaa2fdc0f265ead45c10bbc77ba194fc3427ba3c32a7782b5810e8826971f23a2ce0f38438b8782d561c94168c4af2a615aeb25267f31280c8d20617331cc2ac2ddbd0c87175b8f1c4e5460719a67146a834d3b10707b0e9bc854c5c509668f5870bcb0cf6b97a46101ada4078508588466fc5a9241b584b4e2cd408c0c7852c4c6880b0d021c383e0190a62ff5ff7141c68f8ce9ae826335d88d96d55a36232c85ebc60b1efd8fb494f891722ebd6fd7276e9d24874ebef251bb8fd43f77d223dc03e24c9f81a45d4fc101ba6fe0e842edfa3f5e17eda296ea203cd9d6509ce038270fe8b94c96bd170e64a5114094b509367c2ad5bb742d4da2b862459dd84240eb070d0bdf4b5c54af5c5aed7cccc8266bfdf4ed93678ecbad5685d37fe7ec37df7fba16d0f1e7bab83edbafb1d3f06f3b4d16d2d1834db284743d9fabe63b50a830c19ec69db0c0cf85536dc3d824323c6a011232ab78fa43d88166796c350b6a36eab5b535ed72279f068648bc1a31159d78dc1a3117986eece23f8fb051f0e05dce6ff45f495b77ce5c6b8330867b9dbd9eeac0fc553b83a2250b85e711028fca6ff2f0e5b6d64b1c26c77574b6540854d946ab029254a511c4c65412e743169a000003e051c71c97e8ee0b5823d1ea280e2636242141d63446c89c002cc99041210a34043011a1a519293b624d29c151a9dba56a1e7ee28926cbbee3141b1763d1ad9229a47c2de5027fd634949fe712406652ae0fdf9c7901e6de02282c47ff96a95486ebb374fc72433c9c454dda7b3e6baefda75dfdd25f3387b4453dee3f70b0601f8fd58394e5cd9ccc2ca58be55a156558df6fb01f0fbfd7ec1e0ba51342d06d3a30e1e7b6ff5f6ddc78ad22722ac1a79ad6528e8a1f3a98dd59f4f3658d9f9ffaea7b007d0ebde0fdf2a5aab187dfead32937fabd0555f9f7f3e55b0f2cf38cc53e580aaeaa12a2cf9a76a4e9585fffc4bf528ff527df9fc4b75e65faaaffc63957f7b1351f1f9cf3f54543eff50cdfde71f2a0fa73cfde73af977ea78aa2cff4ef5f24fbfcf94987f5368fe9582d54b09cabf20ef2b25a45489ffcf3f52acfef3cf470a0b29bacfbf0958612ea22e4d80fc7fb3af3f549f0d7f3d6f07358bd448e20ad42c12d8e6a96916093c1a8774c6219d7148c7c3d13f6e8421f8c78cfef1a207feb1a24488fef121432c1bc76e3bce9ea171b7096db0feff869a0b75d6a4d22a1c9618c03334ebffaf994a31e000819b95e6ffb130a5c15b81f262eaff6f97535fc303121dc0e5ff2f2043a3e84899aef8ff973f8e71742859b855c1fff7e2b0910357c14fa200feffd6e812e46d4b95337ffeff5e3915028ce52aa3f3ff58875a0d5157505c79f5ffe5052d8f548511f327d2ff77910103eb4efad9b2f1ff38942b2e4968850115c0ff739539198c292e2a12c1ffdf1de10196cd254096feff4e12b23024248b114e2ff6fb990d8f47209ffbfd4ae4aecb823e508d3ec976b65d913c7e01ed5edbbedba0aa875c6dd76d3c7275df6df4c78d225a0685da6446801b8c8a2469e94eb684b4a2604c160e3c34d11ddb242931f4846210221525de20709ce109d104a840b1dd50332a448a2117940871a00312b12759c15ff87150e3605402654e4c68bbee20280ac5f08e0e48a85205a64a0d350224a0baf0b0e47864421c5906183096e86c519115bb27e33cd103411c91ba28add883126a00d031c646c3d80aae18396a30204a4b19ad14a69a54299517610b51059e98f4529506b294e8ea42c405426d839c48f5179c129314a4d1429ada4e2afa4c1052a00a0ca24bd94229117dcc447958d56912dc87063634fc59e009a751769eda70b5a85b936267ea092ba44b13a207354e83ad4b122c973888b27486541e97893c355376460567fd4055a6972212933b2a7688ac582517ea8c075890a6548243e97420ce9b510bb651d4f160cea6186f4b3020bbb0ba50c7c1d2937d05422481429b8b493df2230486a4d0810df022cec0435149847a96a702a444a28e4820fc41621880959c9fb083ab472c18104a3d62356c40803292f4f059c3b321a2acc8185b0d366f8ce688800312830bb49045e480a67886a90c12852172feb0aa7327079042ab70ac71d243a8a9eb921128c44c1250ed490508b10614ad2eb20c566aa951e21476e44f95072614fac4060f173eaf6cf5a8e0e86a4f13119fd2ffce068d433a394e5cd96ecf13aaefac7914dbc6b4ed622b7ecdaf119e3739e32fd8ff0f799f270f313867f55ed3ac86fbbfa8d48e46762d6fa36c4723bba66a54efb87fec40926d573554efb8b696f3b87f5c080b22246890a00602fd1dac898fe3ccddb93acc79ed9173d58703007abee10000d3c887c4acea1e40e28a70c8f0dfeaa600fe8d757b9afe13f9d64e35d56aff18d0e58250895305e1094d43b73f7299d0b8b823335d909d17969a0f875260b0467d2eaba4ac7290a21f5c004041713bc8a175d99c88dd0e34c8be6762f5324885b9466c4cb917383040e9a80b9d370323d52533ebee7d6801b0060d47ee940afee0adc5d0657106e8226ad575992091705be9c97b3220717098872eff874f58a9874d89bbe64d83de90176298c19e39aeb91b5aee9e376365161eecdb729386eb7745d4a522e596b05c61b9a8f02295bdca8497f268c713d082c67f61650136a0819327f1a5ee97a8a0711158e2761f7c7af47a30192adf2e00e05774522b39242bd7151d908fe04dc895f48b542af1b5b74f0538ca080750c154e620749597083d022bccb8c2bab4282d956a89fc8af57ee45e70f5f861c07540284f8f460a7c3465f2931125e907d0e0051f61dbf303a05529e9906df8b14275bf06c397e7c13ef090b250f08a9cf878577aee6e5e0f2e9cc1c8d8d330d5020f800478553d808411f843e7114911be9fa41f0c80241fb804588d32acc90e4c653575dfc010bbe4623970c79e48979621187e1366ea81a9b0e34a5034e52aba93c07d40c6e1521824e13a7891a4749281ce1dc000f43e4173e67ee852a39cd428c4b75dd6e04b01007f586150090098840b8407fb8d2eb4793a4845659c2e437f27afd67dd1e2149610c7012e069f44bd292d5db808253a75d7bc05e9699073e7faf179784b233476a16905d79919692e900bfee0189a6c5c30583afd1949155c4564454a203b5a7da71d81708e60d7fd800243c9068c9a3b810a5c396b10a72b4814963b8aacd295c26847598648bd17fcd0ab408f31778aab006e1f2e666e154ce74fe1616fdb008dca1c951c6127b1c67a1ce6fce05daa8e6b8a9994bff2834b2f960d06be440560c9072f79175532f2243a21af0d0ae77d222bc50719a2ba723a70fc1d2b0f0f01a4026e2552a9ca30b21cf89f55088b1df35c3fb5a06039450eee9318672fc744ab37b402ef91cd177c856ec42523a4bb5b262180ef44aaf22704a04a2616526c24cd778d121101572091ecad8204f6b26aa8291fb160f5275c59f0b6ee48708127d0301002aac79084ff1464d76d23888047134c7409e8e4a3446487c5d3694340f944bd72e3c46a802910937265254181935bec9a3252f7a6039d3008297337ce13980b09c80a9cc1499da7152ad815e6069252ca25d183e0881eac6182b28b531e2eb7d3222d2597a452f7c52c09be8cf0c3071153032f29d2f6acb64c70836622ae893a119411f8315f87fe53829001f60c64c88013f072e58751346119696a5d20238694051431f500446a545a60db8173bca9d193128a169e22a7d8a3e586081e7bf5a68bcc8d0a4eb2a50c9b203bf13b4c3d5006727376d57c79b76d13a52722460d135105e3da9df8cbb5aad12d5ba4a964591470ab24935c1e8310b80be4c9805dd44973cf2e3cbf86c2d7e563ea0ccfca02e12bf05af16e4e35d78f8c0d2f8bdfba67da60ae9e81cffd31c50b03192162dd42ab7074d9715a738d9736744d9064a044606e458fd2a31e0fe228e4014199f146ca22375559d7fb28d42a2b98d0e9c19e84ba59b4016fc2c19727928af05fe424c1a4405cbaaa36b8282f3868f10e301cb99b15a9674327c69b67cedc387e2c5d36473e5d13a7861ea9321ef79e69ea81c113c1079962a2d71ad1fc2b06887f0005ff9006b1e89df942f43d260c299f0c5a74590d8a2b01f030e4ee9060155ef416e172702079115dca7011edb33f9393e33f04dd8701f4fb05b0b8294b74d073e9408c613439245e820b5a65103015bc861619ff7892e5dec910de07bcf095e4e6002e0d312cb873da20c025b70450e9e8c1a81e8b4aae6e1451747a9a5855f01aa65c79252d7a770294fc702ae8f33a3c81952b4a0ff7139055b8919889fb07cec48f10c5e18f165c28918f543d041468b89a9ed10f00aa808f3545c2753068d0b73872f82fa5f0fca23602e04f93c6ca3d460af7813d5db0026f0f2e22075a7a4b505dc1a4c0886230015302b8c323065c76826efc71d200f88529f4de0d9a5376b990ea8618d400feda50e75a9a70f772c75b6f6b8215df8013850f41c448492a109d17b1c78835ec09e0cff894958958f3390d10a33c21bdb94012b12a258de9294144608bcb04a71ad8ca0f463d46c13df8010843f79c758693a9fcfc2459b56e2855834a3c616c7c29ea5e109e6c7c1c9ede22a34abd005450612f768a7e6dcd9bbbc09d077ed05d9352d32b51b748dbe709f5dfa76910c096e4ee5dd509634336e2fc0f0e5f77538948778d8489d2498337cc848159bdb7d5f553186cc1152cfaf28fd2deb84cc9798159cc4ad1ebf4b570ad2c6d99e2cec25dc4e5813b560186db00915397d328041e530e18f7c86dfd9aaf2765026243ae2a38107c9c142a77c78b12b80b2d298f82929be78252ebfe32e879b3b8b9ebc4969f7fd225eb7eb17508c3212b00abb94cb0fac4c61d60d6977760cf892baa4fa82e186711601818b8f40441f981e9d4cad10b63d79fc91f2f659c67f55a08e30e2954e533647050b662fcdccb03ce758483c8f55132b9ac861470c36694791186b4bc9808ca3c20f4820b05098d12ce24dfbf9ae5a4ec79c53fb400975b0382991b05c7e5925581c37da01498de9f4c63302a3868ba54e2aacb4acea4ae2e1458504a1812a637aa68ea2dcc00bb277684ba884218ba8450d17923615cdd431408ff920a16980e00b2bbfe908563fd0cdd0972662e05086a5c35bfc0254247cebf6ad1e90b4529f4161e482b17e823e673891158c30a47b8c957b204358005655957a1fce0e5c2add245a894f007474903ec4c39e3419c8bc77786dd58527587207183dda869e09a6ad4e372323ade4bc498bb51b0b7b047cae50327e78661a080120312542f812c82b2b896e1599c8183f30a88725fa04db097a82d77432a01ca1e1ed897f0e50e8f45b000239184eafec0c1858d4752e14a64a46e83b9b6976200b15ba88103ca1444f23092f30b533a63f120b8a99b674a971fe0ae15e6d3f374df1220e241a6a03ba555efb63925e5f1ba54f0292a387449f4319652bca87903eea294097ca68b45d2181ed207828f83e3c4dd4001a5f762d57655857000635942e4fee16470113d1ab8d004f001af2a4576331915bd1d4aa1b85071817a0a01c4c039ee20f09764112f274c486e1b1017600efcf122f418f6c2b5af7a26ed2287387a3b0c90ba5f798cdc208e380f658023770515bc8f75e473b5a8cd784d6761de501a19e589a1ad1fb5465f4f47e3e57308f7c4ecc171f55010c5e58747262cd6f5a577a80e808b80a0240c00c882e4fac113a607a5cc710778ebff070cee32d202e1b1aef286384db9954484ba629818cb4848c63cac4e596e5ada93cb65a8e441a0f8dd4a3fc1e799eaf084da5e7d1e03f05c204814c078077cfd95081628f91c695d5414f0f0035d2ab02d38922e57250a5788044cf7479c19170957965e0b32a6301de29e5bc1a02b8f4ac172edee00fa77f4170b8a72ed54c2f46f4e31d7c1830617469ba2fbaa89dabd2088a8a765e629b948aaf04d7cb4f882556c2f04af16c6e263c84d82ba7b55609b6e4fcfa88f106bcc3d03740a5f122016f62241c675638cdc4d54c44e6901a822b88211665d3854135ca9c5b187e7a6ccfd0a11e5459102f3b1b0a8c16374a0ba70abaa32ac0edbb729a0ecbb18d5b99c641929316d7d8b68d973310121f142205170333989746920b05e3b2ba47e48911b8f818bd9050a0978152d2ac0ab2d453d3731afae9c4c3f4a1e6379ee026a46701d3920d19309a572dd49a300a708a4f616e0a1eaa18c8af10f6078f14cea7cba0f1c02f280ce50958e2058b814d44af10c0c882e8e36b90bb61eba4f4acdc205d4cabba4e8ca3e0df0c5357ea972d12952efe44f960f14c8c39351a1e1c29901c01da48342a9258f838f63b3c615e6c7d3278274ab8424c70c3f1acbeb9db1487311c07387abcd928a3b04561c761188d48751a3863df4665d46a7225db208067c9d4e9eca38bacab33a026c80989b0be9800330a44b256e04a5427d2c514f6e47d747b4c9c3656527447963805af7518c429f45c600d884295825964488aea70630dc628a04f0981b427a660d44506a8802a20b491e1a7803253a5c1d4849b83c12609f2c85a50f404e100c070abd7637a0e0304bb3de0d160d3809092d17882a49e5111f78374d9c9e2f8456eaef6e0a8ca5549b0b81844ca5580af671f0ac1f87bf5d12635294b788aaa480c0f758001dc067b28af428bc0954ded832a34bceb0cd7de3a604830ef7ebc66d6a834520275c5a6702bf2691e0cab9cd288f8e68dd37636695741022dc4a6a426ed8af807b0292147c0192403f298fedfee921e60e5242a33c40d4884b83c78f321540c9a3994c49764195ab4646809bc9d11c0c288bab920d84113778e37323d02a855b35c95c96fe8072468a4fb80ebf812f44c982cbb4f579b501d6be800b6bffe0048abf6237e9e6892457aa5ad1765f2930e716313a7857776cca567bd4ae04022a5c459e82dc3d2832952ba85cf0237474782d2f6edc0ba2da9e93da73dba4e97205a5c8801f58882bf5b0b9c3b060c8e97d41eb7323bc8080c199480dd681a6829e871375572c6d124ec0450417139122463380d8634eb270306fce53e2a6b0114e18ee282ab88c72b6e25eb0e8061e42c6ccd5208a979bec60fa43454add015e20f05a22752a599c427b209858949c7c4feea34b08c341ab8445b14dba2b1302aea48fd21502d58772485a872d75d054e2e0e0840199daa3442145511790077a2f0a615e0d9e3e1756bb067f2823e0ff04ddbc00a9ac7c0f0e901e0d870f978bcd4559c00d1ab7558f15d70347af04592aae13194aae9e485a70223279f7824d66ffe0c7768d58d0a10cd1c351a673d41200b2b6bb250fd12de5a0c81562684eefc51ee04dc52ad4753384cfcd711c2ea094c58fc531f27f327d5d0660a0957986e03c900a2856037c70d546a4dd3c31879e8805899e858e50f704e4c348b137bf64824e0fc3568c6b86c7c3f5e201ebae88a3ef2a4c62ae58ab54780d71040b91e07509c043bca7a0902a2dc99a81296c407b852e8dc15e4e90e1065847f7c2ef462fce0c9f1bb60342297c94fc416aa03c06213b9e4c9f302ce7cfd70d53c57e240de8f1283151ea2031e819d884e9e26020eb2fa844e1deea47ee9bb5aed29404115c3c03e67ec6083c8fe5875719800b07f84284283d35176e61018615b87ea538bd041fecbaa8bc40bf4f933b3d330572e0267ce4f09058ebeff130eb4e80c778e54e755d41b6ecfc1d94247748fbea9a184002ec82c9aadf3196e14b85b1e02de94871cbb4fdf8b13b194a190f3cf0acdc08ba33d0387027a80b96432e7b4f802871f59fe10f1054759521e0e522d944a29c0181ca35953c54e6654975ed2a50520a7ae4e3ead801898bc28457bfa6a151c986c3d17de00c0e0c2804acfbc6c8a2afe064ee03c19175b159ebbef9c953ae3274e41b688487a3ac053f021566eea02444fe44a1382509139ade14a6b88a66e00b41285a986848081e51ca00ac0019a58ba48bf7d69ec0ba6217fa25e10710c693232cc9a234f0954c8961428ac0dc21128d29e4a17ab3557c30006e52fd020f707da249ecbd8324a0ecf160fd30fad065314680b7a3520166e074f47d38b0795053f8bd3c34957254310f645156e609c0d41730c54aa9281120dc21cada35374853c9e097777b9f406164115918d19a6fd9224ef80884aebb0025445c92dc7c5c486d03a51c418ebe6c0293520a5da7277b91076f38a4e96a2a97caa3178a37c7e0f8202a70dc3375c4fc952c628f0e693fcd1328af06e775a3f4d05faa8aac9b25ced64d33b43f824865ca062b1ae16402246e964277259aa5df4de1094d979a8c36984eccaa9e222862984821b4520ed9e4157840e5363035e93d81a9baa3defe3c9a3bb43e4426b23f6320c54bcef2dc4058e8cae290d07d2f84b00267ec7e824a14ee5103a71bc44801ee9046cca5f3d5a664a0cd836bc01559f8003989ee1850ae309706153c2c1c79e52c211c7c24cb533611950a67789556ae2a04a1f4e1a542cf1685027e058b54658a1452beced8a71b400f52ff658757e902cf9fc7a283d06590b76f5b16277f210388aba2811ae5021cca7fa024f234b8665d48442c614c94285c3a528e659e1f2a3f06d559ba95f071699c49c2354662cc9d65ba2a4191e592c21d59b7cd15dbc551034f29ebd6a5bbaa34c1808b47791542c25c3d6816bd0f1c68f7c8154aafc4cd85cf44c1996ba34b966b000b4997810bbc4246ad79280f2e28d9e889f00fdc124f43029f7ba41503d72d58e4df54e0c16d8a8c78500780ba17e4b175c18800f57dc230ba35ee6094673a19ff4f97a81b77420d972989eb8190e1068f49722a21b9baf06d1ace709105323d2aae219700302d7e4595e1c3d648f0a0024871159815eb7d6d6edc1349f87c026190603d3d0ef809d29ebca54ee513bac2e90f0881e50b4d6952ee58b4e47d0820e3aa3854045f48d1a524b38b71fdb009d1cbab4302df64bf8838bdc2712cbd3dad4972d753d98f1e849f930baa58e335831aafe4b8eace4a375d379d505c297c10b83b2651c16cd209778f5cd75d318accafc19a51da79c1af51602ae1caae3c02050470b36888731510c1e5afb805b99be82cba653ed0f81f3374f40e7072624f78d34541b6e39eb92abbe4a424cb22b4a694967ea4c0460e18d29bb2c7ce034180c8351285ecde49f41802204997cc202f4fc893950b80a20a8fe7c58c67134707168288e06bf091f5463e51f9ebb384d7acf1f38efe447a0a22c13da1b14c170cc38b9fa1c60877aa2b845db474ca34b61228d940fac2ab1841ba7d3ad4e15af30005ac0145a41e0554cc700900a00b4b08dd774185e5a61813f75c5ae4bd2656606e0c2e24ca5e93375f36880ce65280d597f1ba82618cb0e00a8a40018f4130e6ded984e2b2da30e97661c238a84ac57330335312e023e04dc870556e4a9502233275010b4b74c0700eb0e2b23201d3954440081c01a058a59e3aa9ba3499f952625125c0db4120e272858a7211bd707349d5a8509a521f7d284de6a5934bdd18680e7ca60965b7d62a067e0d02f4f9981d8061c858fb2e7a465d4fb110b89eca0829f93020eb0a00830526ab53e7b70514254725013ff0094ae9a488dc35f3a7496fd0b1eb463131e47af4c50feee10781de8ab8df359166fd544bfe167a70f770ec60f84db15c3d9986225fc6d6a3240449c6ad00509bb7a026ee7e029a70021be43c8c160c7eed0f86cbcacbad87652b824ff96c794a802a3d945764d80e0c3a97d4042b185203b93f934512574fb2a314e0015f29d688c15d832570e1a516e00b1e88954818f0b90e7499285bd559e066a1c4ef2f0cff01f817ae42418c27200a24acdf20b9708a7fbeecc28eef43c11126a426c89f51702c2930a275796845f8015d913bf6e50a0fbab37603bd91c16d807657c82594b5aeb4ba858624ea6529b3712a40af70562104b399077055f86000ce6400092e306b0fb73aa6f93b46df7d62c10fa6c00f99373385121e32cacdbf6da92b4f75b8f16d234b180b28b42bc2d4a0d26d01022e08143b9e2bcaf037e2ceaf2bf5e1c25120764fc47af531ec08b8724a2dfa353958f74b011cb09d0adf838187a9bce1a709d72d4a5b6e318f0b068504799fa0adc242444d789da3075792035c5d2651acfc5a231157569f553a62b55c2c799bae10327497031d86720c1afb7791d09b800a04ae28bbfc901a402f152250fed3a03017548c350c00b2c5c56f5a3c728da3f7b24ac707d5f8b9833014e15a20932b7cea8cb0eb4fa83fef0151282e474c4edc37202270a18261bc663a04ea257101e58dd070548615f1713f0511737b2037f745a5228fc7aeba653229f0a54ef340446c4c6b0bdd3db3409092911d2897c8a817252828e983c8d008aff100e67f183174bf44c0f46dc6847903cc202a87b9130335a4e0dc173202e14d38c660211e9daecd60821be2c8839e0d4268ee141f467a3ac234b9248a14703903252e8fa0995fdfe43ba19450a672f4e4b63d82fbed067d2a151ae10b43e0536112f7dd5183b76333856f7588f45376c095b5547d7a5d01cf937861e232424d943d74c4b907a87d75b5482bf14a7e5e7ab4d8e45c4e807c7491a125f779fc4479342e0495c60bc2b0151c90ded02145784eab54b70b10cf755362c8c59144cd4d64a7784fc1627f80bb3ecfca025d86b102ebc6f1d071063e5db789667325f5062f02040d3cc00ce177c949507639207231119001db11b380dd4278f5424c2a711b941e5cb7c088e02e4724e1415a8efc052450f5ae5440e7cdd8802bc78ea8df404a84eb1528cb0da30657a28afb74fdd059f9324ce6a3ba35e83a0901760340552cb90041ba9638b9b9029055794e4040612a65105d237506fd1620a82e2207d2e00a76d66e9e95126ec0e3d4b73550e45a1046e40a2958e8810112701d0db9d2e5866c004ff91240d98a1325bc66c78843913874e5041a754fb19a74c55278fd91411e6e090a77f8caa5e43f3a64e50d3d91f57ade2294a4a6f0476fdde1394b70dcb42048ca1860665d551022b80758aa745958b9e5004f40dc592d585d25211461096cc9f500a012ebba8a70a9d424fc955cee0071dd51a372694469d4d5667fba013031d1d3b261c8257bf1a774b1e4c2bd1834dd235081dbe702286c8b6eec52b8e0ee49944979b243141e968626df40ed0136a3c2c3ef1844eae2b135e9befa61abdc704e70792801e0cb183170cb8c6a74e5f67cc097d1921291162a259a97230ee2c1f48a0281b938d690fa58980e7149f061edb530f09533e00add4381c8dc32be0bff677d7319581974433119f158cee4ae0b58c192cd9fb84390fc286f026317161f190fa700a32b224386ffb440a6abc76c82bf2324cc1b29c35c3f98085d40915add320b64384bac0b4a4f5c6a975de280cb6b4a90ab26538912898e096f00199e9e2c49b34a2b8f24953724907ab448a76c512cb91c78d02a33a48a707d78a2832d0811e68b201a71510028fd8b59b2ee201879fe851450659e1e3a0f4bcca5b21f9af0458434506668eb741958c440a904887e025c4529f560895272d599bb748e29feb1d117003c9f5e0238b04a04d84a95249474e2eab26bdcaf84b930a5a5ba638268950c6efd3d4335e841dcd273f3605db83f2d30ded78dd789234d0f818b33dc87cb8c5bc74d90e74ac3f2315aa5ba5d0e6df8140584c07b648cf95a3bd3138df8790bf88c95112060c235de7c2807d0d1e909785b817f08e0e0ba8280ace787805397d8a51103b1149c2e0663d99be18980bfc204c65d640bcd4b0203069f0dd8dcb64e6c1792051cbe85059bfe80268cae1db552e58e0366dcbba4106c27ca9e6bb636e30d01d12eabc52c5db145fa01e478dc4e6a8267126204f604c2f712e4d95c4b36059fd5991f3d496f43f00fad7b1d8054ba280858d31332aa011ee18ad285f340d0ff6173545e2800c723e151e2cf090c9ed7395e0d448cb70bd38c9b34027c166e29f0200851aea60707fcb14d883fa724703f39e2726975f1801f4302af299301bf838183ef6c50940f3e65c085147d292148f36021b52c05ac0ba834f3a6c6a19a640cd10c00000010006310000030402c1c8f4805e3019d9f0314000067c260a65ea00ac42ce79432c68011000100000100000800a700f8f44f749763af405bdd0fc5ba92eeeddb0514c1e8fc3df9b244eca122279d212954d994468cbc52aba95e133c64e7ed85f4323181d2b280e7c56d500abea0ccb090bd187d4e6d477c4a91554f5811233081909e6049417446045e2f8e198b8070d1e19999c78cef186dc84af3b9e97ddb5c1f76dfdf4d83f7aad04935b2e48bc525bef35bb0a9504333ece602fd66f0eb0582a43eb9d48ed9d000b9d76a9842c4ece1519f06ec1b5c6499927fcb10b92b381ee27430651d505a1956ccbcb404b15576b8d06a4cde37c670785ff59d1043908d55877a6b5da82b53c9d67d63b0ca585108df5dc0e97b707bfd3c38691715396dd5736b03eddc10c82df34e5c8fc9195cb248cfdd78cc40c9d8ab076880dad5b6e93ad904588e52f99f29e4813c48ea7cd2f1257214bd3424c854b080b20276013d4f83204e147ad60f965a8f4c049ef50b77ca8d41b0cbe8570792c35828f6cf6a2ecc03b6fba965460972648109d451c8c8fbee8d5fb2a0a5fe1add0b1d66b4dc95540506bbff4de448494c7523f6a152d7762f5e6756b0211a065fdd13de07d897e8b7b26db2f4f93d5e4e162bb777acd13be27df9211f4fa7f7e92d78bd9b52afd805ac8a8a4ac73ac31717d24ad92096e67b8c7a3eeaf4f36b58854f529303cee02f1393a9ff2245a1ccf981358db7f4470ff9939a8d383a773877ba6bc71152fb033b117fbf59a51beca5a08c2529a875832e9d8a83d02dc5784f9c56e68d36549bfb45168636969e6f6ffca9eeab880cde1809baa07b667e7e5c8f6513a2345227370bfbbe259fb8cfbb1b05ba6d717881b7701c6603e79a8c85d1dfac5abe23c501ac2e522779b50ab724e96bba46601343098bf72105c07ae913327eb0206cfe8a272cd43b877415799d734bb52d1f2139d39ba827fe34e8fd637381ce32bc05ec5b262ede020bf6c9e386e988c301acbf84e04fdea8a9d218cf6cb6fdeec069404568dbd99b087819e0fcdee6c1f5f23a0428b9418648a405cefb7bee6e7cb6718c129429757d791fcf05ec6e124feaef69331d08508228a038cdcad8944e9eb6f185d6f677aa0db0abb2cde3af6e44fde603b98342862d6efad88f00ad82f5ad5d4bd9c132b5fe12398e31a0417d4df922c65800e1a089c4dec6e8f25e59763e0c8e5e393b51b2e2a0a8710ab4c122380341baf6cca66f706f42934bf3b4eef8b435df904887dd4c3b4ff58ebad3475922455bbe0a1e8780cd1a401a48bc6079f0411174d800d00a85bcace1df02318b65b84149c4e193f6a6c01ba7a3170345ee6c9655a5045cc4cf18b8a29eacaca68898dc844053c51d36c5f92e4137bb570eb0a03b4673f07b07af6022a435efc69e4ee377d0ab49dc37bf567f6935b6e2ce7f930859a5751427ef938cb86b00e4df96f218a0fc7dfb668d76f27bfc55c7a94763ca8f946bc2801a152bf9cfe3be95747ba3ab487dc5cdb46669fa9ddd99d22cc7bfa1e1a52eabafc30755e047a75d1f5b989eac6320186c279a4e618b97685f7d09bd0032f07ffae836855c92449f4d4b622f25e30393b133eae186cc4d665a8d033b1d3915070bbe57736ed1d7c9b7e9b8e2e4564ee228f368b273c7967e75f1f002b16e64db13d5c3176e823e50afce306eec1516373417d8553461e4e42ee0819faf93592bb2c073e659e8bd82e2d30dafd755d1616a0f59ffe06c2a72b3683d8d1aa3cddbfc96f54e7135411df08f46205350d270b2e6387ddc5ebfd99426d691b5c009696dc209ced1e6cfa2795de6564e5bdcdfe6074b785304edcd7ba197a3a2e82fb903fba8e943b8eb23e2991bc769e637f52f7fc5c03b63e715176b660e5bb2a9d7bb4f94b23adf167c2c73cae51bffa8e340b9399e77cb18ee2ab2fa5a145cee3c5e7b7850f6f99c4a7e8a339629a87e0d30996ed11ed7ac5c7d81be8555ab14b1cd7b414789761436f49b27e1498d31b3127a004433da30742003f8986f9b0c54d7a1d54ecd8ec0449a8a685569893b14d7c5830c20478e6b3171b1b675722f09ea4cc420234daea67d36a3bec6a34be6f9b92bd6901eda3b0afad977df666bcf0250cf1873aca6b7ff697307adebda7f27df1b722af821ee9bc8deffec6a8e0ef2c25a5b58340fc59dbe01345ec54f56da5cd2c433812077e9bc438a71c18f48abbca3dd9c7c4a8f547be9981b8177fac38d87e7357e4b95d4d1e4dacac812020e47c454f61a5a6700aca670c6aba64e3b489b6abd3bcce08dbd1e918d4aafab7d0a1cc8d460ec44182f2305805772b586e35ed9864b304f348b38c15745238c1501aa88c2d16be3dad4863a83a6939706bff6ec6a734f9802899dbd0a982a3175f07b878f38bfbb406c3bd6e69ef0081b8ea1b61a0f08ecdd2fefa4b620b2c8a25dced290a612a8cdd0c5a171073272e2bd6ee98cb12d7e8fe9148cb465e09eb14bd8701b74d90cee6399ee562479fc5e6664f8aa613070b4e1a7555ddd4c46785b85c069e6960cb2c202f3e495e4057d49b5ea8a9295da4841d79ee765442302718126e417824ff32c06b82c91d29a75a95868023b6125d09a56cd6cd7838295f3ac76b6b10c5ab930088f9694fff220f4f0a4361b92c560add9b16b7e438054d71b74cde4d5b0de37c4bc6557430daeece45dafe09cd3c7f0b7a1c37583cd39c39f210806b78a9c29b7112eecd8e1b59d414e4ffd9cf0a3e85e788c7b5ff2256d3ebda0dfb21d8af80f62fa566de3e39e00c8f0b7b1c60499457710a05881cfdd61ea0422fd6b80d20bafbd925eba7f323e18986a5405e1d7048ba9a86411ef75ed5e74d773f9c2f15a88c0597db25b1730f566be49f6919de82eda4f89e3fb7994f6ac94fe0c8efd9cf99d85c3288dc16f3675410cd08e9512d6a1ee1ee9731256bbde9b4566aa5149d647df6eef5e1eef2679fafb90692ac6215ef629f2fbaee2481ba00b3e8552265f380965f92ddef9fef4ef2d3b77a8ffb73cc0b9675c3d77a9652f1e8e7bddd755b01532b9dab14c9fc2ff49c37e07d25a9bf3adee26fcb6f2c68bb51561e630ab8b37837f637a3e2689870b8e977eda59a93d8ad5749cf1469fc7c7d20bcf735fede7b8b759d9f48915907ec31406a690878c63c27700c9b8f50d049f5d9fce4f6bd4eea8299ef665c65538c4fe3aeb93293e1e8ce26f452d2c6d30631fe1b28bbb1846b95de42946141d44734c8235fc6c17868b350ef343f05243a5f700e56068b85ed2cfcb07d83d46ee00ee25febfc613fe9fdc6e42d65b350d6b018e4128cd7b9163fd33df2259db38d066491b04cc7cac7dc3b77414327eab17a24b1ea2ae6b1cc3fa511a2a6800a6ce694f6d521e4ae13b5d2f850f4cb9017c8fc73796c72710aed409fbdf9cd2dbd8a0312531ef40b9899a10ac1e950d19839e6a566ac7e69d639e9229a131782c1bdf7dbbcead0e3d1b799765ede6658a6a8c88a82a7014f0e9bc5e535bdb07caafdbab4dc3f18866cfe80e0589874a39fb0f62793946b60857fadab34b201327b6e4faf88d7e84f27a5bc4c6c63f70b4f6683e15f5c1a2dd9fee465a606d833ce7577da91970ac811b97b0bdb92b15c68d7425e0857e84e344c27f0bb9ec3b031b3ddab9d1e58665d2402085866ac39e6674df73bf21c18de0da3f8700f230e1cb0df2bb6fb6504fd2ed981d3225d372d75dcc54110a00da6cd66db86fafd326d4d743c7b9ddacd4b516d6a0f21e5b3302ba80fb83366bc7e7154e10db1705c211a5958411755af089b1be189d0ed9e94e70b200e3ded256f924c609471d2166819025882568c718c58f9bba18f82288e2de2e96f5ea1e2755c6ed4821278173816b3426909273a02e9d5f9df285124fcf99799472c78c8ef5493c918de5ebd00758ef107ffbce34c94c8f084cf879685d9f0919ab0131f00e38f8fbbd19d54b6c9f45c1a80fcab86c0c401f3bcf8f5a57e0167dc3f5e575cf543130d5f526957d1f7424d60fcd3d871ce4f6f7f5b5bd58c669d698db8dba4a9d5dda3be5a12cba8f6f2858950fbc91b84479149d2f20331047eecc3ce07a347efb75e53b8bccf495e588d1fd4304d419ddc384d1344d31f71986aba9aeb019457bb59d99fde26cafb7e72779292da9303a33d8833447530cfbe1e96fb76ad3e55687a0911d62d9b8435c74869857138f920952238c4e0718e4a93208ffebbd90dac46a4f6d7b795dd11ba9c4c9b2ef2ccd803107b49efc5f701cabcdf0b2b7db631311a780d3b38b151c4a4d079c160c99b03a30a9afe97f3613d976eb419055979129a7f19afada077676aa3e8f0c7765c15781e0fb608eeebfecf41c32aae93d538a100cfcc6057d42c3fb23ef63d2a6b71c3328f51a1a103bc07198e9cc4ece678de4e8001f192422cbcad68b459aadaa9c7bf778f7bbfdf56c96cd2aa038e9c6c4bac2b1ce1d3e5f87f36072ed0f40fdd3d8697967c3bbeb6788b15ed638e020308b40df04cfbac05d24f6d03f78c0bad91c99f1f47039fd3f11dc76f2d9951a40c9c0926a7b9f311082be1de42e2df1c91235a4698a6e42e4c668ca4caffa9a1a3b1b9f63d634c823541d1dff14c289e7503df70ec75e4e3efd109a6aff2f78c78d8b185864b96d2538f1e6f7e8f40c4dbb97bc3941fabf48abe15eadce671aa44696b88710f55f41dbd69c0583db875bce0b385f53ff768dc01d6fe1a048f86116285776b1cb05620dd8acac31ad93fce7e55417862fc4e6497e7bae9e52f75cb9afe5f52a6bf7e7810e0dca5f7e36ad56fe953a06aa6ec024f01ec919b767fda7e332ed7d45dbc7d6d8e84efe827ebb0119176c0a686e17093435114eb6e0ebbc112db2202127ae830f5101589cd4d323781b75fb75014cd870d9e7e14a6da975af085b3ff9f4a471c1b2f842a21daca19175cc98e761c9ccd41e5d12b4abe0f8fc98343125b1fd2d6e106800add5cfbf05cffbca6f8bff30c330912dc568e8473acf4489f9e687abb8146f69dcfae331770a50055ccd334e77ae197d3b3936dadb8fa6c0647c428f9d075ef8b829b5a3dfb8490c3b4f4c429efe4180b3817283a439a9704368f1879823d9855030cd225bf23b5b2664a807442259cb492b0d0b3928b774c7369a6bea92687f9b133b6daadbd04425835eb5fdbb77dc2ae9103ea797a8974d634f40e766638d5acfa1013c491ef84202375b0a494115582c00ee8c9f560e01722e8d3b9d2f1a665e636ea0f4113e50ff8ba64b54554e557e23c01c211d321af93816c06693bed1f09ec72358c9cced90aa0cdf8e344fa519db916d85feb0eb2e8b0f6fffc33fd78ee3d60a56dc1fc1f8289fa4941ce4d3f65016cf8539994dafcd31a042c74e6e473d62f024be09987d2798379ed16ebbd2522c8a8e7bade5b9875663285f9dcdd9df590a48800777be2df8de1a0d4457deaf6839c2adc24e887aa829c73b59790baf57516b1e98f5bfed69e569a91530e7fda0e41c307d3a19ac18f26b5c93277b3419900aca76eef6c5162e52e6435c66c7b32c1d067a41c8b86ff8daab34b5d7982a46db43b5b4a68fdde760b26635b6cc2f156b322a155d104208ee22e076e7763ea02c2ca190b6ea0282c0cf9381cb2c8bb28ac7fb5c611c267dd52e17a6962366baee02a8660c3658a24463290f8bbf4c2839e9592dafc4ba849ce17cd8ad2c0d742303d9a2056350d3322d0f00d5e0ffc2d36acba7b5461d16b0b9d3234b4b226cb7dbec9bd700b87c93b7290dce40ea37e91b466d9637aedb0ef6d7585b31416c2329b636cc249c11b3566149efe3385148b96cdd40686caaec702e4f975e9e6ae1f542aef86c1200e415ec234956661e1b6460027a4a0ccf7cac3f808f49a0f1284115dba82e3009fcd1b2725c4ab00ef787be47ca8484b068d181a9f1d24ffb884c963191d3c866c7b8a178f3c6ffa07b8291aafdebc50060a3a35d6f793294721d0a84e0de3b09b191c0431ceb9b6760086f8f6a9a6862a184d13352e8585a018daa5d5f6c10777d66d80fd5cce982fbc9275b724ef3918363b4ed8ad8912f5c1accf8b2bfe4b7d2f9985dda70706317fd27988d493e126f4575a69418645684a9a117f04fdf5ad6581dfd15dfb534388948e75e307d80324f10f9188c488c0a930f83166c615d5329359990e0bb9df991175ff6845379cce92a369168d02d279102ae0fc6e10a6b01cea2d9964105302742a090c21741f0bc073e023ac1299ae6ebb80feb9b6e1bc106f0ff13b6fd20e24f048cdfba1a83aa8fdd4d3f87718807bc9eacb604a74464213c6b32d86bd964b23d1e1db25c0dba01f2880f08adc0e8efa286225ddb7b8e30875bb4c78e8557fc5784f44e284cefd15df06b22e8aeaae671b56a31d6c6ebcdbe3903e015611eb3fae1d6980863f9e301a0fc0a8cd12f4817437b26a674dd5f70ea9979676bdbb4676bd84cc4210b9d1e9a5385f961fcf679bedf0fceb14e86fc59f98c8fb878ea1c3b319a09c30abbd511c23a31e39478e82944a46c032aab83502ae3fa19ac96d78b5820a4f9a54c519fdd5751ea0a93609f9f1a99cbaedb7e897ff5d4021557ec2b858b4ef24f7f02742439822068eaaa2ab233b93d5de9ea80b7f73999cb4602d097acea3447a4923e9c54e0838a80d04fbb7c023f504b30e6bd21e8a40cdfc472900235c8349cfcfbdcd86dbaf144c3336fcda7999f714d422f1c6d8e12df2d9cb76d07e5e068c4d59b37ea7879270c0408ec28d7a469ad52b785887798126fb018d6789b3cdd7018216a539201ef6a59791db84e229bdc2d8d4f4aa43586263c51b6d76fe141d845378e854c6c6adc94ebe0dd90383de4b4debfbf90ca98701f41b1ccdcc24944383c3724054adf664c30b5aeb5a831e5c9c867919113e518274270dbeb0be694ea0e867cec69ceae2fb7749f1e6f6d5faf6aa089fd4d295910f4f2fd005492769571536d4355133a81a778e890d3e026a01d164d9cb962e37c97e938bcacc2b8fc32ed4eff98a3f32a2ec9e0381859b1d9eef75435efa6aa7375c04db51549ddc17776e86f7cb789d30dd2dfde642eab63e7a11bdcb459d2d7d3a9cee37be23519a1fe307883f77b1ec20ef000ebea6891349e536c4f2c68779c9cf6ef8e7f0e86af2cfd439aaef8975f952158229102a353341b620ea1592552519c4b541631c9b7f2ef34c83b2ac239daa275f0a57f8af77179c07c0143f4f442b8b9596ab5c281a4667756f78ed4c964f5d7efc497a894d1307f3a739fab39d6f07ca33dc712f103c57a3825be1ae699dbf1406e291b3bcc5f52db5ada52927c03881f2682c1f1a5b687e9cfcde48453290a5958c3ab9de9e2247afc1e9d0aa5763133916e33f6935034473fc7c8696273298c25b80b42c3ae8a9ec812f86ba924f66dbeebe65ae0d348043700d01381ef4279eadb5dfc932d65470ba6c85cbaf65b8f04595eed9dced4250f82addf32675fb28d3ffb09462007cb59a8b4e34776c8a36c130fc09008bb9069543298ddb30c6ad0ba4403fb2b0cd6804f3ef6b1d01337de0774b48094212ae036a0471a240a1f4b508d0661b95181f262a17af975f6e8685981eaf7e2399332f5bb67c37d5ec09157f2ba146e68109bf933b4372280292be0dcf2c6aad6f16b996c6baae6ebd5fe70774658ab369d08005e0f4baa50ff5bb9b86adc3a5185abe973ee0e2d979796892834200b863bcde77be266f434c402d289a644005627cfed06b2a1b30336147eaec15e458f89c4fd9dd008d4c3bd729f9f2bc06c164db85075e3c1a65d218a88cde50a7508a07638e73f5351d250b1c416a615a862e4fa56d7e4d1257fdc377d65cabf7d9fbedfb4ddeec5af3ecfbcf1da743640f648f5fd916a6e605d2fceeb43908d3616ac107473e43b442b70f4a5da0a8a821fd2402a5ae5187f01a16e3cbec14a82c52f348698b56ec1ef9f261a2ccfbd7ed75b9128ad72181093a160aa0df510eccec7c483b1d9908790f29f15406f9d6ba34da3d67a7e0de2b278c12d89ffffafe41ba7c7b3207d65ada3c00341868558e72d39395a145d5a7c364096a173ef2aa8672088c62474bc396892290cdba9665167bebcdd971e6a05564af8a84ad77d0a51b9928798ebc7fb3914356abe8aa2b49acef8f06443ad9d5d503c0266d9ebb02e18c289aa3e991ff10e8c53c6c1b7e797f9b5e383dac898af001a1188209082c4c30486d44415cca29b8fb0384c7a8928f71dca9cc585d9317f546b537ab626b5758ca5eae67eee46d189131197bcdd82a5f98d50197ea3402c0e48983d903538839f36c49ea7651e5bac31a623948b9b831a5d27db18c884ab7366a92efb23a15ea626b233735067b81d0cd0e73ec1601ee46523a27066519a70a38d6a59a4198680c83b176fd38696b30ceb8b0d2e4667d4440f0a960570aadfaacd1c58650f0a4e743e716ba2a0f3c2e58171a1059ed5e3f18bcfc1547e3c86fd5eb71fb241b14fb97b8bd7102323f8d57ec52edef74bb2328cff10ec67c83e01c633fcf24b54c1f07ab7cbe6e31013ddf51a0316f53b131a935ee005e2d3f37e72ef45cd11c8c50cc94a118584b6d3b0246480b3cf8438a9bfd98f169b572fcc8116217e5c4b48d5713eb4288738d516b1bc137231dad232ad5159eaa01290586b5f53588cc58bda8ae6748e96f7dba5ed591bf5bcd7ab32a8b72da2b209888be1f66626198e48d93d987f4590e525133fec22ca36d116802c44bb4caa3deb203543ea134825c881438b0099517aa67fd108d00d661feb018a02ca66d94639b999220d8ee5e085a66dc161fccc9e2cecea472ec324768cdcd4a38a47ad4141a1ea3ac6ea031af42ac84eb915eb9af1fcc5f15a19b1638d9390989669ea69b336d5aa01d88492453b0d681cdeac08b722ea8cc17d4135cb6ecb13a2dbb34f584290ad5ccb2bf7349bd6c8c723c26348a3085ffd0a6abce6f9b20d39c05c1ca45201feb636127a62337bdda540ed5bf90d1543a70ae39b8cd4f2225443fd020f4f44c2dac0e0c90d1ff27bef90118ab21c19df9ce6b904cdb9c9b187d084849bc5d09489416a2d709ac83ca9f590a762089e198d48ce9b304c0f9973cfcd4ca571e02856903ac93d160ad04ebcfd96a3ee28f00042743c18939a531dddef4b5527e4a07c56cc6bf6cb3e39ac189724d817afc820a519d3450099623d3d66acd81a7d7d9d056ae18cc8a6095f0977bdf8d289235440321a1e81abc6e5e8b4534413a40addf060d5ccde470db94271a76a16f3e788614e0c05f947bac408119bd47e2f4ec6d42df44b71a00be50985315b17c34e4f049f3eceb808770a1354ac12e636996caf1ade8c94bb8bdfded7fd1db2c3cef9f77a5698900049f9b871593f148fdbbb71aa958317b211ce829ac9006bbd3e4186ddd04d9d962f17958976effda58e0318af7bab56119f97e16640e6a9a93166e72b001396bd6f0c97f3a9f6c5ef5043f72f44cf5165bf0cc93338f39ffce69bb7537a83b5892a3091b41e6ef091f1d3b936926146d9eea79c6b23bc85696f7491701c9341e57b137602b6355927dd73b22a2ed1f7bcebeaf88ffb15e51cda3552a1f85fb02deeb55573cb3280004e53a00a72de4ec7ad3eaf69a8156523d925483ca856300d8d86d8a758a04215b07e9fbf01788f08dde7251427cac1e084c05245294110ba79a35d35367fc74278f60603acf165d7963a642c96e0ba1f180afbbda5b556bb12b4cac23cbd0e5a222b7a2e32879c5378f558a001a23e2771e8448a86b661227aceb67aaf7c8c52a32e7543404514d7e12fc439cfc1d78574a129a9290ff53bde8c9521ac1bf2ba321efa6da882374797db5c9287628a90b70f3644292ea41a06e01d1dbf909afa7cb406a79ece2aedb3c20406bd0b3c613099395f29083209ae4b26842f6a0de2c98b091d0636e74f32a25c8fcacbfb796df5fdb18263c915ca208c43ce37670bdb8e23cf2b12756b57faa2504e9d5d2088cef971c9f9216f6f3232ab7642b01cfee358ba64246fff00706dabb2b04b45be1e6e5fdb75d2752f593a3a6fd975d26774c657389a81c06204b2c4ad3c13028bc9baa5bf85c8a720827e4fe66caf8e0c29ba0898f22313571fffea5c6297e0b2704c15e82a597e2395077ce2ec4fbfd129fcadf06c5bd423e9efbbaf73f2708fc46961f7c0a948ee24e2926af4151b41ae2cb418f8a3200efa21dd8e28b0c86063bcf162daff6ee4b12c556de56878b2d6a7e455c50a85587511e4b93d3e999255927f9c1443121258399ff822def75b99050fbe71d0f3b1aa54e47bb64eb5f9216fee66ad082dae42abdb97cf1dd1486ef49729fd897f067e633c4eca5554b2046b6c8bd08779daa77e913e4aa6327647c8ab8b7218847825e91b4605dc99f6d2ee0c9f93b29889038ccd0236d3de8cbfb41520cdd85abbd7d6f1361430e0e1b4b06b3ecef3501401754f18b04067d858bf9d6d36430ee61505cf5bc369fb9e4031b306891540f558e934385531a7d6038df5b026c2d8c2988ea8a3f1ed770aa0408efa0f5845b6fe68bde5f13fedd86e20aed9f76fa59ccb7b89d2bbc5322c1e009fdad4651fae4aed53f344caf8304a8b1f1760872b9cf340a22669ef7dae359d04a844e7026996c808101578a3e8d57d0dcfaedc384d418957a7e8bbb83e0f5f179be4b373b908a6a5f9113e418e1f443687fd057a318d9498d381337aaf3f1d1e859a2188892b69309a20070ede216254315a7d4938224b3a822fea4dcd4c0e1314df43f27fc1459e18a600e475cb52e214022e30fe90627a327c90ff903f697eeeb0e5753c2a63d13816d83ce57e8e118fa803337d2120ee4f1072975efaa493f94d72275ca7caea24930a6c4dbe951d3f89d9c8052141a71a83b5213d460829b3c8d1730420b51cea3d4f7d477f6f37ed50cc33cffabb70f2692a7688a27474a18373b5a8d750c8e529953725fd8fd8d40a578ed99a2691cab9f6c389a0f337b423f1ccde067705db3aa636e11807b601afc5430a2a599a58574fe9594410503b54cb73dcc2e30f5e7154d3669f99d633e1c941bafa10f7fc4776f1c9cdacb31caf1611e6ee2657452f8d3e142e5fef41d19297757f540af741c0e4016481fc2ac740d983617cab1a1dfe858e5e387ca2be60478b12a21ef0c91f67e6e3b151313caca2ba1457062b9f9f22c4a7da6b911eec273da00d8ba2d443b110b446b9ecadeee087d595ac65baa3b5bed644536ea347c451490ca71f77c8a2ff6e0a5d1635c8cbe17fa3eb844a9ea270242c580251e33b98680bcb8a0f1fbe3f3f2d6dd028fa50d9f882721634d7c9d35d06c018d40f6ea403101ce935609ccd9ba53e12bba09a323cd1bbaff0b35f07608c8cbc8aaadd7375157d31765545884b2679ea0b44e33cde486043f6557369ae3e6d0f343a9a42188497ca787facf176fdbb361b36b3905ac6d8a339598036cd4a458c2ecef4f8d2424f52b8745093b1dec5c8dc68a0f2bbcb66516735567f4aee515f2243846e46e545b6d5186c71f544d1ab8ec9d47f2b87f6a57d72ddb9309a272ed05fdb7fb43e929fdf29ced441cd7d595b2d33f11df07b88f2bf2d17cae83bce025829647ec1b0aa91862b7f41eaf1bed2c8649a91a3039ea2f4a5a4815eb946905547998695ad9a6f38a349330859a59ae79147025b102ade04dc07dce47f518e2e49caac4159decf9a1952a9a51db4107a701138b60c610a620790b20657e030060bd9679effc5df6b20735f057eb6f64b72137679ccc18cfa166a31a5e39849547b1a2a7aad876bbf99c82cd19ac0c254f1046a942519998fa836344140d5833fcdff15a99b6602143d05a35ce444755c847f183d991ca8ba3fc63d48c92c3459593e8b26a13065b83ce685b53c4352e4107f1e487dabf548249434c00e71b5a33b0a81c1e41d0f09f09f1a2624bde2863c7a3f7cc70c917f3b96da07a01d3fcbfcda7f656b80b59abdc3ca0111b58714c755253b3b43fa2b320cbb681999245f42b86f1762830f1a7f830bf3ee5a07a23dabc663aec968462bbca93d36d123d18d5ae8e79a5955a931eeafd9678cf2040e054dc0519a5ca56b16623b2895e01f2c45ec2e000e254031d193c5cff84a2461f2afcebad71635fdcaa13e42951b4b66d42e12f75aa24382be5826e589d8b47e111c762850495612efe49b47d4d62fec5ba04bb80ff55286a5018a7805e80f69cbd97450b51c1ae8acf6122f0524a8bfaf40e62aa5ee671280518b36092f9f230c1311067eb1e69a9c0f2461fdfad3c2e65796f95c4b60b4e4001180ebdbc4abcde958a055f777097c12af029b031f0c1e722d6becb4a0d025f4c4afd5ccb98ee20c9c0d5f0f2613e4fd1244289a037bb5fdab53958199b8f24c1db064c503b2ce193b7ae5e37d83538bc21c01c10708db133d677f2b62a5d0b152df13e5ab8bb64d10052efb5fc2c59eb844f50aad45d732d67ce8a0451d798670f31d7f8c63639e5e878cb066cdfeffc43dadb3c1ecb437b044cd69509edfdd855a822fcfe517bba6fc15e8790a419ede33873062e24154de84fd7db63fd521f394a5e7b706b16970281c45c2f505ea62e5635ef6c7436c3670d28e814ff4ed36772dfb52622f931b5771160828f752afbfff475b9b7049ef1262183c448dc027c5332b186e31ee4736499842b56187df18e8986f49f0a104b2179842cfcff6fb9aed74066686c2ac0f224a9ef8b30f41c5effc9177a4199771965531e15adbef7f076a3179a7cfa41e06c36579f1267fd924b410b6850373377496ea9e0d7bcb1cb3113096183be2933e7ab20b3d7fb2ab7fb679e33d413af992735489703c6946a47300acdc22687e460ef8601593d8851b972dc279142604493c649fd184b7ff24c056bd7bb0b72ec937610e892511db69f781eeb7651309bb5c380fca05852c4effc0302250c596bfac80f19805242388f0fd4c80379ae3f01e63d23c5066db108472b547fa734c3d2ecca5fb600ca760d3c0d02fdcc670d2b0b719e8c9b087fc56058ae7aa091fa3366ac666aa11dda6e10a992d07fc88deeafaca928e2078939c852c37e1a148bc3852b1f232f5d80439fa10b90a5fee52a3fea1e2e9b41180db8a6400f9ec32389d2f73f77e8640d99d6cfc5806d0f6a26bad7483528e6612ccbc6447b7829f91cd19d18e01b2cdf9bed303b95e5c11241fdc678e70b884f84d46de4f72ee1d8ff35e67f60024da7cbe13224fd9fcb9827d3a0c8b79f92335b677517cfaf7375a8a8705ba8d01495de830273387be248eb93005431a8c42308d9dc31ced755c5df150b8c570d9fa33f357088306c925734e740399b526c3b692e20288f4775892e56d25959cc58f623c4885c9dd275b557fa769c6065f426411a6a456726cd9d24090c0b617b27c601ae32a57c9ae0e6eba863b1f386692331bbe232b46d0557d49b117049dac12b044726d225263a10d72811aef3aa6bcf86333321d65467f498cffc059104499a35f6d8135f0eb2caac984576f2ce1669fed41f02d542f5aea48cc144e6f711bd1d12fe6e2334059696e4ff6c502e0b667789cf1be72e73aaa4af5cd9fa0430f1d3c321007c5304cbfe64130ff26a7feb75ef84410ebe90ee7d04c728ec8188b505e72658744149bfe4d9d2c2169301953ff06e608d73f1f93969562231b0b743d89e5c12cdbfe0389b442e3c5414dbc4f510033b82207937df805c2573a1cf0b3feed36926bf98fe37c3049f25db8781242af61715f03d7970a1530b3516b1d95236906fc82fd742ea161e3563a04996f056473e5870e87247f6011a337c8291726ec7a568531bf76e8f614e012b2675e0a0feaeffef3cbd040fe32f29117b21a9337f5ec495e2ec82d49842b2113c1ac4dc1e47c5f13f5e4de7615216333b990b231cabfcc2240733206a8199b04fac1543ed8d9b58527da435b93ce45cdea1f6fe763bb3dc0705ea8e70e788b3f7d7bde892bf8e93252a2b3f54700cc31d25e0763d509997fe89f1f5023da580ce58d86acaf58ea8632743b88c76f82e687b9244040b776284d499f209c29726614635dd37ba61add8fd264d0eaa8ee0b7d4ff1bb46826918e191c6bdab1545a56800b2355ebb3dda3219f1f642184018befb4f082330bf221c10696068087c10a8c5472aa1db1f1f1ce14d2912cc31a9987b02c116a411ea37047af78cfcebd49d2187e4d07c0f421e649abdd5d682892674924482f177bb5b5023162228ebd8a53a222d816ee5cc4a1f961569e5c06ea02d685cd01a7fc90e0d60a8d26a4d98044875b78b39189e7cedf28b560928a268cebdf440aeca841c1edbd23b34f81c4c7d6878fb78041183aacd3a6bb97283af513e5cec19f71f947b2c61f064e46fb660851946db1996320e69b0a5a9c7267dc19889e3d19ebb61265ac5f930c36e57fa0ae57ca1efaf1704b238fffa8ed6508c6b571796d1a4cd22ab01b277420e1c43bee40a7b7fa04e6c3dd10bb2b455621d6ee50c19fa6d2e7be65a17ef8cdf4fcf07f6343c354441e4466961dbdb1595a56d02b22b93a1e2b9e4cc9f44c1c7720f260f3c5979e49fca1bb6dbbc541589609081085bf5a30b9e76d5807d2b55fad48feabc4c693251109c868c4be251aab5f1ba727b6e6b2819d8161e7236096046300e3c1146bcb153733d3be0401daaaa1ec01973d6811db748b8ea36e95714b413295fb147b658873a5b71356e5ac9b152adee127fad4f989f8411631cfad92425c9a0ee49c7dae23fdf8bc2d55be9df6d2cecd3c8d2722a0cb392ac504b8ebe4531a60ac7f6f3730a6488267399db172f5a8c8179c425d0a0819fa01aef2607620787535fc9a91bcd1e0c8b1668432b918d580f9dc906f5dd5974a0b8603a811a49de3fad8d0353be5c3abaf64477ba2f02e08bae5621483e6746625a81798c60f466c852d9a17cb2b1b44ed58760782f02c180bc77502ec480f9358a95291f0e63a6a239055cac2a762ba0db06152d2238e197d7cd5b468da4f950d8f530438d22f8e052bebcaf8421b654fff2634449593edced35faa7c44def3f99d20a35f0eacbd479e4bcdf256b02c694ab5816b1088f85fb554b9f1d85b74e9416c9535ba8e28d166412c77a55838bccfd9650e46d73807a813645d75242763dfdfabd8d9fb543a1d5905812e1d2a9a75d2a3ad8743c773baab009777e1afaaa970b0b101a9c36f6c8aede02f6219d98caec7788076c438f00a87e7f809d39449f96f66475d1c21282b5c983f6a8763e4b8356ae0eec27c551d71d15d6774768d07d0c1179b74e0bdccf4feb3bae2d19fcce886c4306c2c7d3bd0fd1ef2e1b48e2453d6591e55121704b6ca220172163148fc7ffed5193df6103dc12e3932b2254f4fd28d66937c5561dd2829f7dd182be743527c6d8489a6caabe24d4cda21e93ea4afdeb3a4de1176d1437b12d870adeeee01fa67426b991d6bdf22d028359555c07fa9f449fb5ab37c5f028cc82286f140aca7858327d545c7d6111c35b218e8c53be5a922edc97b0dc023654733dfeeef8cdf57cd6c1220a5c633e58d6b28430912f9f98f41632d926987b2146580bd647141e4533c5e8cfb05cfb0c06f92ef8f2634524ffbd18da8c87a5db6eaff322e3937060b22d182e5204cd5026193d949a725e167dbd4e1836d1fc685f775e7ee13118649de45e6cecdef147e9877d9f40f1e7271646c33dbb7929b00b90125e54a05c74d96d0bffe5d1d7100f760966102b0c8b71fde1521c7f58e8c5476c334f20489afb8f66f0ea06813acf1bbbe3d43ec636eef46d64774be1148558a08a7eb5c526a2c1c872529a38ea6dba38c4a0dcc259ee566c8c45691650d72b0c19fd8e625f8d40839111fa605add53d56300b4a1facdb092593cfce6aa54ee43b4ced388ff88652e1a558c0c62b3d9eebfa20c3a67ea5db7b49dd860a7f891d2f3c32988cd537641ba7e021f48967684301e60be7751d49f8233d9d5dae543d0c50953c6876be9e1b5e7887cf4e9d8fba914cd6d5ffc3712724c4e2332e808c9c5d6c24d4d322657c284fec0ade482c934304c7a32bed65637f76bae0b940cdd8c02389f319a9048331b5d9a7cad1c91f67daae88b0f149a21905a301f6f334d2efb2719518c3baa138f4ba0e813714c39826cfc8cf8946398b6a1f771c1a75f659a121d330b861de5b1a843ac91ee7c511d4567808f7dca1efa5b9cfc4b6cef57bf0805111ab18ef89229c450ad5f9c3e82cacbc217fed18d64c51f211c64f3ceb534180155bc612accfe49999f3b9674133ca0688808f3814eb94bd227ca8d1e2bd2c8905e641a8f9a99faefe1c237018bcb8146fd414c0b7c77d3b3d2343419c58f7136fa9404a73f419042222e6806e97d17906ae80b5d54113bdc9cfabc2b0c33df58f393c06ee869f0cec4fa021fede753f09cde3ee2cb60c97d53f03944caa1efad2eab73f88394de4227445aa90b81d8a9d109c69444cc655c9371ac8f529d6cc2ea6e723c5991207c879e14b04cf5fc391211be1499e8adb7e05ee89ad43d2874b73194bca875a0b58392cca2225e90dc9e0a8244f9ae690d631d2cdaf4648ab0417869bf43675d5aa03bf1b63a81392978fd6c343bf604335ba6d854831ce84db9474cc70bc7baa770f2e6a591b8c7246b07340af04f8a7bcaf8eb0c1138102a446c563519814a8b15098f54bf0ad43867dc55ac9a5e0f61f5dd12b00cdf38b2429e59c8695927cc09f2409c701c843d4958465a04b52fb0d526d6853aaaa84ac7653a1a6300cc267c74854b881039cb7e132517777f8cfd5f19bdcc627f9521f6ef5593edfe429179aeb2ca5da375568643c77fea504fc07e471fc2c132d9809ff9c6c7a0d3331527739e72eee73046d767a4e2d9bc9a711c096abd769a58efa770d63176fee95fa45dc0c3fa88a649a573d68d4c06c986db44cf1ed6bf8bc87bc4f4eee6d343adefe0ef29295f72e60ae171a4c80c2d60c0a6daee5d85b0f32703452faed9326e62879e68cd331bba87092e28882e3c49baabc2a1826cb543e91bdffc2bcbe70d0c1566995ca865807c3c099342fecfcee3613bc84e22b9bb5ea88475d01b151989a26d1f451dbd70ca4fb774c3c20b2e6b91455716664a170149948bbd46d2477102d7235fe5b0e467221e9af3748088a53ec23e09f7556658efc8c5298989ec8b933e9682de24202446b879f687c2195af290e6b8967ad5cacacab3dd58bb5a5110bd591fd84d372752e2609444afb1db33ef3293845f7ba44782cfd4673416c6dcfa257c4d0810724fe0607b32f2061ab9b2c8bfffc4e986de31cd7623bc55740d40f84b82f421a89dd8f2ab9eb86e12bf46e882ecda12e62bb671edd1075e9658a35a8fb735e703790ac6090fcb21fc3eddd4ebd1dc563bfaeeb00f2df0b18a20accce13be378b3eab7a5b16ed570fd91e58c8f936886fa5975c9e1ebfe13e5678037d99eca18ccc69108e35276749677493d5a98788b1ddbcc4b77b2faa136fa7c6b81a85b77a4f32b4929cf632b50eda61099e26d791b226ff126c77ee7c832829509f0c7903148ec8cd51034d2141a87629e73d0f8b0e5c95d1d9771ac8213771660d5227a37f8475503beb47ca4c8cf8fd87315f7579d5e16e773166c2a7fb91af7219fddda9536a8b538a8b4adf06b3edbe23d6e01a7ce92460100e32d62d9491acf11b59258b9c1335a943147463bbae2c0b7244b17398b9ae8cab6b0c2c5fb84fca2572ad1cd9c004ccbfbc2543d8e0e42913126feff5c3bcba1765ce6dc400ad601284d5a0fb36d6309dd385188449954248c67e82570242af2959a8796e31f1e34566c9114012fdf9e0a12e429eaa2c5d085b683deee43af3458b85fe8b5f0a0697c84338c2d21646a5d41bc24147e1fa319845297e9a41e0860d7d61c20d5d240f5582f53b1fea723c0aa620b05900cf92fd3f26d5d20b41c8636359ca8e34164fea4d5cc1b647bf18569d73e12cb61764d3b042c31126546ea67243749b8f19a87042cb4a8b2ca78d2a13833067f6ce1e00e241eb16c366f36f97db0dbb0f5c33306d1fce7f91e2e0d3a2e232cda65f8186668bf32aa3dbc4987883fd0f0e7a45d06436ed559929ea8ca56041e9946f2282d2f3462fb59138c735a166a6f9b5e43fc1457d9a704dc9921d0debba2dc9e108d186340c03b4916b44078d8c8de3d718cb4add8f3f1306b69c72e770a8092d40b303232d9c51ebd27262c72a2feb0d41278ad4537777c9564f4c8d318b4a67732c23f056602ca11c5d6fa6af5325d229f651d65c4dd4b380957dc64ad71d22ebb96a4d5cca1084b34e15f5793f5517bebcd86d606eef0f03a6859da79554b9c504457412c8ae1c5b1cf1c28622221123feba0b75a7df81e8f1305b0c0f81342fd333059627174c21ab1bfeca65a6a8e7c9dd9e0af4bcd72f7a20a1f81eedf5f2ba8343e18dd50e8158664a3663a1ad9d7cb184316058e5d429fc03fd72ecb1d0407e2ffc1df3d2bbc00222ff7f288ee6998bd637dad43aff173479f5c670cbc12d5ef224c2d9e9ad6a9c0aba2a9602999c1dbf1208c878b464387db2c710b0ae51173340d8e86bbbe4bf8becef5bb89566ea316bf2a9a494edb4f7178ee3b1d95ff67e94e25ff5467b9612bdc5572d59e7d9a34be7ff5a6ae9508a02f62dbff681a7790d96fa1e3ba9afc9fbe5c616374b6279f1b1345aa51d6038b8a37f6cb56421f11981e152810600524ef3bf5e2ac6a0324f05809fac0174472309abda0aaa8171203ef9d0f90a971e0b98d23165e19d6f170519ebdb082fb129082c2fec6e0c27f4753a38097a13060e7748c5a5053444994b0870697b073e12e33e195503cd9933fc5c2785b49aba1178d6b1448ba1a9a2d9ea9c8c75e5d1bed171e5f809a1ff800e64fe4b2aa5de205b71597aea0508c6f3f527f543562cbe09e56276cfec2b22a6c5c90a73e1afd808140fcce9e5df7fcbded756c74a5b9039be4c53ecaed5f9d9118db00c97c90b3d848f3085547c55ad15561e056b8c51fdf70731f3725e32ca9b5d97ce6e69ac7424d749a78064f7bb154f83a01353f27c8e788dd6ccddf42fef5dd765a505942fd9a14cfdfc4ce81067450f0e076fdbbaa8b32c2ffa1ded323bb15e702f714abaf2e00bb3d2752a867153bbb0c2df8a21e6a29e4e8943b2e604674ea432b0593415a1935643108271e47f5d846832b57c647bd5560836c48eeac29c876295914941f67889441c7a6372247c49d4fd6671f7b66b25b300ceb21207d45359e672cf161aa3a4995724d0fa080d8a482e0c5735905e19c19b010da6b8df07ec11b0f2df2ab60debaf8bc8b6cb02bda1ac8aab154486f34f8b532c0041b2a97a60ef8e11112d0771525cb5c406afe48757a522eac89ec01bdc3122991fb1e5b22bf77f6f7938844f6c080b38d5475a9d97214ad9e267e1571acf0fc535ca8b6d5b9eb4ad5fb1a652129c957be7efc982c458dce5122dd75d846c52e2e454a11692621f9dbca76b4e6c715ae5d24db355477dee9e16a6d218afa96a002646d337c8187e9bcabb4de45b5fe26acadc925e00a3491f93c2cb5a52848e9114941edb7cf51fbbec6dd8174f12248bf38f0dcbe2964b6e0b8c7afaa06dd0bf7641202292deee043540ed573a9386e232bb6793e5268cfa127c0f1610c94614afd8d7b885776bcb1f8666b070e6b8a6b8cd8502521f8aae6adcb34c0cdb1cea86fe0dddc71610a4263063423e42d88b2388b88bd896479eb7ab267e2ef51df51229caa0a45b9cee64d5c632e1d6c078d52b756c54fa55504dac88e460671e68d541f295d7d162c4430b18ed24228b9373eb930251659269a805bf2824b50f655951ecfd84c30015634b048e8e1c57fef7333db9c2c2e82ee0dba9e23b0e9425836387b1ebba20adcd529185bbf9b81537d82bd0b073597944d5967b4e5cd6f0134775289964dc613493b038c044dbf49528722620647922a4bb3e49f3ae7a9cc23c5c37e271c91ecfdb94cbcb5ff22a242649ef39883d64b3cd8021d1606a5d08a3e1525a06595022882be0d1714e7dcc6e7035c8a98aa4b068a10f24463716d6281b7dd9fc5ffe158aa576f418ac6ae397d0663a1a5d7cfcb43d456089507d6ead177ab87d025370beb6f2944dfb01d98842e550db095eca97284e869fad34258011d3650d8f016b3e721b078f9d66ac4fb5b6b3bf765c13a58ff0ae9dbcf9dcd7b3e07ea3f9eea719c3e6efede95773687a33e1b4570735e6019117bf9793581a7855bfd13621fd900563539249bec806c8800b7491e2d2ada6061eb7f16bc6dd3f3492f84f33ca9b034a7623b738b64e673f6a13b26447f6b20b0fbe7b187083e6892b5251ed6f045d9cc0d74f09268fe7cc3e60016369000ad0952b27ac2068ae6a5d68046fbd9c61a9e22d5201fc51244837dd860b3a7433080d231d1c03653f23c33ef3e11eeb3fa2a86a5921a50f153ec296cb1991a1c33c487dcb82cbe50f1994d8ccbb031e85fa4929ad32c983ab43c8b808aa5dd8748e71530676b43f2f462edcf53e4e12e65a8de6467c59ef079fdc48d0cb26cbf8fc66c31c44b1dad04cd8d606ac435aae91de1ac53bc30201367c2fc048ebc02b147ca543c264eb0b675731c086b45a502ba310c996720e2709556f8b4192473447cddb62bf05bbe52f929016f5587aa073da6bc94b4aedcb0145483fbdff4fdf746b0fafa5a276deb2ba62c98fbee57b3ba15039e9f9a396c63c51f8401811246dbc7f3a4893f3e247526b3d154c1f2ce5fa13fa932b26f11f17b4740ff3de5fdc83ef44d42c153867cac049e153e82bed837c2c42da00dd0a16628878c5f0269656f1837607576c5d4f7d7f869aebf00258c5e13e41c0120ac3fc39ee0513380938ae60b2f0a1caacd788734d32bc4a55bbeb0603152b2603b491643596a277099815188b044021b1d26373e4e3d07ee06577c33b5752e988111f25c8101383c8ed70d3e0f7261ffb5587fda971779e643718ea55e3a0802556a3955a58fe8f065ec9540d0beab7997f3380dc440b6d95878a85b992101f47eca1b7a0b9e40c2e9b5fbd25345d663ff0dd6c1c1f99b96c995720dc7c121bf35d385f92accb033f9646d68dfb56c16b32fd53965a800e20c4cd52b0173fb2470de266228db2be0b2b30cfc41f68201201df442e0e732b8c0826e5ef5fcf43cde1c9adcc2e98915d968743f0cfc8a4f858769ea5bcefddf82a42306af5ea4ab5beaface6a845164566dd8051eb16ba75aa1fc5232cd6f1356c7b2bc94be4f6c66d7b9aa8aa19d991160b07f4d38284253375927884214f9fc4d2c99fddf5e7f08992c3a9119b88b095ffc9d5b19dc3409be83857b5e896ff273eb1bcc72d4bec03a9c27c2326f5698110c4d23977175eb6a8e81331627623276e2481affe5d9078e4c6dda5b6c115352ba235ef69816db9f074c18329baf19e48a7848f696d3aaf6d16d65cd90e28e75ccf0342e8b9cf1c9b0f502726cdec37b1ca8747ca3bb986a1b3547f7665a54a70d7c863bfe2294a7574f991f52e2c1a72ab86e557c683e7a9c2090d20d34f383bf2fbef8118d262a471898aed25c5f1006e7a780e0573e32184781d4e65285a020db68d849e5e693411dab810ac3629800466d117602c22d812aecda9195201cbf622eb14ef6d561b0d1d72305567a3c91bf5ec0da22b047d0abc97f255e45ae4d5650280a33dc9dc10bc37536b399f7de57c0fd316dd0e8a6e65b5bf151159fbf77adce8e89982d129af041f493afe6d0c114d5341b60f8134fc91a09f41a0781180f26b0d6a1c890f51919b0787fb677d6f325c8182b3f618bf9148c9fda19e0a3424a519beeab13b56d5d45a502abff177ae44d44f0a591e85fd2d6d1476ee8954b52d5d76b4c79d364fd0e148ac4aa10ebef90f738e0f17b1b92ee42ca00f1ef746291b69a31f8097679c183722be3725a6e2f5adc0e4e790380300c5122e6cfaadba289103a1a3044ce256156b4f12f80ebc31629c16b4cd6f6b232ee69b6e5d5f8c06fe912a17417d7166cf4bb7d802e2154ac86d20c40c29418c67813b4239d42897f0e03a70b402c7091d045fe9b90dd81204615c55fc65943f5d98594a8e688b537cf35049bb3fd3384e10ad37bcd9c0cac9321c7bd40dd0577a13c3ac86b3d74e5449f04af3c30fb961e60a2180889df60d44044e0f589304de994643c6e060990624e7e4001a27193bfb3a2e387791a541db29d3b608619d559650ebe81fb06c3511b6efc6d6ed7423686cb06037fc6cbd31bace6df742cc75c400cd69aa3c356d9e275ae733f763cfe58e1f48ed2eb3e6f6cba1b834234436e0fa804010be77916d84a1efde19bb8a368f97b056c360263098ef84905179e5b181eb47d43167f9d82848cf9e87ff9c2dba8bb3b6437eaa1f9314e8600b90328f41c8bda49d993121b784f12c3ca6198f1b0e8db59911e3ca474da10304e27de62b580c2160e7eaa002837816761e0f185e21e930484e7c6a2bb77152765884cd8f5b1b2e20732053101cba11f4a38c1c69e177951018be289c477e66898612783f873e27efb90658cb1ec1b21aa095075d3015fcafc10beac1492e23c1a532519ce786cf1fa04f7ec31c4b3e74166cb0517ff4d6049278c9900fd7afb367d78643eb02f57e0e3b8d3184ab13f1aa3664d508dc8f531620c737c1480fd5e88f3b68fbb7c0944583f59ce1dff8b016b93a0b6927330f78dde54846f154cc1c0c75f277fc4d6d050ab5db19f5465cc0ad8b94db138e456459111b404641d1e21968dec144e27ff78a3d46169dc908ee6dfd6ca5312d0b7d566c08418778e9ff107fb002e7d14d69e08da923f0924a1e22c23f7450471d09875ecff34dd8b0dd505c9ece73bedf101cd7f78dfc7f1313c02173fa266667051d54a87c57de3406fc9830bc0709062785baa8438bc494e7ee3da4e274798690319b3b11d3467f44757d9398a5315fe6d060bcf1c2318d6ffa95cbbc2fb725af2ba8bb3c6bcfc2e1c707a83139ecc7b33cf8979d43bb73139999570b413cfeedc99301f1bc7c2c64ccd18a376d2085f369cb04a189361312d1a93d40d3d8064bf56e2574e4c4553c831f8dc4c87dbcb647612a4c31d0bf7904c10494afa58cf9d94b3727510995b5de34f6c352f1cef9b7327cc5c77e19c51dba8692d53072809663180248d04011b7f516e10c6c389a871a37806565af079026c2c37996ead013df93fded96804b52f3e482dd9344247ab855d3c5f749af0454aff800813d70e115c1aea85ea8cb8547c1f7e65f8772b8de3169e96d2b210eb184e41ae3a41ec8129f6b01a58373e825045ff7e402d472a29f306aafe50eb18406ed1d8a45af9bb2bdf5985fe27ccedf5d08b260632f582ab8570d97fb94ce182819a7a7c515411f9017cd9eb69b7ab4da0149e9d2218bf19f8b1425f472384f6e4e14fa530fb9fa1f33da19a38e2d43eab245b05b83d6c1ae725fd4e3ec364081b152b67fdb77494908e8a9b4c5296b48c6598a2ad70fb13d64446b7710170f4ef0396a53251ea3b4025fc5edc9395af00ee903390019fb35138eaeaea9c0a1a8487d42018f7702d0fc285de0e6a8b35bae98e6f22bffb5d3603a1afad51cc5dd2f0cd82a1b2c80520901d620af1896f4d0e105ea4f6e32e842f01a5310cc6a89d91fb6cc9775dc3d403da101d02201ce43c6cf658ea9480c1995d6a56e13f339c83372e2c3b260bace76812888afb2c55f1f9d286447ab769215ee3a208edd11addc16ae56361673f81f5231d447591e65537e56b8d3c0135219435db439e9db2bad2d83d9da7fce431b478cb40c860c7e098417d7a38c3ff5a9087aeeb4c873013730b7463f4ceb15a1fab9896f10fbb7fc8ac1bed851a8f8cdea4d6f779c18d88605b925945dfb63887af8597262994828b23082e49f8578c07abf175d4f8ce4cab6c18defb1af91cac11eed41fee13efe2664fd561feda1dd26839167e91fa3b7e20be5c7ae07a27b35f86010814bdd95e42c40e1f0a56ea4896d6444d497c85e621cb79927d1a6e943245856eb0923706c1649b640006655d4c947f728ed8748b8ce903df8cff4b7a19dfd29d124785811e77715b10262112c52f2b000d51bdc62fefb12b390b4b2ad48f7a43306fb40f1a8e46b54268e255a1d7635837432c5639f895ea004f78207c5a5f24186530705fd4431d5134465d0482b070589aa1547a9809cff8ccaf2e2662c4bdf9558547cbb81d1bd01a27942b4a495505aae14f1d751c320dd79265185997824a436d3f17818050b6042c5671074db91463ded90536d661f4582163383aeeea66e3408806c39d98e61b68190f3eb3fb757abf515d860dfa14034e692fd4897ede16cf695b069fef462b0d311ca4a5e2f985a9c4a69d5dd556317c09ab051963d9d3351aea408afbd6d66d9b48ae621b131b4635ddd6cb692105f207f4cd88cff110797486fec1de7b11baca04b90984ebd47c13f8190c61de1c249571189828cb907fede328f62cebf2e5a12cbc56be81a405d3672135f3f36abf1582cdd72a4fad252bbc097d71e3d487b66163570529cb211b5885b457634445a67b8f49af9643d62f2ce81c1c452b2f88c44e716d80242f872e659d025dce89bead3a2a65930418a5672f5530f60fe291833522a6879932507e76fea139d272d54b9747c19f7daae92f489ad7ca4365c0681fe02d4bd2d59d96c1fe22297e11f3482bf5f2ea6849f4b65fdad1158469a43096633474f515d36b62146701f0317e53c1052cdeb9401dc469cb8f80d1f8fc47107b25e65b5208c9833d1000bd0bbae87bd06915f086bfb31e387df5c212946550d2c1b6873fa5cce6941c15ce452bfa24a837c14e4444d9f057bd418ba6a49f26fb3824583779e018aa80345baa95e26015ee089d2b250ac3f439596a3c2258447a632bdc6f5f16d9cd3bb9886400c5ce3f0c277f0e1489ed8d92776a3b0e96b79388ac145b52b82eca9daebe2d84bf5a4a91ce7beccebde18dc229c468c69499495c018b32c7dd5c9363d9e7cabfaa0145318399e0597b3b48cd4a2740630e1535d58e94861589830528be681570457a4047922228ab427b9dd4c1b33511c61edb65cbe0c27480c102e6145f965b5f720d3200012e691a86678aecbdc7eab39768937e4c7305cd378800736a96e134a4b2711e3c5ade640521c83b598317cbc1d58914d4da80b8b6a12506f43d2fb914fc096af5a8db60c5f7a229f9977bee3c332bbbd9973a73628d7bf3772248f8a252e66226eb39b513872cf6cd6259c2bab22e2bed17223c0c1fb920a710b58d9bdc52294d7f5895604320cc49d7c5caa8df979b2befb9faec1493f2b023f6c74d82d8ed95f80c66ae6dd3748effaf197a46033d1236ff82c3c0425134bfabe62d157fa1b3164c5be6afd18e67f9dfa153fb40cb2553c76a64bce76d906ea40e0953dd46c30d7bca3a89387fe17c3b3b7fc2563e0a1228896472bd3a5d86cfc5361d2cb0e1b61170fbe4ccd2c88c387fc1f7002383cc3ac9d37dfb7296670965211139b5fccd3dcb2e9394806fd5a931cf64a3a6007579d890dd8bacd498b0e4fe29ad484baa0aaf373b132b2c20764a89c976296a4aa0624b624fccb971b07887750a77507831ce0260c20830d8195a4d155b8551c5d6aa03276d4945ab552dc47733a3b5fc96cf2cfbffb824d3b73bfb842ce0c990c88efdb22eb52c8ecc4fbc0fe8f607316440ac5ce72d8117ac246745e7406c12fd4826578d6f7b43b067af4d69441dddfa05b3c92e61fcfa2e89501811fb081be1ebf27d0e983ce9f45982b39f4e93cec2e7820c778f38810422bcaa976a0c5f896f627765db84b1d5fe83a30484fbaffd8337194d65b166020fd5f807a65f26c979c2dcb07aa30ee9250f16b65fb404e6889be3523d5a209ac98a4f3e7379fa30da10c4582a2959d4010b5f02daae213197d7b034f2e8bc1a99913717e3581700367a0a95472cd1f3baedef4fece20eb3e4a75739a6423dbc0715c8383f09d34e16881d38251908b9abb63ed1674476f431c2883564251f4d5fe02c0f1a24beb81710113944a14975ecdcb4ba53ccfc8baf9061c1516f876ee991f911f37b0532d7835ae780ff1499984ebb96fd533ae210019181e40c75570f1b43851a894206622b2b26f84bf06d7112242f234e396942c60eaa79295032077940eeda896cc66cbb9ce054441a5ee2c4da122e21edafbee0c9d35dfb6f92a61a45a2fa196c57978dc6644b62fd0b5f5722f6b777a586644c8c8e253830c314272705a498e138bdd090ee3d4bb328d71c08ef72b5fe341d029cff3a2257717163d9ffd37d4643ba22e9192cc2cf9986bf26c8037f175b11511daacdf913fbd9150b1e7b9cd2ed3d6680a9a2bdee2bf91f080f201f5e984b68056c5804d7261663bb8030c2883e7ff3b90b88159f61690883a0605fc41b724db8779c4cf657b8d7bcb784a35dcb0d551ab12fa9fbebe7e32f65cd0885a4f42c477e7b308951d0c11af81aec91cd5ab08bbb8c4d0175ca82efeb0b09d686d8af03ff7a34207cdaf7cfb1b3779fab2fd3e4c81c879ff50a2452228b850a29c6f8e7fd899cd680f16239d38621844320347bf4bde73e8e3c77430e198ad5cf6b0886294cff0c8ea7e977c5cc5052428a207301ed31c83c6227179be293e15b2454cf32531965729b4b5d4277b73e6f7a7978905f4993ecaf0bf680d0b21fe38bb6c8c3f560fae4d1bc992c29da8afc88f1bff16d07723ef044a7b0870f967c9f02208ab01f6de6c168e6b43610c21dc1b511c99611873559bd74a4fb4be4d78c9f02d1a941586cf210b601c56aefe52c1843deb82d85e23930b773a8ee944c00d9229bff074801aa5fa996daa8cfef1895228c7c1513f91852cbd608130ac3c2543e1759f654b49edca8e8de05b38de4a670c5c5a66876509f9af79eb9db43b75099e92de0c1358f73ebfedc65b8273377cf816d328d15e61771a281b5c75e1c22a5c1bd253d14cdf648049e17ffea5f58e3811ca610da19d22065fdfe011ff8cee945b97110a025fbb8b5103c450648e8ca9350e619ff9b5c4eb0e582594dea6cfa6861379842ae98d113fdba5be12b21ac8272c5e4bbbbd4bc304b3d2fec1540a5112aa39650dd7b61e3e1b0aa322f48af9c03ba4720ac208eb3f3fb688a2a564b00cced782178f9d5a1b05bf9ef664741cd89c74ccad8902c2225fecba185ad58ce1391900be415a823047b74594385493c2d9cd81fe117c2fbff7a8cbeaa4e86d2c2e5936f6f931bacf794c150f7034e7332ce6ea6d661c4cf4147c35e465578cfc88906dc487e0fb3506ed5e32e11855a8e6cc0aaeb85f8fb466c584e0e37a7e364b79e25261a73097a16c128a2fd3407095b1d8c5ab1109137f387048d8aed1da3a4e9c0c140524d517fce4e00c7d60cca0d268228c991eb5632210d8c0c1d6cda17b99e48e3c35eaa67d70bb70643bbbbc301d93f2bc0ad4a210c301f000a8cf811e7dd05e649d95e9bc05fbf316794dddfcf57e0d7414b4c29b371b746d57dc963eafa670c3c63b3236db1970a44fe4f382ca74a71bc1e239ab77d1ab23af88522e3900fe7e213dfc6f4b64bfb3a0bdef3bd4d343ca78ceb960ec9201bba6527382ed6c0b6b9f9505aaf07a3e3aca2a8dc24063a5569f16942f06c043c76c6b3648195100e3988cbeb2118d2ac2bb59e4138ffd0551716d52bdde88470722f4a754c7a278fabf788a9a7bf9669a9af5bd88e9806875a79e25b0802556fde314041f608d62ec839b8ebaeb912b45f44705b6184aff6589a96e19ef61483af2dcd04872747f87db2f91f804bd592340e13b3366f8a774db9de13917e45504af37628baa0040445435bcd40286ad35dd90e313984254828791e9df2619f60abc1fb6a4ede2ff004c27f8a024de2dec8460c513a18ab074846c56d828eb649cea80b448fea3c21e726a4e2b72d055875c5de23fd21bd7b21ecd0bcb0ae46b1d147bb105c07055148617f9aa778ea777ac17d1d6ffe6c33c3af407bccc10df89cd5c0cfede7bff6c1b0002e816ac2148b0147f49d12702eb531405198fe20c516ac3b733bdcee186863ee8e19d445398da8c1a9d0902b83c5832800a40f38ea96555c7cbea5b10eaf1cdf72838e422476712f392864e6251b1e085c0e37db29e997bcf1f31a61efbd30eba20a18cc80235fc5e2add935b3ef4cf9c1af3d2966357c032e5082e0077a908147338f058fe1b2b63a027c113eb2e9f5f21542f65c50132df614e73a02356950c216d7c9efb2726d4c077acba1576c1bbfb1222404a5eaaa722a57d864f40ad791e229c20c192455cdd13f0d49cbe3bbccf705176c62cb4df2b1a59bdeb987453075cbeab1fa769ae43787a6b704358b926f7ba15cef2bd73e6e6165ff6e249e78aee102b06742c6a636554b929d7331d696250ed78af1be428db542d78524549a34c95a4137c06a42d5ecca3f86e49a0e4915d82c21de718217a5b6c0169d7a32b485fa4f94ac5b737be0e4af6501beafa47f94b9905d66e44bf85dac638e430f05431569a96960c44736ffadfcd39f46a1dc9115786985d7f5a10d961492e4e36e601f52e5a5473c0b69a7d25b5e428e4cefe57aa181705ef30a5bc2279647cc9fa9b73cbcc7f979b3c6846f4f2b45ca0f4da305c5415ba87716ea25ea88c30b7cb16b5701b07c9c529b322ae258690cb8fe49d22a50d42368848858fe52697cc0d84597b5aa43941c94645e9333813b47818e0dd2b860d319e2502d6611ec5f30a24d175774058f710a8638cc0ab32d2ed75871270448ac6944b4c80247520c37854f447c62138efc4011b8147d4ce3cc81402cb00f6fc7edcf988dd19c2d6c5ff89eab21217e55fa685b501d5029cfc3f507d7ed9b1c04542a654899f0ca42210a7d0bc91673bf07f4a8d6b2e5a69fa7e43bb2503b60ef3ca3bb5062186935329adab26dab340812c8c0406af3e95573b43d72b6ab5cc0fae8081b2501080dec033e80beeffd068bce5c869de07974fe8ce4bf8154d48cb46281e7baccbd0a9bcad4e6549e2903d155060a70fea2bb73347c043cf14ff409fd31f541ef1eac95307184d5f8d39e020d5c58b216998b81ac950ebfdf177bc94b0885621f48283de3433400754101106d60c7ebcdab3b8af492bdd65d7183bbfe0e5f8fb965edb9b4b19ddfc99476b7bbbe1cecf52422cd749b5301417cac3e790089a1271e8c31fc434b92c505959900738651fa784bd9f9966f1f3dd9828c805c366021c54b3aace7142d3ce4e4fb6e73c0eb27d5cc4664c3f11b59675e9ed176afd906b4fd8d446a414cacd52cd20ee9c0a1d101ca9cd8dbd98060cf1d334ab447222e0187119da45a60ee6bfe2bb075635b2072b757f912a2ce98a84487e54723f8b7b9166257623648b0ce61949af4c9f95a9059a25d9e6775e68194e1c0f80f1a4bc0c99700eaebbf9e0be34d087cc2cdef2b6ca79b95bbe14e6ac4872a6d157d2c336c86e1fc3eca016a8d9d88fcabdb65679deab64a10d3a9443830a773e505b07463b21134d5ba99f6da930818fb0d2ba53972c494835f2074f6fd4766c1fd25c90221bad0af44d52bb52e2de2879130cb80451407578296f25db287b706a90913e87c0548b4f621c71359057c6dd4b9394009145e1c065886a80619783b5f609e903e0c53d358917a08d909c48320ec5edfa28601e7a9ef33d70627214fb30b4585bfbdb7b18c644d4c3928a39248048cf62088e70ed3703594871fc1449539867ee4f1bb84fd8c8ecf6dd4c771c9604a50be81aceb511262f8b824223791a91ec5bea83fcf0acfacc425853236f950a1b18f3544cd18579dc03e931d5de4cce2608e0816533087157c9e24253fab5aae80717596467c50c864785a6a8682b95304796f59f0e4051f9b4826dad9ee3df5df42c8d4b50c3ce68aebb4b3f0f5e748f8bf89c2a6f04227e8d73980e726ce6884e62e90fa1eb17b56bc3bb63e441cae769b4d9395433b7669453b187cb3fb26d04c52689d3b3f930c6fd2cb1d3f0cd8dc20d5540d6017cb9735692551a0531c704222da4ee79c062798bb222c98497bfe101512b19472017a9dd11851818591069ae8492dca315bed15f4356b6dbf396d7f05e97fab43051e7483d309a17c9692bfda5df2b0c8c88caea9559590c762b5711069869cef8a0666c004fb137d3394ecd0db216a9cc5f00477b71dfd20410f3c548bfb63fa8c2dd3c60775803b1d484a1cb775972e928c445796ffe4cd00128841f337200ca1ce214a24281dbbc757cc90ff09d5163a80d48dcb044c39df768c88e9fe5e1e2d8da32e4dcd76cb857a1ab483aaf4a866a54bb6563be8259bdede0b9bf9a152177430e6a5950c602aad7a895022f423b0e3002b3fa8aca8334a5221cd549a343bab4071b9b20933f6e385a7a206a377d39106be58cf98525ce4bf52a1a95914dd6b1e55268051794f98ceebb4399ad7d7e8df5ac78839efe40758afd245d4becd4f827c1488cfa44102424aa6511b1fff842447e873cb2eb7077708512d33a3e2c54df3f71b9d9a2441472dff2c8f07bee5f5042d4b6e88aa74c972f103592101cb7694289176dff5b69d467cc191c97892da8ba594038098368e73ea5a3b836eb867092001a9994452e71b93f9bfbad7fd4a523cf84f3d7f5d2ec7bf1558320cbda8bba270ce49941970e36283055c3c26d50aa8bfc1c622132d7238fdef3aaa0535e2aa2102a50f66cc8f1c15237ffb5876d0009f1135c24402672b553eb958e204a6e29d4f734e98fa85509ad3e036191524f5caff15bbd5af48b8a10cb18e2567e5ebdff7c52996b824acf25365e9534c01e83fc703e80260ded40ef150f4cdbefda4bffc145c73202e0c61437bf87dd2049beb8ae329bedb1833924ace4fcecf1d4bd3b24cc693cd3e5f11b4a75bc19f25c80d6fbcd5b974c5572a973ea0595f46a81970d8d53953836890d239f041461d3dd921fca5287c8ed6926043bfadecfd486f0f4253af1020cdffe660a8c326ef745c3e3a37ed1701392f8c9713f0e025ab122b4f365e31e0b006f8d560cf438fd42148a7057dcdafeda29e0e9ca5e4905c18e7c603280b08b9f74227f67dfe220231956b367a7e22d3cf966d185381e5dea340ad0d42f4634a8d2e2eb7cd9edadd8ba7b9d25712cc9e4bd4479d5fe332c0df17a4b0656df1ce016f0f57a94a8a1881586d05b1f1784fd0bb07eb21022c9a27c7394f16b81b48308df47a80ba8e0900f83904c2a2f37fb67cccc6711dcdaf1a983cfa6ea088863309167b083148f030350b7882e70e99cc417af7c738c7692e44d8166f963ff72b02be31e6caed2a592bdbe7b62de0b9519d083a479a9456ed5b1793da8e5527fb93eafb28fa77cceaabdab30f1f8aeddaf86b7ea70f69339d5b275426c9e5e61f260b6323feddac66054e61b895f8abbc8cbedf3b7aa77938d26200ccf81b4312a4cf52ef518ca47c387f25cbd6864260cb9deb9ac75d082a0ca8b7e88f6a05e610ec5bdc9b82c21a84f9cce23371c4aa0c0fe4fa13a3f786159971b4baf7234d86f06503ef57a2ba89152722f24f9d597ed339b5f33c0dd14e32afe348653554a90b3c7a6903e027273f1f7018a15bbf664c44d0ffe979a5e3b777b02287db0b2956d3a701f5cdee3c43cf8a696605a106c2d08fa79fc23d9ba70cca3547b8ceeca1dbb07c813308a9eac54ddf3a727bb38f6ed90086c18de8a3b21bcec2735003b14a749a5c80726930321b83cd37a674fced8a228f0f021ece8867df04978ec2bcf3ba04431eaf3949fa3eee445c01e3a5d7f313e9adf35071a572587873ba1bba20997804d7a71ac3505cf126652fc7b3ffb79dae0a1710fdc3278f227984b07d48bc8da0b7d12923049d1978bb0132d1d0f718f74f158f6a6911d3669ad88fa9b529b12c3c05b87dca33ed305bbc53d50b69d5fb0f838e193c056a3bd0b6975263945f46f53275a2eb2eb15a236b9151f3ea56b474a6bc563f1657ed6622e7afc0a893fe334d947a2ac050544722610558e8e86591ae57f262bdf99d986a2db8199226b4d9350c5010a0e5c94145e6fa14254112b95424363e07226ee35aa42517f7145cc18620c186d601de92b251f28caa8f03ac04e6b6ba75046a0f82b132619d948af5c3fe4196d38fe35bb6e97b9baba95fe20f3df5326e2ac39d86fb149c3830fbc18b9daa568955f0f8e54b9685b3eb42982cb7709dd4e4977492ac2d0d94ca0d88bc7810a3b9d93671c552e0b842f88a6f30f634709c3e88020c6c7c4cb5e9c0c79ad01af2592ba6b07f76e0970c2e1318316ad7bed3a674d762339a4cfd9f04746ff2d3f8d13ad2f31df4ecb0550ab206a92b09dfe04674074311beda15de4fe2cf918df8bad91732254c7aa25b38154d70a646e58f2280dc80ef1c1676c8793a5f4bf30935f19bdfe609a06be8ed71702c70dbb1eaad7af310908b889da0095f24be79b88d37bb63267ebbfdc9f982f704f8300a4d961669d692d5067e6061a84f8f26c809dfcc896d1e48b56a4425b4f125186de8ee094f8721cc00d8f9fdfe8015df79256bedf12619b9d3909c0e7f6608596226857cf4f4250d01c7c65c22db663f85ace22f12fbd75f9f94bf13a3f5f428538c634032a5222a5fa470ab8937ddfe9e624d59cfa57e801390db19a7682d756fa9476a45356e8d09612c9cba6fd830f7b9b335c69e0ea0f4ce5584468f5d9488aa28d845228786730c5952cbfbf8833579a1a12a291ea885293722809358ef2bb7b72f481254c8db58b2064fb59d4c90caf45d8e1c0dde3a9a18f195692540c34c31eae275fb844223da2786bc722f624423a5cd551ea22f02d87f0d4c677086569d1df27d8a04d1252db36d60392258a46f166b78f984b8fbf9e2e9db92e6339f734116997f53213917aab17fc175a768b19a814938ff8304b2c0732eb6f3250941c457fcde8bdba1cca2d177af63ebd401a7df3ccb935077fecb0970252e56abb15a557bf165e8b848d4ae1c420e1ec942bcec92190fc8395adeb56063a63a475ca6dd775d5c2cc1264b501f826e55f168637941965252c6ebed86cddd865597b133359cf1733c1e8c19fdcb6637ed1a1c330c0e1493859036ee9aabca664167423875889744503e4dd1dce8ea3b95ba6ac780d4054d893e310b7551f3165466ea02bc5add9a2501c96c8d4716a1983ea42720ee28380eec3ecec57f727a01b035a5669c29be20bb878a3fac6536667361d566fde3dd3da805932768f1cd2afd89f644dbfd34f547d145a7efa898fea931a44e432b04d849f4cd26017f2e062f4f7a9dc8c0379b8fa70072db99500a63dc1782a96cfc106de38563421cd188dee16467d046136351c2cfc9e24ec91f74214bc77f196070a0168f43a565a880d3df82b339fb64ca61870810742aa00e497a0f09db847bb03436e193d560445af6d5e525f43e4aa4ab122cfc943eedf0ede0490445cff9410d3b8076b4ad55ca7000d27f8104c4859d203e3dcd652c81404d8c100b7e4ef026b9fc84e279deba4e42ddec6c4e4ddbad1b2427643c8b0a56614b6cf62be2e1c35ee233807b1ccf70928f65b8cbec1ca50e4a0193f48e1486c79c46d30b9e48c9e393cbd63daa454e6af88bfc55a2ff11be937310b127f4de6df7220da02997a07057052c61f1b0be3599e5ae636d1a3b0b1b9d441c59bc1f31b517c554315f3ef69e93f0903098a4cf449c29d79ba7769e1b50933b980f1b33f1da44a74c46807f7c538cecb85acfa1a69626110d67edab05c739093cca92f92cf92e898c91f45b54e17e3a921eecdf6dd5a263f0e0733955511552bfeb5c7b587d03f57849646a019b0f33c6ffe5b147fe0f9b3772b57efae95f27ff0d39696d9474f022e207473fefaf2a2010d39e501530b8014544dc49142d70842fd016dafed1de70350149e093d077358674dcf90d1948c9ef69d0677d5fdf881a58a6ec9c337d949741e5a0de3a7779c267c8ee82159ca07ea7b071b2c98b72b004ccaacadb21ccb5f2f615cb2bdd6141c3d79befd9f2fd63e1bceef3430c5cfd41d7babceb92a060f6e0d566b9eab964bda2777dbd8d0717611cf43a029b3a3aa000368eb1a5321b56ea1acb6a00ae423f4231aa4ac7b1d9cbf13924bb0fea9a25b824f15c72f4311530657fa45622b3fdaa1e467ae834b8ecd04dd145ef7d617ce26991ed6a9d4bc6cad4a9e61040dc9c5ffb4c160a7f56bb3591ddb8776ad5a3963df185198f88119e4e8fa7831db2838bdeadeb66d2038f386c0bf35a047ca43eef5392f77a700b26d1bf6c7d4df53339ac1bcd069014bf879c653d2060acbc55e509c23fe9ed84b79c39c419a4cf52cc8db94e78ac48cb6b568749fe0a45d79788fa285e090c46bc7309a293c1b55d2ef82ccbde95c88d20f3f0284b5c9972bdca6870c8de755888a1118963d1b82814ffaaea1fa1b7dd02f352325dcbc89220091bac7942be0029b46a8c4cc6149b94a58d1e3de450b8f07f30f701d01c58f8cef22bdb34d3004ca42262598160e15cc1d7f63a75af85e8e88378a2bb1363cb5fbfb3f1e28b7b1882e20503659fe57a5b5dcdcb85873d29a3f0f16c3e0d0b23c5e6858a0191027b5ac33901da450aa6974a283202249a710d6a850d1d47eb6ddff25be568beca8438bf107e571dcd89c68d93ed9a839163cc1ea9eb6e5d4e41d8a5761474b12f5979251dfac11ffd9d189e30eb2a9b723aa30187fa138194057a422439dbf475c018c2e4bcb9b146a3ce86c55b207e9b72858d3e832f872c2b053f3af892b0b92d0f244edf4f70725679feeb990e176e535abe17729b5eb24be3e758fe7099fcfb6c42ffac3432317d38aa4322a7dc748f50c599933cbd20176f4879fc4f72480c35887f67c2a94189989f86f5b275dbe5ecc1aa292b9982c002c062a780a820084c73b1b00516909ff7da2f37b01754e38e9f3e1269a1a827ac1c0395287f7f3b4fe57beaafa78cb07d0674587b6bfd3a5a7371368dc2d12810d8796ef16f7c35b5398b4a02196605cda8547deef48bbb8ad14fe52db7f73a750dc0ce0853862cf44618e60f1b83b2aa63f6db6905e5a3691b86b1fd6d5e8c5d80ed27b70879cacc6f505b6bc1e9e96a383febfa351d7f2ce676cba62dbecf00a4c3c10cbc222b6e7bafaf3d400efbf030f1dd98f8e71bd1ecc08c1505325506d20868b37c5c711794e91c04f80f5c091f244944159e6f8b0d087263b5d4f8893263d1283e04689f9c476c45f1f16f0fca7055775ce24857c03b6eed64f1784c89dab8261eaf9165ea6761e3e8de3725a705b2c6637ca40ed03fce12f466eb6f65a4d093679a4c39d45cca5fe7d81cb26bfa7e02e717347e87b1bec8072eb4efcc6f3e9e444821fb5f379242d5a7c5c70af52c76104fa6317fe3f3aaf35dd0796495f348ad89df31865ed4e165b84328d522cfc449cfad46f56755b826d5cd18550d21671d1cd9dcfbe1a8dcdd711fefe6e454175d8458091867bf15d3ddd72e166e84353e414ebdd21f40d7ab1eaea4565af67d7bb08ec4eb6eb3b6aea9d3d60f08d3cd62eff57bea17651d101617ab3a9c46bf88aca7f83af3c9884d6d598508a3a2c921aea4d780f915b9ebb4c60c464cd550b6d65c79f0d326381ab5f4b4bc2f6e09de4ca53f69c7fbd948564acbcba10a0fda579fe1d532a2854d46299423bd6bbd4d7fb6464c4e0c5087fe6b888a3c6ad5826ea417c750466a04c93c8cf2a3456a8696c5dcd7feccd0a779a47722d49db21bfbd1f5f2476027b54ad1de696435a9f33a75d351ee4b646f3e6df6351b580ae3018fe8bf5429b347029a2af77e230a0f75314b7112b88cd85ee5708a0dfd0da15291aff12dc525a86fe51030b6915c0791276b5b617a6e8e8742829c6cf12ba1a749cf4af0cc275a0877dd3f20ef77edb076987e43f11a960add539fc8fa6b03782dd8f96e99e1f6d8cb06951402aa9279085b6ff55b62de6af59000152d571f5006d8e3e200824b9bbad2f4e0ea71d86fdfb34e803df8ce14bc475cb06721afc33538d204f435aaa65675f5030dc72687d5d0a71b1f865dd462b44abd167ff319463e5e0d1fc2a9a721b299c686237aec29191f4bf9b87c0ea73b657104251d35bbb732312edf2a7dd042a67372dea754fefbecbb436fd92184b4551bfc6beadec1c3b54b969d23e617eb93906c9bed3d8478da35973c018c2d812b7740c8307ab29088bbd01755a10c20b0fb0248e7fbf4e267293efef7f5048ef0deac0d8b45c8d11d45707eee7acd3c41c165c6a1109af2356626e7178b00e93b051bdc9871edfdcd3f0ceae38eb31a4ffdf23ae07db9351fb497f25b70c948f31a5fe7625a633a62f8110b5e29fbb42482041b60cf1ae1ea3e3feb5d44670446f5123ee9c1cd74f41db7a7bf0fdcdaef4e30cd39b6b965a84b66ac331b033c590384c66434e8e803906acf9bd856126c01cb8c186c270861948327ac001243903fb4e8fa832fce5d33853196632b6ba6bfb0b3fa55ef5494c38451a946c8e7768de33bd4da9939e546343dcc00d2a75c298c452ced1fa681a7a1e011c4157e5735b3d93c50c85c1698d2cecf107756c9560cd0a33fadbb9c7dc113805889d793943fae3d63b2d2ce54663f34bfce7596e7460565f6a72f4e5b268cd5450ef3a41ee6c0dc027526e2e0973a5e0f8b868fe1e693de20ea40e78b8dca6c2654938d961edf111e8370a27c0d1548b954eb59ddc1c255a34c814846bf72299108ad442d3ca8889f4a1cf792202b432a62c295f5f492f21f7a1bfdae98a7f250bb042f7a83e23c32f1d5275284c495d5d6d50a04e5dbbd9b941ffd73aaac9655878d7f5865c25448abe3865e8e0f00d3e8c70ffb0b58caad6f577dbcf7b9bf52d52a2dfc5ae54d3683796108397b3db26244a44df4ebaf03a49f09dccc4e2bad001634bae9dae366a1b8c76c2293b8115e8837774f2a2ba7ef6b5d90413662ca1d1e8dab2456482702d20f245a72bbd014cff42202894ba24c441f097ecf8e10c8889ee886af4247a02597847b57a5006a1ce6e9ecea12dacbcdb1bcf30f0ab216a4be83103792343b8c2c4979d761957a88b1e36e54d7db9122feb0f37458856f35a0eb13e46b7fad6e96e11fd170f47a71d9174bded6a11606c95beac033fbe7b4ec8bef87ccb4062b279f71b3fbb6b9507b871c7338d21ecc5d0cc0822a4c968b33a29337222f4e0306130391271e6b01ca0ba9fc4764478aae5071ee5e14e9ea2fa123c73e836cd43d79947ca362dad22956b690d54d04de5e8aafbc91de517ba8266ec8143843f6c50d7716749bbbcc00ccdc5c3335f5c7b61431f1c0febfbb74d9e15e4287db889c2392778af63b92742391a7931bec3102182acd98aec498212e2130c5b010557861ea08f829b7c57eddfa6fffe8efcc06b32a2a19512f8cbb43b00ef9f1e06fcfe57e68cf06164aa6cd0b58ab997aa24b26c665f21f241787d70d853fb94bad18b8d13e8470165de7ec657eed60178db249d1da49897a1476bb2809a1709c4491a591979d2e9941ce7fb084e7cda424aba07e10af5e7e638c9394ac9877c2831eebc13a46feb3faffef9c169ee7072668688fc223cf4c9f9ade0ddba70fedbfbb233d3465cd76d34ebe45a3d39a9fedd2c58ae0b11fb2a3dfecdb77f473ffd38b9433c8dde9a1d6de2d02273a215734d794b39a28feab396c65dda93ede9b22003b81c43beefb4a8057f6611ae6d8df9efa100ca4c135cf0ebaca15704e814b477d225f5d6fe37ee1091eeddd991a4f0c1294d8424ba598bf6387d47bbfa1eff9e4661befc3f151e3a8037ef0eefab0494543702a6b430f48c1eff38594d1065030ad318cf0ed0e0bb42c249715526c061eeca3541535f87e2b758fa5856537c781df83f10909ccfd37a847b853383c3965076bd1f7b9565d6c1d1d7df41c0bd545e165825665c6f2996566cf7f21e55c492c03a73789465af702971b91f1d09df25340f7361bd39fcdc787848890d5bf15a1d413a83fb5a925a80e1c562b543027aca4bd1d799d2d9f0b203f8a6244e3f00222c9b4784f574010806567532665831b5ad21f9efbef6213d1e3c44199c709d34b919ec0bde8db5e5b3424df1ddef3ce2975eac3f46f6b4291c16820adfd41ba35bbeb73e18fb50e372c2f1e1615ab6f63717e97c60570e7a0388f9b7111610d0c29a601a22896f8b43d39089c26bd3ab6ce18bc2e7c6406c7be20d0884f303d3af9edcd5ba2b79422148efa592a870dfb8c4fb45adabf558588015c3d3848589ee7132382cedfaceefc752fce7484d6bee1f4fc90f11a58cd97d65c3b91e47b6f492dde766638fb65bec17105843544725ebc0f82b6ae33e55f248ae701d423f958b9a759e6a328ba974f6bf7a94422c112667e58826ab6126cd0694ba3bc067f926314ebf0c1d05927357cfa7e759800ff8f244da2ad277fb9cceb1bbe2eda62dd2b841e012b8cd08f8a05e6fe8d8191c51b6bb8b0f9e294191d42d08842880a6377fd4628f3f1f506c5ce4a5f5a45c98cd3c492659d92e6111fef98f7515a7a355ca6a1bff4b5ad172ccc0b15f289fcfe4781ba431109c8e8198f911090786685ebd2e6c703375fdccb49197bc1b28ebecc53e9f48fc26d945bb7a3c5bb4c5b6596c672392ad52ccc7f0143acc3d523daa652fd2c239de35d0a50491abc2e53d1be7650cd099e71554cd02fa13de0e4bef7023318c08ef274c04fdab7308ad86dddbbdfde9d3d06157f0330e0ebc1b79b01028f15acfc4ae926236b25293ddf4e42a10433b713d7702cf8ed8c0e1bb6289cedbd8ded1206707b68de90e70c0bc57c4278c1f6d6c93231c7ae9e419b4dd057fc290ab15f06db315e0a8b4dbf74f86c78ae2324099400c2bcec8f3ac9615a199abaa83b3a323b20cd13e82eef32cb0cee8ad653a2485702b3cfe386893dc70d46174b645712912334a8fe7ae04dd8e8e25ea8b2abdab9fec2ae5bbad9d5aa54a6c64121d80c6a52854173e7bf0ee3ce28db5b0692b99387dba0872c143ca4f8a67c5d0771d6b1641bbfb57124563b1e7d61f95389f2e0562b0a3c11420a70e428e3ccb2c9a3a3e5ba4df8d170306c572c6ef92eb5c1922cdb45d44ebcea2430aa0ded472ed2bbd4882fbecec23014377e7982e303da3c2497db0a88b6320e586ad9a7255c87f583eb5f538926866ad6fb8859b68ca273186a00ef2cb562cad12862db6bf1816dbecab61705b47bdf2ef93b6ea52aeb3672a754fcaea06e2c589aa0717fd626da070f88322c3904bd60fff34813f920ff2b6a60435683282e832dc6dafdefd9459e5e60fc377f35301f8d0259b5ef22d621d0c6a31208c1b58f2c85bdc2d1c3248808f166e7b62cf4d6585e6f7f3066230c233b725346b95584bb78efaa21f02ce20789afd15d4a99f577d2e4f021333af43bb3fa0dd1e3475f8abddae60377341623fed0985d14670787148ee6b36cfa938b838b30027162780e1ee8f51ebd6f45b3de1a8e2b2e7b676c5ecc2e597ab2939bcd9282a9505f13a301c1f6bf377ac211e0c1b6808a940da991456273920fcef460db9a2cd03d7616afb50109f57d5047c7a3744477516d4dec8b6da60af40a30310cf0df1ae2588f49ba18ea2ddd5a7e7eb1baa669b880cd05996f86d1d105b856457121dc4021431c5dd1a82e5079f64acaaeac5d0c5c571da53eccc1558d7f5e7ace8cc626c106328a793248ff5b20db759559eaba7939bdccc1bdd4c4debfb2bdb573cd7b1809bd53b28bc420df5cb8420f878eb3142e407c5d63f3ef195baf82ac7dc9be03d20e0a92ea73160939a59b0f51fd020a237703fb7f79a9d6e587ab83332ff2a0404493f1f6706b9188435fd2795f462902f11c71f86a22d57536d20ca91852e5c6a1d3ad8f8c6beab071339ae0890dad14ad5a56480877c73cf9955218117117e2c0a1f6b5c37904b618ea182df28caf3a86f33986bc7299c6750cf66aa145b55183d339cc4d363b242c9574e99d8159cb61f9bfc7f66a3d53952f97b646b0fe074b2d7959ed86051927558b6ced7b5c6bfc4e9ec2925ec3f512f02e9b29d35ac3494b2c919bea99c9e7b63a5b958e5a836b3d8bcef72bfa86761ef6353473aad7b3a5713e5837a546ff62c6ad56b97be99e9b4abba9b6d1936862a1261763c60bd92d87bb5e63ccebec493ac98bb5f12d058ff3e10d86e6152273e96321f94748de62c26296b2f082ae3fd7d95dafd7258992f0b9113a5be6e227c9414200652a66c45051f1f64e09b7b24801910618851f676aa1f99671b42b3217ac7182e8ce4a4c0a086b0d0ea7c468a995f37bceef867ef0e8833f9d563592985f5943a89e4fdd325d50b70fb68ff6ccccf28ccaf818a57892f36360b647bc7716560a64c3a1851cf753c4427073c51a5dd33dba2d1ebff9903caea294a967b4789b5ca798a609d3f96ffd0443260a2a232dfc216191683df0904af6211a2b33ecae2203526aa08d8c2c560ec01d1ddd6fb7d4e48ea08d2fbde3932f1e19a94fd9d5f2db544121396ca0518f442064d40b221ce4eca4399d0716bb5cce388b10e91942a8a9215102fe7d53be71adc64a99f72179dab3f3e6005f9a4d08e2f0c68d6a79e1a16c880c33443881f1d44efdb1fb09118b25fcf2ccea177f9a8f7a623f2bca61aafd3b50608ebcc3cd8e2b8cf2a0094dcca609be79d1693d3010b045e980dc31e786200882200882a88d4025f98cf578ff95b53900006ea66d5b47de8d7a3d0c5baff62dfe37b2650ab7412d42d33e63ba8c9d3e9913a00d19412cbd6cc949f59df8547b41534be6a10655ce83966d8bca05168106643738a52447a0d2e40f8c606a3275ad0e07825346a547ca03106ac212f35b874820d6a5dab25cf8c644cff82af50ea33eb04c3a3fbac1fb9caa0645cf3d60c34ab9f2eaa240456059bdc0f4117f5c84712f009d0c252150441439b925650754234bf24d03ecbed8e9929ca0ba2b57c2dbd531a81c8eeb969efe51a554bed344461fd171a9e130353822a023b6c69f1a7fd1ce9c57853e161c20de814daeef78e0c63e67a4ddb9e6e645ea783c4b1db19e4506c417d8529481b7f43ace2a22d02f8b1636aeaaa505c05ad0044e29f8026ba088ad10240fa80b86d0a176e1c8b20ca8b91624bd328a156fce40b9e0912c54df658e9bef41067fe09086ce8cb4340a3b15b9c99c9c9bdac3ea6d9e24e94529c4e43c19f21c428ece5f9ecadcd2a7b79c6a6e472b20cb9017d9cad074504c831a7051b31d1f21b0148c47dd074a561e13e76a69884b4158aa5035164261d9475f799054491ea756b19b77c44b5af4a45ffc45791106c2ae5652d055ccfe5e72a21da848ac678082e4b4e834ba265667b29aaba31e92842fcf82cb757801901ca7cd68226d4bf2150cbdd89f0583742af9c90dc06eb1621e241f2a8f1c8b6ac1908e030013300f24b65c345406686507864b330c69334a1e4f124a53b7ce126df7176c98cf9afe0b94a35b69c8d268496ab67057e1189ca07a1c04346fb284c599a4d9f12a2a09594f97ba1ef321546b7a84ab19695af34b76612de744ca5a4f3cc9000cb499c90b4e73010406f88a0d94a624c28fadf466435790ab63ed1818d52ca06d5342d1e96461ce583b44352fc397e921ac5c9b5629122c2c2425facb8ff4677e861ab9064cbe231f6d5500a537b9e1ea1a6e203ded9280ff98e1aafdc8ec78b5d0a04eb2f1c8551ec5403d2a8b7ad023456ca716d05c2010db7bf0f47252870356410047d94505b11ce9c0b1d91cf860ff94916a3e5fac64595f7ef4294b90de8456f1d53e142ea8c2940e42e7a375d9d9f146a0a8ec9892cc29d199d0d15d765a8e43ad1c9b69b9a53f5cae8748d80bf60e7c4d415c4f91220cd6d8a525a3309496afe4fdc8714b48c10955686528661484b2fea0c888e4caf4a308267e0010943d62aa4f03fa4ac3eec11a7554cfb5b7384ef95f5bf0ad78f4315563e445601c7c8809e16355696b3480a4e428131234213c66daedefc67151915e45181fed881380cc37a84e16a242b2496b5f6e7fe005a7949c99d78a3996008331f9c11658f876c8c0a6d19811e4316b50bfa9f4d77fe2de3c10082dac151166dee8119dbe01234fabc84371b1250c3a09125a6d25d2e42e90a8c87e5a54ea444ec0b068444e5dac34ed142a2274dc9ba881662a1c8f14860e924955562be20a48090b9c9e331c04ec73ea4f9f4b5e1edb2122a311bf04bdb88ed8175bf8b29555761ad8244686a0e4aff3b83861e56c31f01b46a84da01688db001a1f47dae68e5e2460e5ecdce9208b375d80cdd0a7f4ed68134f743cd3729a252529d2420a0fd84713ccb4da0c09f92f68a821c949d15316ece844566c01d998e1943910ef08551031cb0514edb14b0218c937ec503b0b2040c75302a52f1449f5a7011cf21631629a4a6045cef242d8993abdc85fc8588d40adda67794afccf0f5e0fc2ca4cb7722632f25183179904ca72a5a027912a47b692044bbfc85558426e701aeed0abe0548a94cdc08605dba5ac8ed57bf2c9754c21c9d1078b803e4e99623b503908f7ccba1e4117bbee958c741c26c4fe2f5f6d3485a7bd4c98f3036dc234194ecfbbe94486c57bd5e3740a55332142c5f7b104a3991c78b618361899911ead3e73ea56339b6438d95829b62ea636ae371b9a842205d940aebb6c01d15d5f2a2b0bb69140d5bd72b80882f842d7bace6cca53d6c8bb508a6b2ab3941c569c1eeca83cd026138a416e25a4c563b58a95e1049ad549a8a9327448933f6a3bd2720bdaaee59590ad9a5cfc0d99195f93323b86328985720bd906ac74f89526affacb020bd4820ad3f5bca3580730d0f21b283d729f5187ce2b03f56b3a20429e9080573bea003f5622bfb6e394d561f6d0691d6a42352317b9d853687a9cce0cbb9bb2126c41a3808d16a7f8a3090e5828a83cfc0a095999881305dd6893dce110f075824495d85b748fda2d2d5297d0f5cc7ad85a7993845b8da989287616815ed948eac0d72311455a891128c05b6d50f6c1963579ccd7a5c3953265a614255d66eeda3d0eb5751bb251ee70a1c817a48091853841f3e0036b06d042c6737812c336ddf0b50638b91cafa2d44f120460d5a46c97317094f3081872283d869fa081018b0252976614402c47996101b8a2d4801ca6abfa65c6654c4ec0162318c14ca3be83e80a6413733619b035c7bbf1e55ce214fbc11c6a37b1d3a58144396b3f2140e5a89d33fd76e89aad3048f15c6c9c1c881cbe5e854145fe086ae765c2f11c62697a88eba0fb228c695b7ea0ed9444215bcfc011fea9d17449270a0081c89162e56441611f0920d09a02ac050b94ed3dcb980de10b1f740de5450356aecc8b0e94e55457623ff51b1a1f76d2973a2fb18405db5e42758f14429a46124b6f56525f79c1a13558b19417701ad34f3cd5b89009b89a98a0cbd3646ad1032ce9c88c6cfdc84858e46a34630f3e89d22aa08b4f52da0b942f405811ea7429b038c82908856e936154f0939f2eef9b75872da3834acb6034ead60c7bcec2365854647cdd02449bcbba72f6365c3cf4df0d541d17a941f32ae4e984ec5c39195274ba0188b3de82c0542392b5f7314b061a149e356c8643a45a940240405496203408378b821204506a145bb200299d80901391ea2005217f5d62920616ea6c7329811e7d2a47a79e4274c44639daf5118c690d6d6a9e620f24b6d60607d73467519f1ad4e82af0f6586980133f934620e76875a7975f19ba4ab0ce8deef37a4410b5d9904eac8022ba5823943ab04b2258ea253ae0840cae901ee84a1260012d3afe835583a0a04b0a7a579e146c273bc5060384422eab13702b6dac6caf47354e0c31ab9940f873106a40d851492e759f8428ec14d0b0dfd0b2c3762923ed2720c29c099260dbd9328a6521657a0f26c9c6b265c1ad3a767528007e58183eacc102cb3a5dc65411a090bdd45d9a4f804682c2bcc50a16743e68e6bc0b96821256f53dc08768df82f5e37dc806b166a28a9e084eb2696a1d1b8a940e19439f1afde2c39edc69c7a2a63386c1c5ead83bb8531e86ab3807f1068de5a0a4ec5f0de63a4189b1c69445cb15c1a9c8a9280162615958d82fa0a7af2b681d7e02411c90c753460f52f40008a746abc6f2874730529c349f82edba1c873a6dcb92957ce34d828e5589cd730d629bcf02f44ef270fa05323d6cd2cca06f8174e16788b0fea7c030bb08d3d19388dce8208f1c1f62f7e1345eace85c649ada38c14fbf49cb21d4c0ca436fd8c1890dd468ef4e0059832f98322c7b6acf57163c5ed8137228351478d48d0526b02df2446926483075a7bb0c7da9ca29fbf060eb624d52b1617e1bfec44e33df39f5e223e24061df232a5a4ed355470f60e8b348c75620cac3afa800c3164132a783687ac01e99b3cc31d892f31a3eca2f195e99d1334d57f2ab95d7e4e070507f025825ab90b0c24614da459946ed2ac18d7fb9c27b0bbcbce301d1fd6100946d82004e5f6944202b51a9fa1f4a3bd8a796023d408c9ac36295a94d6099f1e23b7658a6ccdc6f82897ec242c20652c1d736ccf068205d6c3525404fd8e7823710bb3cad97f797c1b2a000de3d51661153c200850837b4aa028c58258f42ddd12c36d954b3c7a144be7a06a5074da212ab36a083d7810820f14dee96a798d028c7056d5d6ec9a7ac044fadf6d341070bab069b9e65d684e5751cd14f02e561f9a43ad49162292c956a99afb2b2049896eb9919041574202a19b2ad18a73e610a8c9c235205a09f511afa1188304216091a1d86fb868d2343ec190ea860b381e3c52068c1c63bf6b4d54cad4c560343632a11216fa93a6a4102dc7a4e9e097d21edd2695d1fb5941e365ad6df591b997cc80020b46a535ea62c16bf0bac9aba24bb6765432b1f8d102209d2d7795c7161617548e615ef5f10c6091f7d07eed9911f901e12c24deb7a41e346d81c79a72c0d3a8ca04bac213e457a162757ac8197cc85286be42eb0489cc8025bf9549116cd614296e359d07eba23094b7563702fa31065366d75deab25c48e85315b15205d99899a3387e9d42a47da50a8b5349900b43222541ed56808dbe666e9166b48d84979656e879495cc442d470bffc0b14ddc9879164089b29a0fad3a801a232dd4c2f63e104dda472d281d09c5df8998887427896464e1291bad82172e3655291b4731bd638fd16927c774b013227962810c103b1328aada100e30414fa2d232a865982f3b51c864254252196ccfed2170208216943cafe24c7f8d9ae61f2541d3693804693c8b966d3cabebbc32626c19b5365ff231f2af0f2a9a45725226752aad55a988c49e7986fa5e1b4af94ef4c3ffeca47a8f58d25e5b91a60594dde53658f8d8496244d800073405a57494b071308e08cd863c7b573410b054a4af4c9d114148a5d0ab03c29bc52eca1371b3224fb60a981a97f1c641637a84d6593e55ff238f8126a1a1cb835b1c3c412f46f9541211efe2004f5759b13d970af1b416217a898b5e0da7e7a78d7b8d0ec24d84c6758357eb806f640505d4801d247d7807b654395710326792878cd0112a25599096385f561c4569212034642c51fa1c50093e6d3725d5bfa438700a2204bda5a426dbc214d773e014b1602da95f90adc955ea34349e10ac0e0189aae3123cea2776678dc468890d1213baaa4960722caa984bec49031c20264ac781e0715068693eea9848b80112a96c6d604ad8be8ae6407848806f5edc6911101e3d91ae4fcd760a2dab4230a8efa8a2e61137921d8729819d104847b35994a943c87ad4290a41fb932c2a9deca1844dc4b7a28dac5ebec557e27570fcda442926af45e3114b6a04ad4e03f3083e82dbe39c127e954df97418591480ad2ab5e93e62d67a75d20e35204e88aad4ac5be954e41b27da5ec5b2ed494c8e67aa212b8ba9a2765c9b5efe02a748c3d9b95ff202460ffa7bbf4a56a55665ab0e1b0054a2d665c3535b95aaee038897f9dcb2d5d21e66a785a14aab5805cd3404d962ef2641ca777092b2a33a4380365c793aa9936195c553f901af1d6c5752d4aef4cc68567b0ffac1991bb9cedba3f6334440be8251f41c651434949080bb05b963ab58f1d2757997320f4b044e6604905dcd5d3bcc4c049b280aa81e0468ac8f00ba9e8415b1a652a0735b2756413b7f40b2923515c1311492b4173843fe1427f82e7d6ab151404d6828708226a340c38110e1b0bd67d9a9a8c461512d63b492095caea2c2d919da9ae53426d37b7418c0967db1d37d98a2b089d4dc68eb3403eba30f8307adb0bd8a66e3940435c967d0a6fccc5d6b1a5be05a122d3bc128aac4e40b7246056b8901c26222953c9f4372fe6a8986202006692c84c705435d71f3204a36f4a6f6549729b3aaa904d9d2b306c9baa01730fe3689d735fc096b4b65fa640191c67ce9454927da80fcda5f93dc8705dc634b9cfe2480a42ee507d0e74830d231a828c979d74c4fe541482e8b408a9da20650702d57a5f7398a6071f4ba70064d54042ff96936991679ac06bce8b9b4089253f0c2d05628ec6111e5c8742e2aba5c40d88f4634467d21a188667b555b4f39e9082e81385f17284d0d50e5a26215ff2803b41730f5fc560b556c24bab61e00a8654f59c9950925ef00310ca8fb21016580552c20ca62ad19b6d525b47c02c3f24b8455476730b4851526c39a0fdfa12bc812a69f6424bc12263887f5161f1083121700e342b3a1a5aa030df1faedf5bb02306f990f95614b7b1dc85cfc90f5978325ec5d24b69e62034ebe0b016d151f3e7c4d544006d0f69747cc49c93c687900de21f2214baa024b7866829ab6a54940508b9b9c35b11a02694581d536320216098aad530479931fc19c5ea4cc9ceea2a7a5ed7c88930744a8f53c0cec5e480090d78225ed65a8340fb2ca0ffb810387bedeadc9a31051388d448d2e690f5cfb9521af85d92803f19c003be4a2cb59c874004d2d3992abe880129ce661d2758b6a0873bcd073453b405d521b24b744ebd345bc20d2ad7ec465219286fcae8b9ba9a4e896e3a4eb3036bc1ccf842b4d6095a247b052e947825cea12b894d911113d77c0345e8794b5ef22a526a7f1d2f44e88a2ff729426ab72defa6a2067d9f187376088006e5a92926a4b2184280f9f7f4106324465211f7a048f47aef4933ccd466365d6a1bc0d73900aa2be2a050f36158ab356f1e814cb690a9c26218a4e53b56c7829291b2bad01e96b6a56e44b659a9ccd053c8f6b6068135f14b213336b2705264623285ac9862895095d3922d44f401582523aa5faceadb72f2425ace44e76f08bc6b0f754d5085a5f47b77e45ea534d54740e1098fa06a2ba5b41f461910cc88de747af5bbd127b8d2086de81cb8be6a005513f1153e90d5c11c97066b39a4f8f222c892800da4388108781e81dd38ab4fcb6a0508e8ef900d4f1a6112be40227e320ceb11fee88c2218d9c7422209b3a7782cb777966b29869db56eb2b160c271ced8948a43b29526c004524b1652ee0b58c4cb49a8e9d1a8ff322571ed2c247f7a121a48ff470755e704a35970d0f3a432b960d602bce4d0cb2d1029ed126432184f5a400af9f51e6e4504398ede3c9cb76827ac985d810d93e6367b22b0a44722f2c7672a30919ae6b1aaa957078c232fa33a6c934c0c42e0a61e5179ef0e52c766e75893412b9d7031e2d049ed41f76446952358e5d6290894375ac385bcaef7e58ce0e40caa66fb47cb9b563a3595ed39c039189f621a6150bc5c85a1674614d50d671408fa94040c8b66a55ffd99481fd9021ce58523161b3f8313baeb798cb0a04dbcd101bff1a318de54a2456959a8ebe42e64fd17bda098a2419eeadd3f9304d75972cd01610694a2f9b373aef048f56b00453a7d27b95ff2e00ea1c2fb6f419040c9de443ade0570c65d332be100ec9442abb9121111432d7e310247d01afd4d9d08dc0dcb0791ac4eda44e23b6cd1e9b1331cb939d550a2724c6d23d218272b95d128e0188a273b2153d84160fbac51db76cca7ad66d62291b4b05265732c7820d5bf63a2d3b6107e5244313723086d5d10b9829e841153426f8f03db521acd5a591c9c40cbb3aa493b720a9412c6ca464a594b7ac776ac2b70438d06478069ac073c0a76868d232e2fce5eb8dee590b5064265dad8655ab434e0125513b7dc8f910bab6b651c9481380c3d307da12b59b93551da0caf7466d6efd56c7416882907749b290d1607ab20d8471c014515c6520914541e64bcc58568502c2790835152671ec233a9d5a801b5c5f4321ed2d03ae6c00517afaaccb19168c2bb0b624eb47c6c1f6e53384d59faa11e7628bf2ba16186a935206e9e696331d069e5e2e87a2a6e3c88e950b04a5fd34d9c5e2e0b4895dba7f99048e59dd7efa04447e4193a525707de971032757a8652908a760515e46d091b72828f45b989e645e664cb4154f2cfa8689139d8bd63463589a7a98b731760096538da10eaa8c04296ca7991b1dc433e1a49080fd862d4c0fc157e332708d7a083f212c2ebf5aada905a5ac05d18786348c9033f502eb0c024609cb46b10da14a86a0261e255a92f4c5175c003deca42c00847a0068359cb05a2f4069d73ba0ead45626943a24114a0e3721ebb60d740f338571166e3ad7da03e14126e1faa12f715ac7a8b3832d9a74076b8f2e4a87a2fe5305152beb47ab3e913ce6397df81eab21000fc92993f14c40fa12e3e319059b1d994f57822b5a50254b1129ea1b464d19032700efa27490e7c876dc5211635e4393a0739ded7a7f91fb7b2cc00ea7dd959c60627b8d7af43cac98dc44063dc7f1a547fedda4df51d2e8abcc10781637abf2e318cde8105d874880253b80d44b284409e64afebeb02fcc3475aa36958414ee6cca729788f948c94b4e904c146cfb88b433135cae70bc7b0b6989cc29155fc3e202804d4424d5a814542f2946a8b60124500e13436a4562b26eabcf85cf9ac588bdaa237dc6477f15115633f10c68b31682fa0c1e98c3520127fb417526b461c648fe05224038e7595e4ba108b88b095c67a9938015a0c748039a0023f74095a5d16a0d6941502af5f114dc73ed02f4539d24bc851b89fee36453068487d56125f1c3b2c0a2e6768d26b04cb406fd45c9592eae41f211637b4e8a828ff395faeb31846e9c0c8b0eef82eacff704a5654b1064e45a88e2808258396a0a70553206476e58162f5ae0dc8952fdf767d06b31805b14852fb915140c5da9171c9b814890b65e09d3065edd359818a6dce242f34029425a4f9d2e5d5d61c636cd241f4da184b514b6e936a050be401299132f8cb5a85b927a441e153912a0541d08828f7e9ae16395804ad24657756c051c6fe39262244fc588c98b58b2172a94426802adc1eb26595f6bccf2c4114f580b85e47c850511ddc5c0b02f5ddac05ef534fa0759307a2cc2890e7588c30ff948d132ac7068029ffe8e868cfba753609e4a2e0d0b6107abf76a4acdc380902eb4e8ac6fe88963e96c8961c3648ac28601b4aba334982d41d735cb90442747e2e0841ddac9d0d5459c5a512051cdc0538b4eb331aa5d9d485ec92943ef83c4260f09bcfa9d1e62d7d164aba5167065138a5efccd8b682ea205da2592bcb106e260eae50a1d2c872624bac8147e92156876d1a819acab60200b6b8d61eb3644685c6818d8207c586529b8440899c499d35462948cab878376f26249ebb986b8ac52731a2ec5a0ade868c342da64f755994ce4062ec2bc4c34caa2f1f0ea85f2de32754c843f4230e5b9be54e811d0e39338bf5a4e0c4777801bd430a428d811c0094db6e711f0c48bb9d673bf0461498973950a50768ef1158b1677164c526490d9041861a971d46e1768105b05d0e213e686f419556e4d26baa7499432f452431259d71d4f2f844046f6ab1abb95838a4d43bbeb55ae2c34a830215e440f8c6e0fade915422af94c98ba4652e8c94358ef58b3be3ca0b453355e221242b4e6b08bcc01d48a4eb0650b479a59070654b9c4872d422e7526f51a255e7e69578ea6a0c94d3f7f10f882bab1fe3288fa279088ec8cafb09642c84fa71915774e693875f403a5270923aa4dac6af13f6782b01aec449dcd9a681e40a5d5074dfb4e06c35887416195db7010c8a3acec68221f00e5b6566f424d6110f217dd00e0d14aa56e2297ea693cf0696710036c2abab89d3aaad94dacd7ebd0d0713d3a809acbf0096ba907914ef422168ba7935e0f00b1832573a9ef019c5d6e6b8d84875ae32833a9f2a391c870d518d4607d1804f018d4347dab4829b6a9c9cae2897505afa14ab4274d89720e22d31ec3e91030c925483d65c003784c1bd45f4e916151c1f168047a7bac59bba871241ac5be4012d6af5ad0c92edaaa7cae0a59160137a8cb68c8c24a49bef724c74f7e02e1d1739049e9269eee7c09a01559ee11a4a31080a3ab463235ae0c626ca40d393280239580ae9880ca8bbad0ca9a501d62c10a14c82f6c9cf582124be085d75407601394cfe432b59a487e2c04150e0e26aa45d7aa7a681c4c17ec5b2dbc2e7313824d852bd3cd8c9af50077757da591df256c62f5eb153d19f94a793349e95be8f6fa2ecb942c4c9ee520cb08c13d3469da0f2309c1b6429eac6a4f0abb2488a0f3a1b2fad27017f71cdf9d608aa0f4365a551f2b43f60e5a82796e8c8b1362f4b25af692b4a9ba4cfd56979a8d9ca9dc82128dd692e345db3163a785137afd080cea5750ba72b18d4df7aa152baf7d4183cdc995719592c5221f5cca96c020730e2f507280241d320805a732d915b76ce0c42bd693045df9c5ad0b415144a2afa28ac0ce1820e5acdaa8605138890dea8dcb82dc7cb8863ca9320550276e421301b6ecf9e3bb8245f21302477ee9902676890a4c2d038094fcea31d1a91acc655022069c56266aaf2d1a94cbc0a20434d166c88518159b16af016da40914e0aab857bda7ae0d9beb4b5b2e90ea2a4c22a949739a11e658005c9bad258ba550c94b67e0d0abbb28c95a8e92d0e5d47abb16514fd84b4c92270426c6de19f3d4682908b16ad8c0e827752b3a0b933f7735a5094b6a53927ed19cc5aed9f3aadb261316881223d9cf9a105f46dc22f9801442d6950b51ef21c1862de5667ca116a882994ac9f5a3b303ec5c9c25cd0a17ae96b37e7a9c3be49bc33fd10d2bede60f8cdbea91a3232c09723d32885de6ba9a8625b947535568475e8ce4346d14da0671118be38edce5c25cbfc9079f5cf772ca89e0f4692784e2af16ca6946ba6900bc100799ffbee0006bdc31f51445f2321ca53dcf5584d3531a3c792d1b5d1a45a16827ed2ef45a9cf10b7a895d9015090743a74a433862e81c16a8ac0a25a1fb2841e6a0622e17865090373069f22b0e47d723026d2c207af50002717258824c5997899cd7def8103a09f4b256acd4094a6013a831c891c5120a16d9170998c1499d2cf48d240a72a511a65ac392bfb840eda4ba3d2122f1835e39c9660a6234a73257d85bae76dc8e102119128ee07f287e514cad3e338b4e7f72e4a03d91d1d1c3bd0bbd600f505e20e45637c042882dc3f279880717fad9c8d684a110500d5d340f789b03bc43604d66ce29616925d9f01950845d4667a97d34399ca921ee2cec1556518824f733566543dc22d48cf8acb51b2b8fc683048bbd31c1ed2fbfe111dcf1d4ac227cc80330cdba8d55a5d8185380f45b02c26fc5f1d0083ad861d1fcf8943fe891054bbc39d0c6eb877cc3428accaad98a6df5a23d678c1bd0da2751b71a43d4573217bda4c4f60360629fc246d35bbcb9d440c25a640735067d0b25032dc5075ac770ab9cce2a2a4da74749763447caaf828c6401d4b65bd1a1e77732013907205dc6e325a68f3fe8b0b75c5c39409b675664654b36854354b0112ba0430910046c3322d771449bac142c553fe901e80bfaa03a1cb4b0dcac84e37daa7cc8a4aab8b88d4a74f2f5135da31133f50c54de6e839d94bb2880f6955b30721067b583a8421cc2a8294d844bae6c6d33a4abf0a0d3a690014e0b2c06d0d19434f9c20b21ac8d2935df421305168083a137b511591a3cd0349c9c1bf080a45497c4ea45ae43d7e4c7509995e354e8a8205061cfc8b16b45af14e5493af2895c62c5024220e1acc8e83a40aab9eb9a70a3117d297b14143c9aca46391c9dd37d44e4ea5404c2d844cf634bc13b90db1aacc8c04272c0a8a047b98a078715ee5a944915c2c2c2e9d0a89574aa918b6a6efc40e84bbc09ea3f3a14fc61ba59038e7ea651907e36e094a708e8d0671ea9c89388f45d2e081d2b298aab0ec2a7fab7959a0e71a1419efbd2e9b3047516ea617b9a2a62ae55f6e6781507f653ab1a1d4d8380151025d3ad9c32b2104fa49ad2de9ea025b1986590778385cba0b4ae096c0dc60245f75991c132c061d7a9b6e060e7b2d06a242d2234d495f15d56aa5cea08acb9ec71d09b7285b82a0e16dac92b30578562045b6bd35e93294564e7f65469340074fe04891916090919c1ba38913a139bb0e00e0d1a7a821b26ec2143cf51c24eb120045d6a417a6c054d48adaf40ab097b0c25fc2539b4ae3126eba5a2a63a011ae2cf3c51915d1c4d1cce10079609d5560f33e1617508f0d00f2a99c9650e28f8280895d8f80a9f768333cb6092061c8aa4b4bee104cb00724985ada01b2d83035db0975551b715f194875b3665351808001b81e1e92a579cec8fbf25ed2a975b834903f64ec727ec8f1f8c9eadb4b16ee738ad2072d9f8cad9ca1c87ae63c69b73726be51c68069d411e532c136880c62587cd87ab2034fc27c32ba9292f61472856068844df25c78610c7195559811d96f512e7caaee810176465604893b8ebc58aba0483a5db1321c8a240976f001489dd25cac38d6c78715c7da66f362a0ea76df82a74d657a181a3c39018c2ce08137c9458476ea726434785d8caa4980d0ec30a66770783d6c4e800eb9ca4e06bc46664e901b03c26e9c49be855e8eaa90becf2f99751a101024c4b4b63135540037cb0262c0732d3d68a3c2d3b4e82b4ae53ecd11954159f064b402f379dfa24011cba140f42ec1f286abc1269dac608097fd44b2d2b3a03e068d60cca6646346a1696e8b04854c43a09226858247fbc5c930c352c5b424b9378650a98265225d6855bc69a8d29c95afe54121ee845a40111ba9131fd35ca3754d06163238c3ea24988e5512cf32c98bef205267c3311987c8d895d97eaf3b2b6f4107c8418b70cc5469e8ef3b4a4938828d480dcb87595d2b061c8d163a1937edc2d1092274293a74d85dac55e1914ca677b003a0f2832dde15900e8165bca90d2b879a01eb33a49090eaba47a8a4d622705b04095c1d738b8c29ac1fad229daa8c97009b2be3076e0739eb7be84c982ada4024f9b25cacb08485479a225abae62ad6b465490fc9126079d8b5322b61d71a11fa188b2445a9de84ab0ce58226e12b26bec032c614682e52336164c2089afcf5c718513e458656ba8bbaef1aa2e8f859d08b6e0c2aa31fcc14d2348ab7df0b19da50e8da603cae51d79258e26b9a36df55f431852b814beb1b66612d5696e98e90d6ed696fad5b519d54bce0184d4430419b2a410204f678b5553c23480751da901b2a0517c212056b225024cb2821271f224396f2e698309e09e0c7b1a919cba2cc108aba37891864d5a00704c365eb5dd120cbfe53a7b45ad0c1d42c55f3f4051a98b9cf863db5975de64974dc707ad1e9497e95447685e06a6a36f2168d368c6747ad8989fef6a7385fd6046426322b3833dd5a703fb664a2c4c4be0cc0aa69860691111c0ba783247b8e091817c474501d0462e230d61cb9255b2c9cdf5b4616d012ed28d2450d2440e007f8bec8fa5c31b7521ac0cb9ea49ca9180e295e9faf49c81524faf4243a5d7043ac232b9537453313337396bf0a29c47ad420819562a8590691930c05a50b503a806d02616001719c23728a6643b76c43a115a25d6c55e320b2945a90350a2d365ada34e2123166b458d48cb6882805523f5d1ad021579d183268765eb0adb290af31eb4a460f564356a465836b1a06eb46ffe2561f138297428c737d6cf84fc584d393fd30043b6c00348b3eaf0cc47a3a1dbd96121f7b8850a58964457a350b02ad8c2d5d3045cf1b92859cb0fca63a2d95c5439944e3fc680177dcbbdd01b2ef9695435c0b280b5bebb1ab1e58838518fc9c2287652905aedaaac7931a2d0748a378e3215087b2f9385455ec4cbd0efc49ce42dd5326d41c7942f300180f4ac37228072c1426cdd0835403d3752f652e6652b79b294cdc87a2064c1273e8958312e288c4c6b0255830d3204d8af7815684a3ef81e092d4ae76a1bd5acac136c340198e6f041c3c554ac6838ead7053915f45104b059c0a49a8820c255bdb3ee81d65ae624875786a0c249cb4172d54afac8c96b204eb5dfaa5bc14aa6a264641f33568e488d1c0a920c56c0a2b1f3fa8af23db8c00e804d7eae5b479acaa40cb722ab51a74174f705ee5827711fb17988c9437822896db6d8fb0e2ca27ea78988ee7067afa938e2d13dac43583d2c8a8dc986851f395db98d57693d0843a5cc609158500c8be675290ac12aeac2a7c71020913bfd58fb0a108a329f82030d87eae981cee4359e180e9712a555236f717a9226934b81403a090cb5fa0099bd3e05d6a515d425c912cc7af49544a846b007794405c4b06440507dc8c4c54e79c4dfc01a590e8910e5621c1a4000a92cf5728919a11b0c083ac9972ca4e34b9081eca120ad3e53996a978ae5f4c14d03dae164e11e34ba19400f7e89929316b045d33d3c75b4831e4af23b86d7e7fe083d9583bbcf81f5c69a45d5b4252eabf280d671587a4e0edc72e66dbb201c47271ded68479d2be9636c596968b17a02e2dc679a23d3019392c1dccd01e7b2bc781d0a26c206158834d88b251ff5e1ed059cb5fe6694914389f280651a38d4677088c8852c756a219b4b0f8126d36dad89c1027180760775602c063e144d5724461ee3a5474f0114c70239193a9a45bb9a80185ecb4569b278ca5275953483806f839e19d49716f6519b40edca8615c81626419666244102905cf4972e34a02d257d7a9775c85e0d81351a4e297ea00b8db71f129de351df5fc1cae7d559e8446f3bfaab14a7ced537a499331e5d815fdf43e8da6bb62a1d3641866737c15124ef9853a7b970491e0827431fcb20aa37b5ad6817064c7d3db39446000403109327b4dc68d61e5bc7498a5cd432fccaa06540e312f36371146af31f155ee52397289cd42b4a6c241459ee64a0a497f02af538628532dba8fe1f6829d68d995d9bf5b9e5566ee21ec2a34946f5c7497fba2b72b92589fa9425339f11a7af73d428b6d18e984c16484d98a5949963faa48055618a543f3fbcc99ac83eb51c5e8e803626b0b89f4b398ea44f91362ed0943dc56f0bf0f286955105c3519522c472e880a24998cad1672064bcd60a593dc6604853b872695a31881e23a59047e160f5641b900b307e69267851d8047bca644f1fe034a841ccbe922a05acd2014a16ceed0143c009c88c28f010f2cab1270f98e12728a97ae2452b73ba0a32ed165a5c6c3640ec0f16d2f2d70c3a9d0b2c4e43996f5a50d89e4c542a5b0b9b4bacacdebb803a551fa362fd915e4eda0f8fa896736be4bc0dc4067b52d7d7110a329d2431f98e72b51b979a7b109797b44705100b2f474d6a04302806f626dfa05403d84590933e31f7e012e6f0b58b3a06990b9442df932af39b30b10ee03aa5a5cc907ed510afd3a6808ee051a93e2279752c5888f41d1bb7fd388960a5c8287565ac3cecb40cd94f153cd855706a3aae1329d6281dd0cf5820842cfe44617df838c3de594287f5a161ac89514099cccc9d775a92e904502fdd0a4c52bb92a1a4558149d5425038731f11553a50955e2ca01c28fa099b4decab3409fde542ca1b8c815e9554c5814228fc899c0c59d3d8986ed36a43467ad8f5041884b49090b05b2cc908a70968e515016c00e1deb75cc5ef036ba28f46e641674ff092926ba78221a4691991dc4a5d0236d8a1d19fc009eea7cc99969576d703089d785b02664f7271348b227fba51a5dc3314f9e84ea964bd6a873d9212b1c3f08038735684be25c4087b6288fcf10d3b8ee499b62b230958178a411e5101446e24474ef710438a2d340b8c85c225e8365a4f3202386af2a31674580de432931b685a102148f96f489de602d7aa6514f8f0e69cb4c6a2a9ca85520899cf18097f306245ce83b834185884fc4483a226d165efef1be67b49926de78bf12e42fcc9647176d8012fd880730566b4050854322d2b75f98f03845c04c6a23611a780add2c2c22a50c405b84704861bf2802b573a55f6304868f9cf80081d415921c73003e3714684752050ba9a0a00198d6188f1857a08db221ae02714e96a39c51547318cf210758dbe65978d96b128ed105ed86003bc085a972a35c1359be8d8641b0538c14c89ec41d002e0ab356d99c8052670492a5399d5a634421bac8b135022d716d0565d79e153b6a268d94c3b51ada4a1a3f5c82c620728d8715691a03c030ed57a0860b158cc78faac12ed8928f674a65c6cd830a4583d025d828e9273bc133162a1cd28df808517eceba2d6a7bcf8f8526b349f514628ec378cdee55307164aa87904cc2fec0b3cbb1a829cb7bf0802a6dfbca0c84748f0e837490bb7b6c96a36344e74125b607d8581c6726a44a42dc978d442dee08ebf3cf3045db9ba3bd5603d41c17ad10a20cff224cd6915e558be50432e7db4299325e9b0cc324d5829b6d69a442c242c0e5e31321f1e230fc4074d43f5dc0471e5b0d58a66c8e94beea886f2a2456342d1e275144cd91863543c979a1c36d41e14eda74d07eb8c9b7b2552640ec414e953ea18f9014003ae46d28e96c02041bb58e4260340d4e2615ee462711deb749c2d5e8243ec7ee45c4d387c1150f661ebe2bc405e6f903d26870f36c3a468fed2a74836e0827a1a0971b9092c05cd8b071916c02e582d7747d59b7665fa0d9445ddea849ce721b3a3a7fca871556e981ccc901d7d1707a2f1c408dd2d8cd94f2195fa442ccdcbaed20e2cb57558981859af9de4307a65b90d174deca1247c58233844b1b4fc7235921439722e4484b21b2a65ec8b34af1e48d9d70a9054e93e0774fc0c222eed5413bcaa0b758de350b4bfe409754648a62f8a59f0169496643b53a87f6a19d098088879a91362da459a8b3f2a15839d02204687d990702f545efda4c2b187e42cf58c3dcd3605c1566624950484e367aea3fce9f932f06745a791a26199c022eb3857b6583bc03d87454db2b6c8fc3ad718a30c6b6c4ad74d59d64aac4bc7900aed7c7ba15e74f3f6d6455903b0f5a0174459f4a839318ba0919b8e54ead4a144a175b00c0cf2264f7f58333bb880687440c864b0ec014a11c5c88a686401d249c26f25a512d4fba420fbc8e403d8e191de876f24b29a19b4ce0948aacc67c383b6c20ac3cb0080d38d789df819695ce339b27adb94e6a3406f6435b2e01acf21b586d71cf59d1bdb5159587526af896e13e1a8810c927d4664d142128508c6b892c94130bd41186c0d1a542d5bc02b4022652ac56bd0b8a0da8f8e0d805bf0e81c9a61046b36d7e57984919b3de0d177b4b0349f2c100f52c174293a0634186bcc52a0a1e22b8a28692d4d0f27924b4397c005e07b8f12e5b94af0475178c162f1e3bb85385358568fb26413cb434007438e99ada0cc6a0075e762442a88ec32a911248091af8c96c7310995f0d9a651b77005cbc9939983b9ed399c34065d2301b343b0207a0c81a63ca1c572477330b21258749ac303302c530bdd19c869d51edab6342c044ffac51e5d6b71f1e4d236766e042ec71f8081ca5e30fd0953f18d61edbc5861bb2091d45526b1019e89a1d75ebe9c60fdd82992356478220442f9790d28d1788ba46f3637d8356ed44e74480e5be8c35e4b87629e444dafef1855241f9ade398933316f25674b57baebc58a4a71e1434cb978951cb6ba41805e2db7aac2534438d0822608f91410858010e25860c91c8fb48f347472860b7b80274249c8299669808d2c20610f50cac02488c05a43a36a066ae2fe29eef13909cc0ea1062d360f24101f4309c7b3248af13712758de25291bb82512353b8c3a32d59d863f7ae19fa0aa9cd4a2eec681d127a75acea800bb0239751acd99511f5358148986ed745b7a95324e1928d5abe0017ad20d3309a985c0dc38b756044664c448a3cc0a339998ea3009fc576468301d61d343535212dae9a0da1286c11466b0eeb4d932c96e4d44294963218324bcde845ab2cc300adbba2d4d6372e8d39acfe63ffda127c978605d98ad4d2683a4a008be811ca06ea16b1b4aa0af2ab691cab63d0335700a208482baa2beb621480adb3a9d86fda74c8b2d2e4c6e0a77a526cce7ed58548b30549f45f2b09ecd442a4cf6168cb687238eab93b28f7f047ef094ae462bfa8da7316791572a7af1f3f6467402e00e2c2e3fc988d85af40e37282a65b36b7329f4e591a0faab3aea5a60d70d2daa19c3462629164d893432181138c346c7bc319713d3bbd5a48438e8563c2ef29fe82b98d19dc3746dd65315278ba4ba9273d024cf3a80ed784e8b6b41dbd60c6a48944e3a1248ab5200841a36282839da065c5fdbc71d45f48d4e85217b204eb143c69465392643b2e7af4a2505484b4d8e0583c22468016e6f4f4ab59436004102b2ee0089a7c24c9a31bf4f1c19a4a42e40dac0cef2811f44f1b68598c08ec2f1eb0f2914f851fc7a462ad3d34353209a1d5e6fce83b5c0eb05978857a170967b7c14a41a7a1a0e54aa664ea45687065202c9ee40f8eacb0449294dd99420570131aaa3f4ac08715425dd4444055e1123f0b9e55456a52712afccfa65eec2c09ec732928d36a08bebe440799ab1dead2458e663296609b160204c1670828d055bb509dca059fd613e392e37c71713e552f9f95e7cb434d3263bfd258c93faad001c220e322e741ca0056c8b1a5278039c4ce0830cc2d7a3809be3263a7a1af7e002d38c8d38b9060c81712ddf8255337da5316e4d3fc005babd79547ea33258325e07537899ad59061c78b8c2ad5327c50ff36e844df9d6a7243b0ce6e424ea93b38c08535a5c8cd595472d9010dbee43b13223a4c2c4b2c9e0312583c91ea722f1678be38ccf2d3794f526cab3869f28a374b0611cbc63b9082b38e0e4cae678a0e8b0206826e7063433f08d6399a235dd830951e5c901c037615901f3dd6822c938061dd566c889dcbe2e550805b1ac80a1e5f524850bb5aa2a5df4c711f27c58b4c2984dbb3aea4e8496dad0ee9cbb5f30460c26e39fbc4ca10758525427688a51301652de451d01702ddf958de94a75d08d162c672358d22589acd019ace923a6c974766ed8617b35f72ab8e2096aa27dac3d456256cd84a44a8ad0ac5a1eb11c2a0d16089f02901b0b427b900ec97d64b06b2e6da2a164c79a6430bb29a5ed1ac494917a112965bc36092019af9805f578645700f009a83f0242b930a65fd1f30717207516f3e648911b66ed8e1a89e6d5f55ddd34b1f6432914c8fda1429572c29052d0ec583b3c91099d2ba84473ad6a3fe533badf654725425d4f22d06cf951e3dfa3aea6995738052c052d020a0e952bc62d188c995d57c080a116d91d14cb800014692e2a98361ace43c44563e828b960b50227735b9b4dc4fd34b9f7a03a1954c15757092aefce789d9977c28cb766851681f6953be85cd5bc3f02088ed2304d139422d6a32a1f68e07012f56061b9ccc098f58539164a9bd2cba02e40b052537354df96200e04def714185c5f506a8d3d892d1740279ba5d1466e97c8075352807b2f11ae707ac8460c9d845683f64672d8488922eb4c2ca0ea931f73d7025d85c7d1b1a48840b6d24ecef7fd07c61fb7acb63596d1db687fa08192abc7dbaea450ede078930ba510264ee4405464ea1039370ac411a1b878617fbc38a9963c873f74084ca7ad08f2cec9c46adfe16eb4713180bf3b8066258134336bd4b8d372d01efc34fa543275ab3a4716c52f3124238742250c4c34112ea4da348af000bf4ab1f5af91220036d6755a94695a1d96e4c7912724072a701081505534170b2463b3e1951dc952ef4a623c880c98ecc6582a7200048b75ad96807cbab3aabb18c9de5440542348d1961d9149a9f06f449caf6625bf45b190af9cd81466da782acdceb8e897e03c7d953de810be192bc242f3cde225f7d55130a872403cdd1428461f5b810592b5c88bca3c615605cb24ef7d883003cf2c112db242d552321c239cf1b20a0f2cb4f8f99e1cb9a0a7d7a251f1cd858bd74753551f1b1a26f5a578d102c99b7ec41b92eb9ef0baf9f285393792481bc0ddb809eb5a54cc7301736d49a513f4375750146540733a30f8b2190880c87c502e10c56169a0a42c946503392c5107502a6f914d61bcedcb59893af139ab00a08278a94cee3a62fc771e1d37dce38c8225051fa16317b7ee202b5fde800d065a6796d4692a22e2341d13f815993cf70e274eb98ab6c48d1a8ae55e04046f58afa5c032acfd186c597932ab55c260f9dcb0001966e46a46672a74f5861c92516cf95be206b46176b61d90308f75cd47080a8b1a49064ea19430edd11a028fd055690c3592b3b5fdcd787aee0b46bc9164b47aec71b5c0091b14c71f02a4b35eca345d58e34a54d1391bb7b1e4a4a6e1402a8997674b0b8b0f0388cbeb63cd737ab93a4fac0a66dd87153ec19218bb3ea6f452b0831f8657aa15e74be304cd193d3cc9d052bb885382bbd03f9c68f4c7f52d4c55a51f835166a9ba35f372e96e3c3636829d06ce6fe322bbc742c145858157bca2ee69633d76589be90a9251f8be3d16e5a533ec0e64d334fc469308ba0f7b160cc57821986fa2d56937c0b07800c6bae5690cca14359d2972b40b612b4ce470508201dea9d9e1462cb5ff89d39d3565b86b394e8515cf9603fad79d0578815d84aa89c8f5b9423f315399ec828323f64e4c2332571c3ee55897111ac8aec1658774764e5d881c46a3c0f1816b97a6840d3e9d2f64b294e64389fc97c16317bd4043b412b46bdbcc797c68e99ea915f446a13ac901975015169728c0bc7d3dc2a9399d638cd2493cb1aa062d6d94607f68c9444efe1e87250ab263d938a5ec6d2d916c2b46a1d5010b4a14479aca80088fad589240ba75183363bd5d7ad548c3882bf2139129411ed46571ab60dd5404e4b25aa9dd8216b2460cabee8d67eb496176fc01578737a0d4081f64387fa3d4672be74c554d3a1c4e917f01469546408651766541e80879015f2847b8628903550a4d5931c5034a924401e448584cd72abec925cb53c202ed55ce2d46ec54fadbea474c346093e683a9044b181eec0741c0d6d6c9e0d60d9559838d8414ec7f5e860123e990e38a6e8a1ec66818083c2f2e977998a5c5126199dc808a44f3af3d5bd887bee4454920d71c3afffca447643bbd4b798e8ca00fa8c3d8f902f960d8e0b1782e33d074c9c9a2d4680f652a399cbee1644a176cd5004545854bbdc5b0381ca4538bc7b0992c30adfb46a597263807a1c7e0b4012b08b6ce8f88bbebf96d1ca38bc0bed6fe278f819a0914c65428fee5067aac7c818ea2851152c6b60d1e774c8d5b7b058b80a320f583504656f4db38e0a386d344cf2bf142574ae2a3ada7d43270333a46011f162b6fb080ceb57c80beb1673dbf11b6b500824b17c9598349c1c240bca90db4f9998bb8c24bb3ecb4a97bed0cb4b0fa0d329bbd94de93b4f38ef1295e8220454c96f6e506ab027245809f18fe613268e0d726071a204043d046477259fdcd84ab8a619c315454058eda5eb10cd802a587979231e4d205e2358aca9426341599ef6349dade0fd9410f6156b91537971e4acde8a641f650cdee4b9f75ca736f505e9dbdb2f544d84cd9fd6042ada4858a060afb8692e6b98a7fd32d079a23a0219c78663afa585c8676fc474045650dec72752d6229645882399cc958e82f21a8c49ed858a2b16cb334253b9f084fd528142e7c0808505f0e6eca4aa93ce884da4db31c2c25a1093d347d27ee4b33c280d4535960d8c50d15f902ceee50d8796442b6e133c06fd42270f0d6415e5ab2ed4ba17368c7e47cb9b7671a954d3cab227275070e4848c76c2186758daaa4aadc7a0f0cb792ad2b0546820ca4164e80026ad05bec5c61c2018e3db182654789ff54f379160ed3336909dedccea487538ba962837d993dca71329e59fe995e9212ebd1dd2a7b726a209ca7c1d865cd418d97171fad1666a886279f0bad289ae85a57387a51fcc38043c90014027f2312777a8d3213309332babd8416c234f88405e1fb4e45667767c517860a21da8ba905b2654ba46a92c2cd0c8a5e5ca54391a173dfa0e54028bc68da49682d0d380f0603a9d5345ce4548af35fc49b28e8cbc5ec10aa1ef8e28af468c120b8993a05ef440149bfc2b91cb68b860e1d4f4b0654ee87cd5296bb17cce0ce44d28a0a76441fa49637664374b445e4bcccf0d0c2d34265da15ea04c279657944adb684a39832e3b4d2480a3cc0441e0c1462b0eead9e6b8dab464346e6e4d03d000b6c9195ce3519064359d02d473ac738e4249a84311e3e879cea8790a0f204135510232a4b0095fd40086f21946732c022faffac38f22390d81826e03a303bb2781886331d4aa7f30a07243add464e400450dbdf1d69912c03826560e3a115a6e00924e9cd59c6abf66de5a5a45cd1d34f092251828f24ab340bd0e70ef51435596af44866b3851e3b25c3133910b6a84617378b081f2f002f68268669b25833d23874b5e85679727cd3d60f1b4d4120a91b5e21c24d8ca4436906815ca11ec091c72fa996f643d6170f49bab9b7609f1fa9f407a1d63d488aba11022f70d9bbc1272eec155a75e8ca496955ffd3e060274071f2abac88806dbc4642513fa4a63e7a4a8d4d3251f219e38527200b942ec9837b91a569f3e2c9f4e9cba960f4deca7414cfa4124072dd6c2518712d184ad8368f925b8627d5417bd6ec3dbd217fccc7507378fd8381793ce614d5817aa91e59a42b9f88734ec01b138329821437a82180b2c52cd5b1761832c134f60b21a175060a3e2b1a17346582bb197cc47a6c7428ad7742734b75ce0429f4ce90fc8e6c1d2d64226b5ec0206045e0bc2a46ed124edd60760d9898357a71a238125c4823a2939435685db5d8b9210770d65cefccaac48799840d5d30a2c6916102e3c0614bc66b520d18ff40dd946a28c8d8349a8831285968178789545cc29ea122d3404357ca1cb1d144c60ef2085e526050f8d88ccd403f41a94e19a8c9d871b8e4cdc90a28d6460d2a672496a336c9b58ebde9e13d0e3205f15a83a933759b94a06d3935aacdc29ebd3916c8175b81fb26ee2ba807d824ad1023634cabd78a1b11d9c42201c0d156c8e46728451785d9bc614123b6b048d1322f36415e5bd69354d76b06a96903c111c5d57b0ebd2613d2e741e3a27cde51d725271a2236ba17999cb6a478ff45e21048357a0b2ec0368b8ee428af43264cef481a3933c0b889b238892c716a153abd7781dd92419909c4a5519f096dd84cc27d08f46144b52a3d2f2a36dc5a9c9526480017f0c68d1d7210decb348c9f76050b90805bb8ee75196abb5555a8f852c5f74c9ef728640691f11c01a85284f39d4a8b1f6220aaeed943e3ea894861380d33b1e3e79d8582a84cfa08109dbaa81886b619125db19c5a73be032c0f645b1d1c03f2cc19843df4fb970591f72c8d8e49e4a3d9ce1a53374d2d24e260db90a32b05cfd32a34d5801d09a20b0b59c1e8b0fc292a33f5918954bb550d25d2e09381846adba8b134b06216572bb2f232ee0129323ba539325c4067ac791338f7312f60f40b42e84614676da7895d340109317eccde9283cccb065afd8d81babac19d685489d2411180b872f54db7aa2a1655ce8c12631ad789215902c45a3a707343534832a185e2531a56d90edfadb21355908414517d9042b0b5ad1e4a704e968a716ebfbbcb9d14bdc2e3dcf4a74b618523e41049383a1a4e58ac6b4020650fbca757654203441a59c07aa4570cd138e3d51472e48050f4cc7f1042bf8a8c6a4af203286f5f4c25477b94164f1d46035030b393e4b0bac372abe3aaeb2372d89d721764b73d990d474c95548d89d02ad40194d1cdb5748f7f4da8203b77638921fcd3afecd1eba4ee3fbf32c8cd0b0801604612f40e9056ce447af938cb106d7f050331d530c58bc1f6b9905181d105123579d2689322f65453a29421b585a9b62b5910d6e2d6da5e77878b9b82a0f487a8cc41ecb0626e36746c13aa514783a158018b77475fb54a3be57f0d1fcf00e91aca379a249f860e643a7ae9943163779c5a647d7b39c026c037b3cc8701258b2933a0d1940769554652395c73925d5a18515a128024189945493bcacdaf483e5db64ddf001694a1d08c1e88b0e3848feb23d78f004a301ec0402fe6b50602d19ce49d082d1981f2d038d0a639ea8f27469c8cdde44fd593519f8d1e61763806ac21a1911b7253f1a271ea6781d69541b6e06a7961e38568ecf2fe6e441840679ca953f8dcf439513983b6cff595b5634aac4ad21178b2549880253959f409aa94ca460adf8cfe4a8f1e179b2e163334ce9a082e1274008ca4cb1e80f202703c9a631ff98a12a0551435e20cfd602b93fc09f43c8293f99db97531bae9676a5cc2f60845eaa9f6ccc1ddbd520b9bf004d47fe32b2493ff2c8900fd8f1972df9d7361ccdecffffffff4fe626c01343cf37828833ae26e48e6c21230046df845a2234bd72248742984613a8911a04171664d180b42a889720922261127e9059300bea6d62ab2385add494070920000230e224bc14a5412838550556a0f0d2c7a2c00f152398d0b113a09719e5b2cd96dc314874a0c4080b7b75970889fc43e6d05a000e6ab1dc54b1a40a971e3469f21acd312040c97d66da5b628a8a468a98f04f211e63c9504b61114f14464aaa3b80735725677eb460f882929ce543958c43483ceca522f000c89184d48f9cb45270f0a4b9028394d85f140670ac5414d24827a82c298027ce5985373a5c74908d81d4a1f0ac42500a2d4d4910b55e7b049078071966966f3c8512bb12a792204b00a08c71a09708d98a9867c6a793925751a5b761dca5bd346a8c89d052b17060824f2240253630c2326545a43d626f707c24bd284097a20964570f382271661884343d3840e0815406f36393200343d04e84eae082d6d3ac93910f7d005ea90ab536460059aa5822d2c6120428236b734646d921007994b2449096c803c511ab3669f40cc0432644944bcf131ccbb70c562c2b9648523196ecf540f3c68030441963770648190abdb3126fd4b86b6e85a826489026d200364b5dc0c8a0a4a12c6e930068dba422480743221fe0208ab2e991d446543f9421e3846645a3565eaa223ea4120a138dac02a60038b34b4d21195b0691118a0a03488c5a4c9602c3581d574162311ab125115448d67422b54208bbf4c60002e9ee073081801c07c51a1e4b8c9c4450dba0c746b0ad80211aa2986646f871852bc194536f89f8c6fe0644d890088c1af7d1eb244543016300be301456e09a5289922a332a71004a0128100011220573d4af244a1a788454f242692c8a052f14383813d8e13560ec2d45467926ab5191e09821a8a85d61ce1cf531f943208ae140921872246860087ac960e90d84b104d6392bb644c1d194080191af0f26bc9c45d70af9d19ac109859e372d89b31c44d218a9b1a3c76d421819055aaa5e6ae43920166838a9451035e7213d41def4003a98cc5c390132d5ce1c9a3b954346e7005522ad812b44434e81d9f290034f2b4279b07c12a371c58b544a033e2508bcd9b992a9b5317baf854ed49985ea0b195e6cb862a0394373e207172a941c32461a0161331721458db557b73468d1e4858929406a6545f6da5865b668a00a64254427e1165c84b72660e806cc9ad3c619c3c3481f3b81f46070f5c1c8da841b3dd2464031c546471c17ac89e54c19102e8f564067c451d5f7ecd5a0c5a164008e6b582e89d3e98c8b25196655efa442c61b546068d3890a8ae68e1472061c3850365cf396a4c9a22e93da0e3cdadeb318021a55c290aaad07a123552b607c8d3ab95103940452b28e138f535a704dcab48b4a0cb22cf1061c4a25a82604f9239524478f4c022e8cad88ff3845e5cac1c70b835b684ed48c6767a8b03a42c38eaed9b268897945b6c055124f71707a0e25bb29d479b01560ce9a54547af572f1f420c20189f0c5ac02372c1dc823a49786039c0c50b18a582561ac0f6bab4e582f25544577724d68350b478006476821adf472baa850a642ae54126a11a8936682b5ae978735c3e2a21ea4ece88ca950f3095629314d480485804e405aca39334305535e9f92a7a710239e630ea81381a35e894496d88c2013d049c9a2bb6221868ad1ab3f1f484032a64875e5d3afb83833f067471e8c1b1b521c28d3c76c031f66da0e0848840fecac68dd98687259818a9058111b71083acd21c3600c337057690a27b332b27ab5a27423c023149554e951341786e6288e9054413b0584b422ce28e0935b88ada10893c54a9125e9cba3445aac9089cd18b0b970f561faca83915e7e18f8985042734bd54c85658aaa4bcb5eb74e647a03620dc8012307fad84a402288065aa28e326294c599a362eb0e273a38af50f93dbff26419c0a5d0ade981b023347690b9a58478c6027250dc9db05556dc175189b8c123b01265756c2b98ccfab2e67a4ab1848b90252906081b2cdfbc1e18686871e081a0503b92c8af3a403439299315b6a00d9e57d3873fb24392a2a4c9d34a8f0ddf033aaed54901017a2cbc62a9e68a8b4c733804e9682ba5e6e2480a534028559b8e0ae1995057e106d90b383a29229561354a080d3a188c20e8144095061c658a9848c329949bda9352ad2615302365ce7800d494293174258855201570cc5a81c8025b8ed26ad0e1d012806c8c0d70a76894d40e0651b10cd5d2e5290b160f0470f1e1f44814188d08b5665d20fb617765acc7a5066cc7a18c3f68025c2750ce8039e3c404ae415811ea660d4786573cc00e1150c12a481d3248c433547251da80b1901b1ba335618289410ae0cc19c2e9c1881684ce62bc7fb9907326a0152013167762f2487b63bcf2b1deb00ba564e4a2c558a31073a3e487d984eb194761705c2a2b3166925926a70d340b7e60bc90d82231408d0430edc8d209d5e4d6581006b0971c09bc801013514405843c9c1abddaa4864c5aa65147331a4952b7ce4cc1d0260a2526ed132e29d05e3f5eeea0b883e20c0e092b3eb004955973d4a60899d6808e1a4218e0adc993d3a42c54092a67faeef4d549e11be3c0038a3c0334a9d10128235516c24e8c4571570c24583b9a3242654ba72d037444e8b1a1ea005ad60450c9aed712316117083c6a9ae0636a4007b2248c04ed4864c9784050911c6b70205b6869f06302083e259058bdc95b2d62325a61aa28c038c97b21ca8e02309c6831113027890032c057211e0c08052bb8429aa7f6450d1826211c68ba2346c0aa44679e24212392aa10d551a705934a4567fd59a0e9928003495a651926cd4152a0849c3c37288b0aacca25671380237d6994386c80d045c78e8cec130a54c432222a5e69348c805e210aa0e50a191d8e643c6a778d819502ce0ebd0218aa8cf6f06a13d4030811c2811988cac6d23c916561fab4bc89a2a08fad3d1f1102887d29d892c40a202d74e438a88389062e025786af78ada97bb32b4c5aadb52673a863179060b97380e0892c326993953c71489c3949f39787d653c402044800ec3943c353daab297c967344cc80f506af051e24690b314c695350af2268c1e24810ad211bf0e2ce8aad5826367a00f9c82027968e154206a59db8c238be50f0a613001badf89662e688490960627047cb93e188ebd451865291cc9e1c68c4830ca0b76883c2a44000be144293030108806284468f66050fb9aae2619618980f090d12d9e0354880072f663e76ec998971bad6c1358b9754cd8c08a72e20ced0cec4f8d6f0c00a744c40a3cc8a67aab156e345160666f2e0486993164db55156d996f8d3a644084e547fe009230d51b337204895b314b8deac69f124644911364a2f9061e8ba94c1a38501904a773bcab009b2293b408a0d0d9d207c71ccf82883868d99260258755923490d54804e276a385161767caa5989b8c8a7607c99128a8784039cc29490cd11b3344a120c229d9ca4f98349111b3c026e9c0d3b7978408a8584294f63852b07d4e2d24c8859b2d5e5540f392500beae0fc834c48aae3a521d22e9a9e352f350ae3e0124b56a8246819651ac882ab1c82d3b36058ca0829322ba8bd3073419264ad961e462ac92f4118b600e358da46492125d81e3520011c75e6e883c2c00c53d8a23618a4a82c0a6491a326c515a230c11b4a1008e2a5ec69845182859cbf07a9ab4b764ac8f0f32678492749aa800182b1dc2b2238083342604defce030c446298a226a9f435eecb410c47a9da571a955e47a81cd953ea35614f07396a153bb8187193d4e2c5d5571398360555a9429d729518e68a28bfc96078b604a8c4b9a97bf198a50b918bc641832bc106626a8ede0f7ea3a2a03940c80ad68a20c0d56052b7a1eeca111c5ad039535c74f9ac4e68ce86ad1c30e053f500ebc10874535661d4ce562a2e908e23abbd38e4a80ad407311c66e43a83d5fb127033943812967d6d620594449401a628b31551d2099b903a98c910b3045e2284264c9c41ba34149587c809f35474ad5a8ea19a1c6eb829b9a531b90abaaa429f03640419a02208e2e500235a5ac85173594c2c811e026232a46c842481bc0a0039544c21a7dbaa025422832626b14f2ad8c8e821259572c2cf9b2a9baa445830cd49b2675e7962e3473c92f13a1060c10904546b69194f313a9481f26c84ca0d4d47cb42a34cb8ed0aa42089c10f09e604143069d5e352df9c0f18961c00f9b804f4a040c27f9a1c0804a08d34d1b07a9d42ea92ae02848a7034a7bc721cb953b490525108cf12ac541d78c1b9e96f02a100b908c14310204f942e6231a93e5613813a1151c03ac0a203d08482b312640080656c760d30ebcb12aaa5e49a325e2281735f0300055872167b8ceec3832f94443571f3817a10c94b111a0423922e5cd8b393e52054a9549958c54630ef8604e8eeebc42c1638f45121699649d4d33499992a18c035190302940653d01276dd2a3b83845893b34bac4db860ab549ac9090310de6bc9aa3674c24246020b04265466b6d09dca707374e31c90c807ad651691350edd07025ae8564d5610ea22b63b888e1885586f987d1002b2e88717cfc30a5c38cfa74d46e72e5d8c3e0515ada0d353eb42a8a76150af04787f726886d0cad0499d6705577c17dd963a8572009a9184d78652cfb7b934103195a3d8464fac40208a05a8a065c65a090d4444a63edd115349fda785cd728c031e0017a7c9982f8487ac11769afacc823b9460918201aa3e992812b8e881407288570d005e74d0f118c91832baf1605f2c71d998f4d411238626121e603721d5306e6270f0a3345a65e64881a384af1072880d283105242079e04d2a407f33a90175443146f3acaca9bf00aca8b30406e8289c8bc38bf860148edb235e005934c4d4a7d11d0c1c3884605bc00827338df9c68fb62448dec529a2e3385de44ba0514705781354306e1d0944f561a6f9a46a1285e8c940911200f1e3c6777b104f49c38b80ecb269b0a2c95076ae86955a050010dbc2aed8213652c10205b7c756851b9b250d3cb03a827d50012a070a65073f4f6d28cf532cb34410b813e43494fb47cb97226254b904e463f3fc65e8020a20714a13e28d540612344c09070c6260629059960177336b55949a3f1a04aa0ec4d17392700a40203a00e972858540162ad4c88822385098b6483525d5a616133e18c418e4d9b68e5a1e963a70011357eca7aa4f1a09312e616428a83bb5265c6d86ca22196a4109a334804b4aaa757204c1419d499ad81c518faa4676d530b5599c03ca22344ab2445c41d4c095045629e390142c327116208b83ca114a42036ea89e4b811b1ea84590421b182218e6042dbe067230b9aa9506814c4b4126e5950bca34207fb189343c01a1225a88619263cc1453d1296b6840aa6cc4e1ccb5df0694f1a1b9889140b892203c6b48810448a6581011792203ceae3a51472119e9e30c73e2f9b35856e0952d5c15319860d708514d542d28858ed4441162346038e53344cabb2d45c70b26905a257172c20c3eea85831233a668c136989cbe9a80419a34974aa50f8d0480d8a0dbdd2caac0110f0e3c3a75b88ce76b160a3926154893495d46cc4388bf56cfc40d3c413850b981549c8689c416a32bab6a16a7954d9728b170160d9d85048d75eb5ad004953335b551570032746cb0f16197c967040d1811d63666c55af125b60608861c9568349243a7cc00be00a52cbb0e3c99b25b3a6401b55300d21d04b82a928c1099adc4c084304ef3509352604488a4d8630581bed727a6236c9f3e82cd48d32516a81e900f2e7500b0ed0a5100232e21cc1d111db0306822d3c4a8b0085190b302447af386da6490b551d1ed4f48c9cdd60d313d40586985d23197b5c98ba9420cd4e832bef90552f48fc7095a6098a18414ccef506a239556238afcc967413447fcc3ce1b283ab809e314e4e2e6ddad51e8434325b636a95329549c5a19b15695eb2256682d3112e00dea0ac4e438e9488204933cb21e994a016454551166c62c151561d687a46424d5800a54ae254d3ecabc69ddfdaa50499fe62b969fa92a3101ad10dd7ad32535a843732219310f25341059a405dfe067cfaa3a896314d03f8c19719291238ec593b83709505404a9d2877983c81af951694412dfca0638326898b1b265c8afa10cc1830a2898d0f2e74781002c448e563298204648d37b3ce9a752fee6ea470d14004ae0a647e6f04a06811c8f9e13287d635c2df11017934a9b1f179f6aa216a6eb8f82a28503bf3c4541395a2ecb2250032174344942cc9260084a6f4b542e40244e200a3151a010da290f1b055560dd505885072c38c5460161d2d00e019336210529609118003213201717839bff628d0d58102910749155a00305d02b124600459456799121152de1500d1d02304da84f8b0c7d3093e1e7f288cb9613305532991b78a1693304722ceca043853a526650e8cf0d49c3c178106783212f0a25cd2628123c41448d2008e983148631c906b02469182a3e46189d3d3481edb191537cda74e3666fc49314292a2332052a5d912248665c52c09ac5a4f2c1f283292629cb4ecc0d1420b0e176a0d48b9f08b11e62c6cd7875a6a30c8a931354d4b75eb0922cd85ae2c6f948e44287d9232ca8cdc570cb118c8a75007d45042f0a0ea4af82359cca880811183150b4479b60634450c41e3072d0fb0cd9b340be812cdca5184860e490fc4c60aad72834a13a90b327c8cedf511f16116200e4d8c51903a3982d2c084c912194e6804064579d385038b2023e6983031a2898ab5e49c3a32065142f3a9535f0e64da9b526560e2bc7a9b8c7cb559c3a84fa5496f000c4092667c11c50c232c2d7439507244d318328d8e280eea9cf0f3a7c64f9c2abc63a63206ba66740a7b6dfd5883e93c422d120a9095eb15b3626296274bc50d5164f4f9527bd5a3c4a53d485855143d9a44c7d51a2664788ed143b4ca60f8f9232abf724c620848aa68a237611384076f06b0c676e346cdae14b4cc4fa4395b32902ca44201b0907d889420020c1da3f8c4800be9e09202192053c3a1b0834f1a833761b09f2032680a382932000908a60e3a5aca54e481d504c098a637152e4ce8f812e5f51cab04230f89002776996980ac33d424d0578010ac107822200a8371968244b5a0d0259e36a1695569ecb980c19e8c2f330e2da94c98cc3bec99a2097460b41989da02528515210e648004ea728169c367aa449d3e5c69e85421821863c17468e2244a1c398dd21649c805c2d1d9872356da24c0356cfc21d3c3ae8f800c6958d0e8f1eb0120d62c378c36316a82011303a3a44aae13246268719383019051c906668754edf559b3f1064396393a3868f4a983760b01a6356ae0b49cc86548edce1c0777621532b94c9a42c24c51a3316bca2b344f66234cf06255484c12b90473de3907635aa0d5c8c12b8d1d114c426d7d4e723c64aecdb91568850e340e74aa2ee0181a2b0e298bd4856ed685304600585d3ac165559a29030a6d3814c751999f166918dd0d7257c821674db2d509c8a6360c56f8d03327c8190e65320a71591345cd8e195550b488a462019b016d763690d561f1e1551e516a88908ae50259814d4c408e53591e2cb7504ada78d3242b75a708c08b0a209d82842af2a96a8290802f648e553a20c0c102a945794553183a6a4618a9c853e98d1c1a3c0c177a8025c14106e3a054c9839b31a8eeac201e8d98fe0900602b44884b599818a0104a93861a53ae052ad5d1b289c91f084e249a2085bdcd512063e5844115e3cc8b015a6944390842c84380235e929081e0a55749a76c8903c5c34a0105859a24f48814728f77026dc804e637a6850b91b65563684218c4ba950ac9223f67bb848caa2eb1dc8192e54aae8c1abb55047e84e2a409501f3d99e234893862413c3326c1986a41a62b75ac6dcd628d2ccdd759350a29b727154289d9c3010d9e26154bd234cdeaf247ce08194ad2b40030080a821d6289708dc1e30a0add1d45b448d4cda03b60e6876749a966ac23ed35168124510f6ed2e848b12ae021029a1e7ccf034de2a52c6249149564b1c985eae9c4031622c204a0cc7a8c1211e70c8d4b17b6a69b831b5126e4ce7f17c36f92c38a0ccd693b85eebdcbe7fb2aeff85ae89d140a3761577a07a546eff85aa8bb7ca3eef3ae0ac75357196def96ee1d5f0b65469dca6875dadad951d0b3b39b42f3946d0765b43ae5b0031370155cac4636202ca539841f1fb32c9d61d0e128d3a71e5e30a0b2b561d09530a72e4479031753090283a8cdf0102c21288e9d6005a295a1c5744a1f0678cb135c4ab64111a48c8c8098f3900a4e123486206b048d0f5f88a683823c22d099630b8d821a868ed405519a2f3234449b14e97542479e1fc9595382ca35757fcab8a0b931066c4786b5bb2e46e8ae842ead696a4dc9a0f46745410400523331b724a7fa24b142d86c19d2fb00ea140615bda26c72e06882820b04ae90e9c161c1a34f2d58371200b9197940328e21fb8d9cacd7914f610977b01b3befa02f8c77a774a5d3094a57afacc89625093b3b0f70c878edc9167899fd67c370818dcd4e0733ea8c3adb78da4ad9fedc094af55095be1d96f02b036d09bf32389d98525f2ad3998472f8cfe1cf1af2a663569149289dd117ea46a15946a19ed4922a64bab4e2ffddc97435e1da2ac4499d49e59d8d182c4efc21f1c783060b744b2d6f7f1e28305040c01f3200c53fc23f59172e5aad529902d5e952a446890afde183870e9c3567c070b932c5499222407ae8bd3b943768b43831a2839f0b7e2accb9f981c0004181017ec6fc80f9f9f2c3f6ca756b56ab54a43c69aa04699121407beec85953c6cb95294e8e00d1933a9577335aa420d1e102dd843e0ffa2ce8b39666d667ecd847d8a775dd8aa5cad4274d65502b9724394a44e8cf9e3a6ed088d962054a92203d62289e8e1a3158940091a18e8f5b043e0a0cf041f241e373c647acc807c8ebafd6ab55a63e71ca44e911a2417ff4cc6993a68c972c549e283112a447fce62973cc607142c4870d16e62438d8a3608fda9e00637b8c7b847b585e7fb45ca91ae529d3243d8b1005e25307ce9a3260ba64a9f2648911215518da4ceac1a127460b14243e64b09b7b8bd020015b81007ac4f48011f504f55cd72d59ae50950ae50913a5488b0c01da43c74d9a315eb45481b2c488901ed3a77b27238f8a121e34d4c1b5313890670d2dcf591e007988ecaeebac56d6509d2c455224888f1d3868c678c942a50992213ef4a4cfa632079e1a30569808b1c102dd0407780ce04933c33366c413c4735db866b1b28ef2a4a9d2234584fee8a9e3260d992e58a6384142e4075ffce63175dcb171c7458a3b223a64b0a3737bb0e080da1d34b3b03bc43bc05e7fb662b54a450ad4264b9114150ad407cf9c376ace84e142645516294c8e0cf1418d3695773460ac3021c2c3053b15ecc0b5d96170c04edad9d8d1ced00ebbeb5a0b962a529f385d82b4a810a03d75ce84c942c5c991213d2404d283772871c8a8e302451d11753e68a81027c181813a6864c73ac23afd674b562b55a74071aae4481121407aecc45963468c972b519c241902a44a470f7e32933866b44851e2c3063a15e2da1a20584ba3731666a413a4d3bbfc68bd52756ad4a7399ae65c8ab4a8d01f3c72d694f972e54992207c3cf2963966b44841c203060a73263898a360ceda9c0173cae680cd11cef95fad57a84675ba144911a13e78e4b84943260c172b529c2421e2430f8636973a6cc468914204073917e4d4c9919333410e03363432394639413999d775562b53a13a618ac408511c427deec05943a64b9c2a509208e183f3963866b03051760348080d7671e6243430906616072cce57afbf5aaeaca2fa2e3d4214a84f9e3a6fd69809d325cb94274a88f8d883f35c1d3662ac380182c30538757070263c8083008e1a9c0170c4e0148370be0b97ac55a6406da21449d1a03f79e8b44943a6cb15284cd22019d2434fbac3cd41e3850a13203860a01027a1c19b036fd4dea4057843f606c09baf37adcb562c55a53c619ae46691a13f7ae8b84113860b162a4e921811e2237e9f269ccb1c3462b4301182839d5cdb023637696e029489b931c29d20730374d3ba6ec162654a94274c9216190ac4070f1d386bc894510fb509d3058b942544aa541e50667b23c68ab23c282644b4d96027e7d606020235016dc28c6d826d5a971f2d5656519d2a39525428109f3a6fd088f992858a93236c84b0f941e591e7e6984186058a111cd860603337818d83046cd4d0ccc28a6c826cfaafd6ab55a64275aa14499121407ceabc4953a60b16296b9cac51b24648750a6d2671c85861e243060b736f795fda1cac51b0b6266d8d803562c535c05e7fb55ca52a158a13a6488d120df2a3a78e1b3566c070b122850912223ff8e2378fa983468b14243c60a0530357a63726c1c101350504a8315323a686a806d82b972d59ad4e8d02d5c95224468704f1a103870d1a315db05489c2e4c8901f7a705e738983868b1527caa8ab4070c09066eead8d411a036a6906a4294b239686184cd3bcaeb560a542630acd284f98263d5234a84f1e396cd0984123460b95264680ecf1d8e1e24033a3c589101aeae0d04440a3604d009a312295139ace552b962a527d9926312a04288f9c3769ca74c952450a932241aad3d799c421a3458a33264274c050e1cc5b840507906967cec6ce18cf00cf5cd72d59abaca54279d2440952a34483fce03103678d19305cac4871b284c80f3e18ea4cde98f142c5093324cc78c850416ec2830506cca4051833033634c3eac235cb55aa52a13c6192c4a810203e78e2b4495306cc162b51941401c2074777e0ddd170a1a204880c14de222c28b346a04c803262658e65866582bcfc66b13a450a94264a9014110294874e59ac07ce9a3260b65891c2c408992064f478ec60266fc45051828c070d15e2243c584086adc08033313247324432c05ed75ab0565945f565aa0469d121427df4d881b3e6cc982e57a63c51420448751a6526ef6ac04031a2438631766328b835303006cd6c0cd898af31b9975f2d57a94e85e28469d223458502e9b103474c1a3166c26ca1e2e448103ef8d954e690b16204070b7212c42c10c326064d8c99180062886282bb70c55a550ad4264b65502d8f141512d4074f983861d290f192654a932341f8a64f3c1d345ca410b1210c86ba307061da16b0a19585390ac35c172e58a844710243e99122427ff8d879b3e60c982c55a2343922c4871e8fa7abe3860c1760509000018603980c76746f6d0d1020d1ccc08881218201f2bada82c50a15a9be4c93187d41f4a5509f3b72d694f972050a92203da679ca3b1a5f5898f88081ce2dc282035fd4027c21fb62fc12ece537abd529509d2e454a34a80f9e386bca84d142c50992205eaad3d799cc11238517111c2ac4b53140e085ad8017332f62452fc05eb96ccd72a5aa549fa6498c0a75e9a367ce1b345dca80d132a5099220d56974c7f2ae4617172a4a7cd0603737e14117055d0c140830d6c5d845d8e57fb55ca522054a9325498d120dfab3670e1b325db854717204889e346fa983868b14223c68a83027c10197045c905c04907101c085c805d8ebef962c57a8487dd254e991a24281fad891d3060d992e579e28290244ef752a6fc8686122c4860b746f11b63440a0b625c096b12dc62d2cafebac56a74271b2f4689116425afe68c9a3658e963566be70b102458911213ef4a479cc36878c155a4878c05007d7c640cb012d6b04b49c8d8111b5047bfdd97ab5ea54284f9626394224888f1d386cd08ce16225ca122341f6e0275e961c345ea420e121435d9c040709b2ac6519709685ec98459825ebba158b152a52a03a65aa1489d1a1417ff2c86983668c972c529a2019d283ca63075e12cbdd8c16264068b0203701c2022c6b6865580060196201f2c225cb552a527d9a2c414a3408901e3a6ed48ce172250b95274786fca04a9dae0e1b31548ce890a14e4ec2150707ae10b8520676e5eb0aabeb56ac55a7406dc21469d120407beabc4123c64b162a51981c19d2c38a1e8f1deedd0c17565690e86061ce8a042b0dacb011b0722666c56845d8eb3aab957554274b90aa2caa7208d01e3b70d69819e3258b9426488654f5a651e7b2bd116345090f18e8de222c58ab8256254095b12ac62ac05ed7ffecc9a8dc8aa56ad4a74c931811f293670e19953565fe0b4f46e58b16322a55a844514265c8ffbe3d7ab470d2edd18ca96f9519a5be55ea5bdd7b9f4fb70ab76e7509b7d578ea84f7dc673c75e1bd3ca50bef495b78af19c3ef5e1586dfbde5137ef7f2e0776f09bf7bcff7dd5bbeefbb176da1ef1edef3dddb6b4e9d7beed4b9077d9753e79e0b3bf7f6f6f2a2ce3d66d4b9e7469d7b7bef32eadc8b3af79e4fe7def2e9dc73f19c7bcd78e9dc7397ce3dd8954e9dcd3da9b3b917759b7b4fd76deec56d73cf7db6cdbdd8dd8ba7ee9e0b7d954f776f093bddbd65d4cd73dd3d772fe9eeb97b4f77daba7b70ebee455f69ebeeed55db3d57afdd73e369bb279db67bcd69bb07bf4e186ef7a02d1edcee51a3cf76eff96cf7f29eed5eec6cf7a2ef9ecb6cabaf1e94fa7cf5de68ab7cbe7a4be7abe72a9dafdeb355be7a4ba79e53efd9c64ebd674b9d3af562e7d4a9d74e9d7a532aecd48b3a9faf53cf65b671d4a9e7eaedb94ebd2afcc64aa7d2a917475b38e9d4d38bb6b01b4f5dbd2aecea45df6a1276f55caa5eecea3d9d4d572f7e525bbd78daeac1afb4d56b4e9dd1562fdaeab97acf564fcfa5325b3d2895aa6cf5e268ab6cf5a653f7e94ea954774a75993035da26616aec4ea554d8954aa9d1574a5d3edfa694fa74ba7a51eab3a54ea9512ad58d526367940a855f3c67941aa52e9f2f5e940a3b5bbc28f55d46a954f7a98c52a3d4f6f974e124945a7d32a9aeb41abf7b713c75eec551e75eec2a9d7b71358eba7bb1abf774f762f719c3d4aa9b0753e3a71b8d52e3364a8da9b19ba4c655386ec654388ea754388e5d29940ac7d527158e5d2a1ce729a970ec3e93d4178eab4de90bc752271c439d709ca774c2b19b74c2f1b375c2f132eac2d5168e9fae5e0bc7cd690bc7d2168e992d1c2b9d78782d1c57df38baf7c6b0bbf7c64bb8dd7be378ea84a96ffc6ce3379e4edfd89d3adf187ee377d946e137769f52e91b4bdfb819bf6ffc7cdff88d5d2a33fac66ef48da16ffc46ddbc378edda7f38ddd3786dde772f9c6d576f9c6af52d926df389eb66fec3e63671c2f5dd8194b9d7135ea8c5da83386dd24d419c74f670cbb4c67ec2e9d71dc8c93506a33de7be166ecea39e166ecba70338e9b4ceadb8ca7ce66ec366317769b71157e5da8db8ce3b619e745db661cf3b9713c756328ecc6d1a81bc7d1367e3edd18daba7153d9bab193af8ddf378edbd88da76d9c376d6357dac679d136ce7bb6711c759f6dfc74b631b38ddd679eb28da554b83a85beb1145a9d3edf24b43a6d2a63e7de941975ee4da1cebd698ce7dc9bc6aebb37855b776f0a85dbbda9b4dd9b4aa1eddede943a8d9555570fa64e612a4c9dba4c983aa54e5d983a75a12e2ca54eabb02ba54ea7d568943a8d52a7b00b8552a7b1933a855b26750abbee72499d3695d469bb97a44ea953f7598da7f1140f8f1a4fe3a8bba4c653f7c96446f39c703c8d5d178ea755a6abf7c6d3e7fbc65337fac6d3a6b285bef1344ff9c6d33ca5339ebe52379e42dd789ad7baf1b4dac653d8d9c65397d9c6533c3ce7b28da7d3a70bc34e2a8fa9dbc375a93ca62edc52794cabd22933ea84a5d269b585a5d3f8954e63f8954ea3ef2b9dc2efd3f94aa7b1533a75f39cd229ec2e9dd2a9ab7436a5d3e7d3954e97ae2b9de6b5ae741add6ba5d367eb8c5be9d4855be9b41953dfa994fa4e9751ea3b7d52df690cbfd32a0cbfd3e8fb4e99d177ea465fbde73b7dbed3a5f3f94e61d7b97cbe53b87dbed318ef9db6cb779aa77ca7cfd6a97ca7b0ab54be5369fb4ef7dee8140fcf1975f3dae8344fd946a7cdf87da15337ef854edda7133a8de1163a6d4ea9cfe9defb8cb6ccf739853a9fd3a9bb743ea7cfa9d4759f5368fb9c26dbe7f4f9a4e64d63d899378d3af3a6cfa7336feacc9b4eddb8cd9bf239a76edc5263e7147661e79419754ea74fe7b4fa7c3aa7794fe7348eba4fe734ca744e5da6eb9cba4be7344fe99c569b53f78987476d4e9954b83995429d7073eaeac5f0db9c4adfe6347edfe6d47d9bd33ce5db9cc2ae9eb339953a9b5337ea6c4e974fd7d99ce2e13961b7397d3edde674e936a72e1ce3b5cde9debb6c9b53293576a7ee338edda9ab9c46dd29fc3aa3ee146ea34ea83b7d3edd699eeb4e97ae3badc6ce377661a53b9dc6ad3b6ddd69920a2ba751d7859553690b2ba731f5554ea3af530abfcae9147ea753a7d2a99c3edfb7a99cba50b7a99cba515739554e5db8554ea3ad725a6da7ae5e3b7599ed94da4e5d25b59d3a9bd3376ea755bca9336ea7cb683b9dc6d4673b8d9fed34fa429fed74d93edbe9d3d94e5dbc763a9dbad1653b9d2edbe9b385a970b5dd8b52e12815769f54984985dd251586dd64920a3fdbea324a7de12693fac2ae3b7d61e8fbc2ae33ef7d61bcf7855d65b485be70ece6295f389e52932f1cbfed0b2fdb17865d3d27fc6ca9b113ce9b3a61f729953ae1f875c2cca8135e3a994e386e329db0fbc473c22e73e984dda5136e2a9db0ab74c2d1379974c249270c6dc6d526bce73af5f4a254178e5b1776c62e1c4f9d5317769f492855397561386e5da90bc35157faba701c75e165d485975127d4855d18ae3e5d18cf753e5dd885f9daa70bc72ecc7461774ac5736168ac749f782e0c2f5dd8859f6ed285a3efd485e1b7470befb570d4d56b61d86db6b00bb7f0d2296d6129b485abcf670b479d2dfc74b6f0eb5cb6709eb2855d650b47df57a96ce1d8994cb6b01bbf541e3d9c541eb193ca234fe9a4f288bece78ea5279c06e0cb7541e506a1c6da93ca83c964e680b57a5eeb3470faa34a64a9b53aad49dc254a93b7da530555a8d9d30555a954aa9d2a7534a95369551aa34eacc9342a952e9932a7d3ea952aa74498d5f580abbd2e90b4bab31defb425f581a2bdd1d2f2c8d9d782f2c75a1ee33f9c252b787139646dfa9139656e116ea84a5eed2094ba5b08be7c2d258e95cbab0d49d3a5b58ea2a9db1b285a5b0bbdc7ba5d51876f55ea9f48ddbf895c6d357ea3e93ce2955fa4a9fef2bad465fe932fa4a5da5f3f94adde72b85dbe72b8ddd570a755fa9cb8c529f5226f529cd53529f5297b9749f5269b57d4addb87d4a9fd2d88db64f69dba38753da54c64ee9f38da74ea92b754a61d78dbe4e695ed4298ddfa753fa7c3aa5d1d7653aa54ca7d455ba4ea90b5363a553aa7446954ee91b6d9dd267ab744eab4da93b6de1a6f48ddfa6d48dbe4d69ec4aa3ef1476a571ac74f160570abb52a92b759fd2d7953e9dcaa82badc6cf36ea4aa3ae34da2a9d4f571a47db69eb4ae1d695c6d468eb4a5d65eb4a5dbd56ea4ea9ad342a7dbe70ec8c5b69336ea531dc4ae317865b691edc4ae3a8dbc2ad1476a5ad14769b71b4953edd682b6d97d1560a7db65257d93e5b29ec94bacfe5b295c6ca56ea46df1eef1b759955be1776a37b6f74ef85f5de67abf7ba4be734a6beb01b535ff87d9731f56dbed1b62985df38ea3ae137da32e17709bfcb377626e1f775a7af54ea94be5157fa569fd1f78dbecee8eb465f57197d6327f4553aa5d0eaf375f59ccf77f97c9fced7553af1f05ed8c5c37bf1f05ef85dbeb09b7c63e51b6d5de50b5395ca37f9ba3055394dbe2ed3f94cbeb1d2dd917cab6ef2cd6bdf981a75a73055498deebdd1a7abf746a36efc46dfa83b7da3cd187ea3ee73f9be51d775a7ce37ea3e936fd4553aa3319f1b9deeb951d8d573a3cf168ea96e34bae736a56e341a2bdd27d38dba7aceb865bad1a5eb46f394d168359eb6d168d499276da3ae9eb38d36e32ab40abbf1b40ac57bbad32a349e52a14ae7940a85be2fdc9c52a150aa3ba542dda7724a85569fd227151a75e2e151a14d2515ea3af55ee894fa42a92f14fac64e2a8fe90b65469d51f885baf00b7db6f00b8dbe2fb41947dfe80b85c26fec8cbe5028f485569feef285ba4f67f285ba3035e67342972e9f13cae784ba796d3c7542a75327b40abb5327147e61d809759fca167642dd981a7542a154a813aa74429dd0e89bf77442e326d3195d3aa1ee73b97442976f1bbbd0e6d4855661d885c64ed8855695b00ba5465d28141a3f5d6875f93e5de8b35dba5065dcba5017a64e5b171a6d5de8debb6c5da8dba385c62d94cf09c72db419b7d067eb84ddb8853695f1b4854ea72d346fea84e1161a479d700b8d3a9949b885ba4f690b8dbece69b485becc680b75a1d116fa6ca1d016eae6b5d0a8b3853e9d2d74e96ca1b11b65b650a593d942972dd4854e954eb8faacc2543eeab3a98c529f701ba53e9f2eb36532a9cf6a9e92fa7cb6d5f7e9eabd4f9719a5becf25f57d5661378edfa7f47d9fcce8fbac3edde8fbcc53be4fd88da7c9f7196ddfe7b27d9f4bb8ede17c3ee3d8f98ca7ce67f4753ef3a2ce6712ea7cba79ce678ce77cba78cea73b8dba4ae7b31ac36ed2f9845be7f3e9eab94fd88d63f7197de156fabacf69f4197da153a7fb5c32dd678ce73e5d98aad7ba4fbcd67dc2eeb2759fd51eed1376abed337652a77bed33bad73ef7da67abd73eabf0324a6d9f4d26b57d469d71fb94beb1b47dbacc96ca8cb64f29b47dba79edb3ba6c9fb0bb5cb64ff84db6cfa79b6c9fc9f6b9f746f3f2946f34cfc57346f35c379ae7dc73e645dd3876e6b9b1332fea3695b1332f76e64da7ce3ce8bb64469d79cba833afea4697cebca5332f4fe9cc93529bcad8cd93ba6e5ed475f3de176e99d1362f9e5277e445a93be0d719a5ee709fcc287587144adde12a9dd41d2e7349dd517d7754dd284c7d77b879ef8ea8f3dd917c773cdd3d7747f5f974774ca3adbb634a7d9fadbba3478f7607dcee805b2ab3dd11bb79ca7647157ef59c503776ba71fb7ca7ce65b47d4e9dd11776469d6eecd48b3adf1876e368d4f97cb6f134ea7461a7741a75469d6eb49d469d55d849e51175c654a9f28d469d6edc42a3ce2a4cdd1175c64a77473476bacfb61975c64ae7132fea8ca32d5ed45985e329751975c2efbb8c3a5de8ab8c3a9d4d65d4e94e9f5429d40975ba512a14ea74a751170a75bad3a91b7d3e9dcf96e93e9fcee7d3e93e97adbb74465fa5d3553a974e38596d2ea36d724a6dc630b55985616ad37d2e616a338e529b559819a536972ebc8c529bf00ba5369bd1164a6dba4c6a33496dc2d4b7f974e3b7194fdfe6f46dba53f86dc22f0cbfcd28fc365de9db5cbe6fb3ea46dfa60b7d9bcd6abb743edfa6fb7c9bcbe7dbacba79cab7594d26dfa60b3b9b5267338e3a9bcfa7b3d9643a9b2fec36a36e53fa749bb10b7dbacdf7f9749b4de6db74994bb759d56b36e367eb3697addb5cc26d8fb699b7d7365dbdb6e9eac16db33985dbe6126e9bf09b84db66552a6d9bcb68db84dd780a6d9bb00b85b64dbc67db849d6d337626db26b4ca8c5299701b3ba9ccf8e9a432dda593ca9c4e5d2a7309b754e6de0b33a5d41766566157f9c2ccd80933f15c27cc5c3a6166d47561663576eab530733a6d61a62b6d61661c6d61a6ab6c6166b28599d1bd9729754ea72ff3e94aa72fb37d3e9df0cb74a7efcb5cbe2f73f97c99d3a9bb5cbeccb87d992ef5c9dc7b9f4c57ef7d32abf0fb6442dff7c97493ef93e94ea550e793e92e9d4fe674da3e992edc3e99cc65fb64ba7a4ee61376325d26d37d32994e668ce764c6d1d6c98ca76f73ea32dda8931a7599ee338eba4c178ebacc29d51975994fb747cb7499d496c974e396299db6cc69cb8ca32e146e994ae7136e99517799845ba6cb6ce196296d992eb4655661f7f96c9955bef6d932a3ce9619bbcc65cbacc2543c57d946a354bca74bc58b9d54182fbaf7c278f1f485f1982f8ce732a34e186f19759d305e73eac2787114efe964b6301edf8b07bbf1f4c57b3aa7d3170f4a855f3c374a8de1172f0f7ef1629709bf78f1d4855fbcf87df196ef8bb7f73e9f2fdef2f9e23d9d2f1ef3c59b52952f1eb47df1aa4fbce694fac45b3aa94f3cd8dd7b9f78f0fb3ef1f294ef132f6edf279ed4f9c4c373dd271efceab54fbcb87de235a7ed13cf85b64f3c66fbc4e373e239f162bc67ab9c3af1a0b013cf859d78b0cb8c3af1dca8136f1975e2419f4e3cf7e9c473e2c16ed289b7e7e2c1ae9e8bf76cf59cf1d4c573955117cf7db65017efe9ba784bd7c5735dbc670bbb4b17af0ab75438e9e2b9b1137ef55a3c2ad319b778b0db8c5bbc671bb778718bd7e241e116af3a6de3688b5755465bbc688b37a5bed016affa74425b3c17dae23d9f2d5e3376b67871d4d9e2b9cb16af1a3fa9ca16cf55b6784a279c6cf1f65e38e9565db7eab670ccc7e74e5f299f1b75b64f299fbb8cb6523e3776f2b94f279fcbe7ba53a71276f95c774a55f2b9eed28d9d4a3ed79d4e5d259febba7acf57cf75f35e3df7e9d4735d65d45d3a9bb1ebbaaeb2da4ea7ee0bbbeef2855d77dac26e0bbb52279587fb6ca93c5c77f94e9d52d78dba51b7a974c25137ea56df1d51d75546db66d475a34e66d475992d33ea5699b01b75ab5037eaba53f85d46dda8bb8cbab0bbf74aa16ef57d4aa16ef475c62fd4755d650b27a16e75f926a16e1edc26a1ae3b7dba6eec7cbaf01bb77a4da9fb74e3a8d209c3eef2e9eebdc9a7d36532dd187ef1f05c376e95d4a51b3b97aecb5cba55a5fb36956e75dabaf1b475ab4fe7b4759b31dcba51b875ab4fb875dd27b3756197d9ba7870bbb7acc6d46553195397f194ba74a3d465124a5de629a9cb2abc8452e1a5f485975227bccc8b3ae1e5f3e98497319e135e425b175eeeb5f052e98ca72dbc6ca12dbc8cbecc165ec6782dbc74f5dea53b7d97f0fb2ee137fa2eab6e5ef47ddf65d4d9943edfe5f3f92e99ef52f92ee13756becb6a8f1e4e3de7d28d6157cfb9749dcb6afb944e9dcba973399d3aa153e7b239754e9dcbe5d4b98c61e712a6b6d337ea5cc26e34ea5cb650a8fb742eabb133ca742e9f6dec643a97b1eb5c3edda9eb5cba4ae7b20ab7cee532fa425be7f2d93a97d01666b6cea51bbf7aeed2553af5dc65ac746377597de178ea2e61672b9dbacbbca9bb8c6177eabad5982a7597cd58ea2e63b895bacb6ad45dc6d367eb8cba4b77ea84bacba5ebc24c7759c573dd69d4c57397b0eb2ee157e93a95ee1276935057e92edd65ab749753279c74976edc26dde5b37597d57619dd6b97b0bb6c4edb2533ea84dbe5d25546dba514da2e5d660b6d97cb76e9c65465f47536a754a5ab0753a94ae594fa3a61aa524a554aa94a17a6c64faad24d5295cf369e566165155636a75458d95446a9b0d27dba532715563e5d2aac7cb64e2515562ef75e18564ea72fac94beb052e98c9db0523a75c24a17ea8495b19ba774c2ca3d1756e6b92eac54425b1756567bb4b0f2b9d7c24ab8396d61a51b6d6125b48595315e0b2bdde7b2859579ca1656baf15b7d9551e794fa2add294c7d95ee534a7d9555b8a9a4becae81bc7af329ebe4af855baf0ab7cb6f0abac4a5f25ec4aa5aff2f9becabcf755bacaa8532ffa2add68f455465f25148e95d465f455c6b0ab8cbeca6ab47da1d057a974425f650cbf78d057e9425fa51b7526a1af320aa53e5f25fc3e5f65d4f92a9fce57e9ba78786f4c8dbeca57194fdb57d954c6d4a71276e329f5a99442a94f6592fa5446f7dea772497d9fca66fc3e95ccf7a9cc53be4fa5d4f9542a9d51e75399843a9f4a3c3ce75399a7743e95ca3cd87d2aa3adfb5456dba772af7d2aab784eb87d2aa5ed53f974ea3995d5d8a98cba3d9cb153e93e95b053197da16d34ea54c22dd3a98cc2d4a6322f4a6d2add679e92da542a9dd1176e2adde9fb3695aed2f9369555370a75369578aeb3a98cba794a6753194fdda6b20ac36e5389e73695eed4759b4a69db54bad3b6a974996e2c6d9bcae9db3695af530a6d9bcabc67db54c2ceb609bbca67ebc2aeb20a53e3a8ab545661f7e92a9951e7d3e92a5dbd96da2ae32693da2a9555f865ba71ab6c2ae356c97c9f6ddc2ae356f96ca9d356e93ee369ab7ca3ce69ab6c4e5ba53b6d9531dc2aab30dc2a63570ab7ca25dc2aa36f126e9570ab8c3adb58da2a9f6dec94b6cae85b8db6ca69ab176d95ee1b6d95d1ea32da2a9f6db455465ba514da2a5bd885b64ae8b355e629a7d458d92add67b255469dc956e932db64b255f6e8414dc26e954a4d56a7d4e474ea469d536a724a4dc653264c4d46dd254c4dc26e5e949a749fcb283519bbcf273599f7a426934f6af27db67878d46475b9a426dd6732494d36a7543819dd7be1249c84be2f9c747b445f3819753e5fa5f285937953279c4ce2b94e3899a774c2c9e4b275e1a4abd7c2c9e9b48593cb680b27a3ce164ec6c9ea9be47b93c9e474fa26995127fc269379ef9b9c52a36fd28dbec924f44d3e9d6fb2ba7c9371fb269770fb269b53ea3309539f4997fa4cba53eafb4c42dfe9f47d2697effb4c2ea3ce6732af759fc978da3e93b19b176d9f4917da3e93eeb27d26f394edd3996cc6b033b9849d496772197526dd3c6772e94cba4f6555e94c2ea36fd54d56e3a99b8c93f1d34dba7aaef3e9265d3cd74dbad598dabac968eb269bcad64dbacf386e93b19b376d934ae7b44dbad336f97461b84d469d2d136e93d5a70bb749b84dc26e34da26db681b6d93ef9b84b6c9e7b34d56a3aff2d926996dd2652edb64f455b649f87dba4d659bac26dbe4d4c9d7eeb54f2ade5eeb42dfa9bbd756dbbdd6cd6bf7da77d92a5fbdb68d42a931b57d636a4b6de32995496d63e792da52e3d65d5263671cb7cba81bc76d9eb28d5b37769fcdb86da1cd78dac653e774dac64f6a3b9db6b1d2954edbbc699b07bf7bce186e63658b07b77b2f0cb7ae9e1386e1d665465b186e9751ea0bb75528dcc22f146e972e146ea170eb529970db465db86d5db8553a6325dc369570fb74c2d2d6759f52690bb7ae54da5699eff395b631dc4adb6ab4adc6ae932fdaba541ece38dab6d1a8bb378db6cf961a8db6aefbcc8bb6cf16ea8cb65598196ddd2733dac22e33dab6f0dbac3edb68bb74a754681b47a92fb465469d3d5a680b53ddb885b6b01b3fdb77fa6cdba6f4d9bad1f7d956dde7b37d3edba8eb3edbe7db7c95cf164a4d3e5b28dc3e5bf73975c2ce1676b65067ebba30b38da32eb37599edd2d9c278ed127ef1f05ad8d9e2e1b54a67f285ddd6553af5966dfca42edbd8095db6b1d2b95cb6cb651b4bdb653b9dba70ac6ce13756b6b1b255b64a673ca5beca76e954b6ae9eeb6c2a95ad52d9ba536ab25dc2d469b28d956d127ea7c9d6d57b93adfb4cb6d177196d936de79cd96794c3d475e1bacc367661d624c7ff1d278b228a4c4a8917767b3ca7b133c986e76c4ea7b00425dc9b043a09716d4af7d92aa76d645106fd12966e4b16454a1685cd7db2b51cc8a0a4fa2f614a4d924159b3248392e43f8c9341799bb66c1148b04ddd0e530e4b17eac624a06e4c62da8c9d32c89ec8f95fc020f25480889fd4a95319e5f016c36df1a448e6e48fcb6452a711a66d73cf4db1e7a620f19278beef0a6787e7fb9258017563122baaf19244deb47d91376d5f3cdf7785fb5c92b873a1ce1887db1b9b853aaa8dbc690bb5b3cb9bb6285cf8f5d0ebfff0218ebad029b37d86504e9d285c3784d49db641dfe51232262e9313677b633636e9bba4be344eb624d6cf6c9f786c2fd85cea4be307dcbccb41b6c4ed8793e026e14d0f4407152d5eb8d0d1ef353d8525b8c9178ea7716d51e71376a16043863d64089121438a439ed9216732c48993bf78e2ff26434e1c3284c890214e86fc4386341952448d2cc9a6ff7112dc24b8b541df25932cc9140306a653370a7d97d116766167a77497aff2c58b9208e14edbe68a0c77b02be56ba1cf367ef95aa83b6d93d369fbcc001747bdc0d742a77d87ec08dff19c1674dcc12f2c611a3ba11aa0cf76da3e3dc0ee8a258916b113ea3c91a76c6317cba7eba4e1017e611884cb6ca75317c574ea2a5f7845dc42e137c4b2edb074f56077eab6705d388472ea84dad94da36ede1bdbd94d63670bc7767631fc4aa5d4e99d0b6d61bc29356e6317ae32da3edb0e7193e974f7ee9e2d3556b62f5e94445319b7d2a9f3433c7d57b82ededdb4da4aa7d3f6a98c9b4ca7bbf7e245493495712b9d3a3f54e1376e97d116c5b38dbeceb8855da98a670b85df14d369fbf4d054c6ad74eafc30adb62bec9eae9efbbc8bbece58d94ea32d1cdbfd7ff0b791ff3c4e5604c37fbabfe1fef33859110cffedfe7efbcfe3644530fcc7f03780ff3c4e5604c3ffb3bf83ff799cac08867f0a4b80ddd879076d5d389e4e70992dfcc6d3094a783ac14d829b04bab8b6187ea552eab4cd85b630de04bc3737b7b9b7369984835b5278c3a36b7b9d12a66edcc24a097cee32ea9ca084a713aa703c75325d78c2cb845be504d85d3e9dd4e40417fa3627bc29a62e8dffc4931559f0a8fb64cbc0b6744297ee33d946412cdd67b29d3a3f445fe734eac67cee5d73da36efdab85db6b10bbd73a12d8ce72e5f14787378722d05bc38b8b937b79593c373e79214e8e05cb816ae0ecfadab8bc38bab53ba72ed5c52d9becdc5cde1c925e5e8dce8e45cb9389742dcd2cde573bb86401f1a4dcdaf21981ad39352c1f5192d7cd8386475f7e1d7284be9a3de949c585ed401ef2134fde8261f1e7d0d905a1d2204af2f4ae180ede547c7a168a1c0d66065740237951a919c84bb90820485e411ca667d2ad8527aacedaacd88fc263dd57ff03c8179025ee557a4a66436545dffb2844a932902d517bc18ca0ea2b87d1422086d28c5f141b4315dea4c9f263b8661b160f2f0365b7a4f63c8ac8d680d7202554ffa8ec5cb7bc4fe3498036f7e2155a1136ae528bbca10d76575b2e40731a0be2001150bc4cfa56c9769ad4b1582d24af8d858180a4e6437abb208c5e6f0694383d464494722579a32d5573d3fee688e1d3b424eb0dfa6253ad4aef65a9403d85d0f1a75a1205358578e1c7c8e81ac16f521504fb932236798f18350a081e5f254f0d78fc3db28b5bc8e23016d0bc459837806ca24780062e5a6b4840bfe38ca12d89ae45663c1d7f9ebd599c194767118691b2ba4bc0eaae39f7e9e1a5506042f54614f2b00d43c1125011acec31f565321b1dba9412b6ba0e3e9ada8706d6042acb613b183b56141549bc1d5eb6d0a0b0dc02fc3ad7e0279c80ffcf49c9f3fb0d03370c18fbc7bce497e20a93751edbf5e781b633cfb2d7813d1fe6bdd9be8e4bfb679069ef891ef335be847c63d5f47134bfc57e7fb087cf6bbf4fc104da4f45f633db3837e24dc59a1f35f83f1363a9ffd2abc8bd4ffafcc335beb479a781f153d5fb5bc8b09fe3f156759e17f7474e097897fedcc7347fa73d8815f2efeb534ef22dafff7e1ecd7a67fadd4db18f7ecc73e779b0c7cf023db375886e73f00cf1ff4f784b711cbb39ffb366679f6c37b1bf93cfb31792e2afa73c9bd87b0fe2b803771c77f65f21efafd5701cf6c971fc976b6cbfcfdf73eaa79beee3d0fd1fc6e71dec709cf7ebdde442aff75cef3d0c7ef56e3b928e9cf55e19925fe6025ce06fdfc6b60cfc3f7774bf13e2e7abe6e390bd4f423e9bc8bc8ff8fc4fbf8e6997dbe5a7a1b5d3cfb21781b5f3dfb057a83a13d5f553d7f122becf2afe53d0f95ff0156efe39de7eb91f791d7f335d2fb48e4f9cae2b98bfc6d7dfe8e6cecb7999fb711c6b39f826716f987c978ee4d7f367b0676f8913b9ed9e78f3c3eb7dddf3e9e9fe2ffab67a3f18d56f79c63fcc00a6f6280ff3af72e32f9fff23c0f3dfc07aaef2392e72b8ce7e0dfbfd6e07918f71f803eb36e7f1898e7fce507c23a5068e95f3bf23ec678be2e78ceb57ee395e7afeabf36e7f939fe5f9f87467eb7e4bb68e5ff1bf4fcb4ff91dec5bbffefc9fbb8e5f9da7c17cffebf0defa390e7ab8a3771d37fc5f53ee279be22791795fc7f779e857afeb5bde74ef467e3f383fc5d7c1bfd9efd16bd8d3a9efddcdec636cf7e3ddec521ff1f9c371882e72bad3791edbf6a781f333d5fd3bc8dcd67bfbab3c154ffda817711c1ff97e26d84f2ec87e31918e14726cf7ee5f8d7d8bc8995feabacb3c148ff9ad933fbe747babd89c9ff4ae4393bf8d9366f6283ff2ae24da4fed72bcf1ff3b7dafbd87cbed63db3f08f0c7b17a9fc7f81dec72bcfd7e53330c08f9cf1fc35e75f8bf42e4ef8ff6a3cb33d7e64d9731ff91bd5f317a47f0dd4fb28e0d90fd5bb98e4ffb3f33c44f01f48f0dc6bfed6f53ece78be36781fc93c5f793c0fabbfdb8c77f1c4ff577d1e7afaddaecf79d56f1cf136e23dfbd13dffd1df15de60fd7ca5f4cc7ef809007866ff1f153d3f4aee037fbf6fe297ffeae75decf2ff297a175ffc7f449ef38e1f48e86d14f2eca7e20dd6e1f98fecb94ffc1de9b9effc39bf89a6fe3ff639b8a58d199efd00bcc1063c5f533db3177e02c27791eeff6b0f1486fad7943c0bc5fe35b7b771d6b35fa167a0de8f2cf22e42f9fffa9c0de2f9d78cef61f15fdbf536fe79f67bf2265affbfe01b2c3f5f2b1df835f7af8539db7f7f8ef0361279f6637116b8e847be79ee1579a8e23fc0e47948e5778bcf0a11fd6b439ed9b01facd373f0c08f8cf4063bf07c9df536b279f6abbe8fb79eaf899ed90e3f81e3db08e7d90fc833a2e7a2ac3f578a3751c37fcd71b6a3fcddea6c2bffdef426a6f9af889e738f1fd8e979f8e977fb3bcb267fe4f04d94fe5729ef6394e76b8ee70ff80fe93957fa8977ef2387e7ebd8dbf8e5d9ef79f6cbef5fabf5fc65fdd754bd8f78cf57ba7731feffc8bc8964fff5edf92bd0bf96e93998fc91a6dec458ff1fd91b2ceef98f7d1e2af80f2c78cef10fb4f42ebafd7f36dec520ff9f9be72f53ff5aaaf7b1cff3b5c9732ef413e59e73fef384b341173f32d4d9af22ffda9d3791cb7fddf3268af9af81de4703cf7eb2de474fcf573767857bff1a8eb3414fff1a81f7f1d5f335d0f390cbefd6e37dc4f47c45f33e7279be3a9fdfe7ef086fa295ff5ae77908fddd4e3cb305feb016cfc3bbff40a9e7fff89bf82e96f9ff24bd8f7acfd7106fe29bffeae8997df523473c0713fc4849cf5f75fe3549cf39f90371bc8d299efd90cfecaa1f19e27d94f0ecc7eb1918e54746797eadbf633c7f85fad7ac6fa2f3bf2e791fd33c5ff5de47f8f9dae15d7cfe7f5c0efcaaf0af897976f43ef23d5f473c37dcdf7bef6296ffcfd0f397a77fcdd41bccf87cf574f60bc8bf56e76c70cdbf267c13f1fe2b923711c77ffdf1061b7bbe8e7a062ef9914e9edfe9ef0fefe280ffcfc3bbe8e4ffdbf30c2cf22397bc8da79efdf0bcc14e3cff15781b553dfbf179ee3a7feee760a27f6dec3da4f55fc9ce361ae097f35f7bf326aafaff866f62dc7f6df1dc0bfee6f12662f9af77de4659cf7e84dec753cf573cef63f1d9afd3f3a0b35f36feb535cfc55cefb109cf2ca91fd9e16dccf4ec97e67d643e5f29bc898ffe2babf731c5f315f92e4ef9ff00bd894dfe2b9be7def07790f710c06fe0c159e1a27f8dc8dbd8e5d96fc7dba8ebd9efd19b98e1bfe2787e9bbf0fbc8f759eaf459e87407eb71b6f639f67bf266f62dd7f95f15c74f4e7a27b1345fc57bcb791efd96fc473eef4133fbd8bcbff8fcadb68e1d9cff8fc0dff513d67507f31ee3ddcf55f0bbc8fd7e72ba3b771c8b3df8ab341e98f5cf5cc86f99107de450effdff35d34f1fff1bd89c6fffae02c90d28f94f3260efaafa1dec701cf7ea9de4749cf5731cfc1e58f3cf5fcb5ea5f63f52c3cf1af8d7866d7fc4806cfbff1ffd6bbe8fcffb2bc87bcfeabdadb48e5d9af7d1f453c5f053c0f5dfc07e03ce71a3f90c5734ef3036bbd8990fe6bab37f1ff75ed6d8cf3ec17e44d8cf05f4dbc8d959efdcabc899dfeebf57d0c3efb617a06befa917cde4625cf7e32dec354fff5eb4d6cffeb9077b1c3ff077d1f753d5f1f3d0f2bfc07a4cf9da0c1143c5f6dbd8993fe2bac7746fb5925678b78fe5c119e3fe76f05cfc1283fd2d6f3d79d7f6dd2731ef303659d05b63fb25ae8396ff981aede443aff75d2fb38e9f93ae61918e64766790f9d3f0207cfc1013f92d1fb68e1f96a7c83b53d5f779d0d96f8917def2298ffafd1d960861fb9e94db4f35f2bbd8b70ff1f8e37b1ca7fa5f336c2cf7e1d9e59533fd2c37303ffcef12e02ffbf0967856affdadc5960a41f19e76d1cf3ec273dcb4afa9115de44e47fc5f03eb678be1e78ce877ee28ce71ef0f78ee75ce207b2ef22f3ffbb72d668f6a6d4bec180cfd74bcf6dfd3381e746fe0d3d9b817f767b135bfc571eefa2ddffe7bec184cfd74c6f238b673f03ef23a9e76b9d67e0961f69e540a1a67f0dc99bd8ff95edf977fe76f0dc4dfe5ef50c7cf323cb3c0fd5fe03c06736c34f60f82ecafd7f77ef6391e76b8b37f1c27fb5f10c7cf123a7efa3aae72b9fe7a19bdfedcebb28e5fffbf336e27af65374f64bcbbf56e87d64f47ce572e097877fadcc738ef203ad6fa29bffdae86c70c18f9c749615f223cf9ed9047fd8f47d047bf6fbf4268afdd709ef228dff2fc9fb58e5f99a7c2e96fa7389786613fdc8b9e716fe7df70663f07cc5f526fefd7fc87731eeffcbf12e96fd7f199e73829f95f23e4678f6d3f5362a9ffde29e8539fe35166781827e249b3711c67f8ddfc73dcfd7256fe38867bf01ef22dbff67e2f975fe6ef036f27af68bf42efaf8ffd69c1582f9d79e6781cb1fd9e359d8e05fabf02e5af8fff09e3bc4df729e7b56ee207f937ace477e60a8b35fc07f8dcabb18e1ff93f12632ff6b92e73fffeeb771c7b39fdd33f0d68f04f42686f8afee335be947dabd898cfe6baaf7d0fa5f83cf4ff337db5961f15f93f03c64f3bbd5791bb93cfb399f73971ff82a3fe7138341213028ec7f5dec39c80283c2669b15760e362b160403068544e2810f0e8941e0573098d916f6017c0959a150d8dd4021b0d800c488c5619ffd3b0b00d0c46c362c3e7f65600fc34e6ca0901d16e8ccfefdaf668dcfb9f389c7dcff502864df6342f6eb281c12870fb2ef2c20ec67c18c0f2b3e77be3077e7043f9065915f038c39c0cf7e3dec6770e07f3c10ec423036837dec78763c10ecf94076783156a816005804703200016ee0cd7660dfecfd063e78a3070144b1af07099cbd82e1f0c60bc5320be0c703eff6fe800303443811c0cf7efcd8801b7f04f90ad43e9cf0a0b18314423cc3db6d6c79e207a438588d8f1de80305bc17b48193ddecc10760a0e47ffe1f60d07f80f777bc0b9cbd5aa40bbf88ff7ffe3f3cf3c2fffdffffc11acfdfc1e7b7f9ff1bb8aa035f3c882379fe6fa9511cac9d9cc77ff66c17e6ee1317fe79c81b74aa3f6b00d6f8ff0338c6695525be2e7c617d5a0febc38a584f8444fc7097b52901e93b1b2aebd3f2f4fd4baeaf4539b076190feb83714c35a957d6b3b5eafbf094f52c82bea709637dd85cdfbf3458afd6a8af4505b09e75d1f7367bac2d4a68fd972b6b535a623dfbd677b736501d56c4faaf0f7d5f73cafa3675ac57bb64edb0157d9d4767fd1a55d60f07584f6016f15fb4ac0f86b13ec8c5daa21258ff55c27a56697d2e64dadbaed6a70de87bda9ebeab6db21ec088bcbb4910f50214be7a47ac3e6bb47e0d2aeb8599ac575b657d504ddfd788b2b6a8a2f5ec82beb33bac0f1eb25e0c4adf859aac07b0a8c759147d2f60ed07d958afe6c8da949af4fd6b56dfd9b97ef08af56c5255b37bd6abd5b27e0d2c6b531ad2d7695c881f8eb27ed8c9fa34217d17d6b09e8d9daf0b4359ff3596e00048e4b52803d6aee4c17a5e09ac67b3768b3ab31e40a31e27f0ec7b9a23eb059c114fe003f1ec84be0f2358af26a9afd9aaf5bc24582f34b3be2d95f54142037aacad1f7ae93b015ac40b4bf59d1db3fe8b42df0b9cea36e52ad5859dac1f0ab01e00a31eff1ad177d6693d9185f5ac6bbd1b97e1d54e591f9463fd90525f534262bddaa4466f43c8fa3023d607cff435cb67fdd080f5ac98b503e8f9faf080f5ec80beb351b2361a06e20ba4ea1625c07a355bd6b3767d6783d60b5b1cf8f1c2d7bf40581f46a1ef054cf58529900fa2e97b1112b1450db076a50fd61e60f7d5a2a4589f16c9fa343fac67ebfa6a94ac1f7eb2b6981062b33dac9d37673d9ba2efc34bd61655666d00bcfa5ec055dfd54859cf8bcd7ab72ec3a3d959bb0c05f1c347d613805acfbed60faf7d5f93cafa6197beab59b29ed701eb8536ac6725f4fd6b57df0198c86bb255c47fb5b29eadd1773543d60e33d177f640a38335223e0d4f5fa7a2d4d794de585fc093f5ec0d551d86a4af0b68207e58aaafcf067d5dadcfda7fc2f4835bac6737581fdc43d6a3e2ac1f16b01e78c47ab545d63e13446c5141ebbf32f45dad94f5ac0d6bef90d5675ff475a50ed62605cbdaa65aa53a9babbeb345ebc306f4759e9cf5434ed6b369ebbb3095f504a4d6b7b1637d814c7d0faeb19e055a5b9498f52c8dbe3605cbda6517beaec6ca7a220ae20138ead1a20058df66cbfa212feb0b5cea17d8d4d7627dbe0e4462fd5787beb31fac57cb643ddbb4b62804d61605b49e8d5a9b6c8e78310a5f4f33d4f7a19ff56cbbea6988ac4d096fd3852eac5dacd00f0a12f0b65bd60b0358df468ff5ec59aab305fa9a542b6b8b62607d508bf55fb1acff82653d0bc0daa930f5f558b65e4d96f54138a0ae26ca7aa1296b538263fd1a21f1413d631ff6593f8c45e0434dd6bb41307516cc7ab64e7db7079dbec695f5ec83be2e6a203e0d9ff5697cfa9e06a8efc21b7d671b65bd1046dfd520adbab0ccfa6118eb5916d60f4d59af66b53e0d92f54201d67fc9b25ec027ebbf48589f4696b5c9e8bece8ae8fbb0cd7a36567d5683f56abcac3d0ace7a007bf4de86cb7ab708a99e46c8fa34417d6775ac7ada23ebdbdc223625e9a65615c2fa35a5ac67d9faae76c8faa127ebd9117d4f23c6da949858af86c8fa618dbe13e061fd108cf500ea9857d3646d52aeac1f6eb17ed8ca7ae10ab60f7559cf76aa5bd4d1faa1186b174f591f6cd377353f534fab633d81d7fab21cd62e86593f74643d5b2c6b879db05e48ca7ab544d6a7e959cf1ead4f135bcf0ead1f62b23e88c5fae018ebd9068d4e74617d5aa3bea644a4ef413b6067cb647d9b9ff569f4ac0f7601f5e09fb317e8647d9b10e28f305e5ce8c97a362e882d4acddaa286d60f09581b2c81f5bc18f49d1784beb309faae06cbdaa288d60f59591f24633d1bd7f7343eac6733887896697ddb2eeb875fac37a2205e0d95f561687d3da002b18b607dad8a84f5693a884d89d94d4948dfd57af59d2db3769891be0f91d617e0d4d76516be3e5c60fd179cb529fde87bf1c257f7aebe0f03583f9cd4f720a000576364bd1112f16deef49dc8cbda600aac67f1ac174a119cb5d077d684f56973acbda3569fcdb1ea0528595f00553f2d91b5cbf0ac1faaeabbdab5ef6a9cac9d0767fd1095f5693fac4fb367fdf056dfd916d6a614ddf4343d7d578b94a9458159cf22fbfe6567edb50ec4b7cdb2feeb95f5ecad67ef543d4c89b5c38e589fa66a7ddb2aeb09c87d5db8c9da6125ac57ab646d4a4aac6fb35acf06abef611536355a06e2bf3658df46ca7a026e8867c3ac4de9ddf4b65ed666f5ace785c07a9644df59057d075022af45ad592f64656d006eac5dc6b43e4d80f56c07eb8567d6ab15ea3b8bc1da63b77ed8cbfa34aff5c201c8b330fa1acdc3d785a2ac4d2989b5557db09e6deb0b90ea0bd3d83ee4d2f7af107dfff2607d014c7d1faa59bb5867fd179df5431a7dff12613df1c2d787b3d4ce3ae8fb1092b5f3e2ac6fe365fd1094b54599592fbcebfb979bf52cd1da00de58cf42e8bb5086f56da3ac1f36b2be0d96f5c12ad6b36d7d3f4222361803eb0154603e6dcffa321cd61e8b23beed94b5cb9ed60f6063eaac99b5cb60ebdb6a59cfaaf55d2d96b545b1d6b355eab304369d95eb0f19585b545b1ff4d277b64bd6870d297296ae7b6c10b1c7a65f6703a1ef6dacac4f5b647df08db5cb62ebbf2ef475aa4b7d1fd6b29e855a5f0054dfc37c14795b296b8b22b3fe18c3c6d55c597bd49cb54521ad6753656d4aeea6ab7db2fe8b95f5c3bfbe0f1d58cf9259fff5d67702d6af0f2df55dac02f1ec5adfc3801439bbc27ab63e6b832db0b65988af1e93233e8dcfda6122fa1e44d4f7e09c628bbaf50460eb8736fa3a55a5bea7ddb13ead4f5fab75f87af08bf54232eb838ac87ac7acfef055dfdb5a591fec53f635b1ac0fa6e93b6ba0d185a8ac575364fdf1cfd7794db09ec5b1ea5f18ac6d96c3fab657d636eb40bc50ccfab01e455e7667fdb052df0b7cea7b81507d677bd5f7e1036bbfb28457cb65eda2b5ef4130d61b2f7c752a4b7d4ff363bd0033e287aefa5a5498f56016886723f47dcd2a6b8791b03e022c6b5382377d4d046287a9e86b510dac0f66e9fb1056df8366fa5ea05477000dc42e6b5a5b1401ebdd26887a9a58d6b3acb5cd0a115b1508ebc12e102fa012f142146a6f7365bdda2bebd9187d17baea6b00bdfa4ea00df16b0e115bed03f1412a7d5723643d3089b5d5b410dfa6cb7a355ca6ae96cadaa2c6ac0fe6116b5523ac67e3d4673d585bd402eb879bac2710b57e28cafa6126eb0b5cad17e6b09ecdb31e001fbd4e1341bcb0cf7ab547d60e0bd1f73440ac57a365fd1081f56c22f4b59894f8027d7d6f1365fd5785beb7d1b2b60008c4a6e4a4ef413e656f23c8fab4437d67b3f543497d6f4365bd7094f5c219d60b4f14f83094b547d1591f96a8af4565ebd314591fc413d6a4e2582f6c656d4a6eac5fd3ca7ab55ba9ce72b0f62839eb87d6be13c8b3b6580862878db05e6d90f52c83460f4b626d4a3efa9e56c8fa30b8489739ade7f5a0ef6c96fa5f757d67d352bd2d97f5bc0c581fac73fc3097f56b4c59bb6c87f5613f8abcf8e7eb1164594f606a7dd08df5421ad6a359e87b9b2ceb5910b14e6cd1773553d60b63f49d45eb3bebd677210b0367bfac2f50c9fa201de3d9a7f54023d6b302face2ead0f3b427c013feb7939e8fb579cb5df58c24613453c2bc37ab40aa01e1463ed516ed636f5aaaf29b5b13e4d0febd3fe585b8c0ff1698cac074341fcd096f542156b67d3646d4a45face06e8bbda28ebddb8229e2d55dfd542595b5401eb876bd64e932336a5207d0d6057dfbfe6ac9df7663d9b27eb87abac2f20ca7a5e11fa3ad5a4be4e95a9ef6c086b536af6c37810399bc17ab655fdb4417d4da6eaabcba6d6b314fa6db1ac674f589b92b39b1294d48377005ccd93f502e4881f42b076599ef56a9dac5753653df1cf57efa0d59de766fd10cd7a364cd6b317fabee695f5052659bb4c6a7d1a1deb814aac6767a4fa9785be8bd9117bd49bb58b767d2fa0a9ef1162597bacd157af7120becd94f5acd0fa612aeb876cd6b70964bd70ccfaa1026b87a1e87b9b39d6b36deaf36ad0d7a28cd6af2165fd1097f5435ad6a7d9b1be4d95f5c417d603b0235ead95b503e4215ec8c97aa109022dea81f500fa109f46b5b62a11d6f35260bddaaebeaf91656d516cd60f6515b81087f569556b9b19faba106ded6bacd687c5479ee6c3fa6124ebd530591ff463f635a3ac570365bdda2ceb79b5f535251e9bfec5c1fa230ae259b8beaf6165edb0147d4f43b55eb8aaefc14268ad0682785e0bac0f96e97b815ad6cea2fa1eb4623ddb24eb8b288857d3a5ea6c85beabfd757adb9ff56c096b66f3cf02fbec87c0e7ce39f820dbcfc0ce40209bd96083bd1f04e6b3c57ff5f0f94121900d3e78620e0e9f81ef6e621f85390b3be7e35bc87e092fbc987078a110f81e760e33beffee9f1864075cf8b3377eecf9c13ef11c8467fff500671f1c3bf1ecdf5dfc8083b16242a0f09905303c9a3dbf59e18dc0136f7cb11ff62cf6b0bff87f00fbe08117deecc7979dc081bf0219f1f9c4fff9c0078373ecd9fbc160f57f70ecc37cf6f9f97ff7f39bfd81fffad71b78011fbb8802079e3d82e7db3d00fbffcaff4cf7560771b29f81bd1fe2c52ff9817fff035b4b4ef0fea0444410215f2226ccff45c988231911c34e141dfe96cd021c9b82a8fb9c2b43a20bc3ad5a702ee7ede732454a8cafd0507664128923379ec9a4333a2c312c712ab45f5df8db552740fd670a03b9646093b6cabb3d371a49088bcf5d4639fc6f512442dcdb13864128dfe7934394e97cb66e0fe7f4c49caf647ea0a3d827215a9d960db904bb4a98c3ff6285970d5972e634563ae3369e0e494e980042affd907c3b4c98e03a97b01bc2d503e1eab51f5ce7136e9b1e926f0757e97c7240c34659002cd8d275a77c6e33d8170fccffbb970591f3ebb9295dbd6ad4193763d73da2c578af8b66dcae58c2ed88e9b4658d2ba224bc2c58c0e6c686c1403043b06dc102360c19d896511758e8c03bc028c0126a3f900470c40f60fab21f333e8525c4f03b9de02a9dcf62dc1496507d523fb0b52806c428f3f1e1d39c2567819340d726e5fffbb21e1e5ea5bb8493f026c1adaded55c2afb2ad2dca744e3f98f830e28de31630fc469b4a17364441422b0b62a0ba6684939b5c1c00e57909369f40c8f05c6301a0851038d62294d0a8b06577a74627eb429416cceb8a1f043fcc38488e3452636107d1caa4f587d1f0678bee0b6df174a5d517b08b777737e57fdecbf8c6bcc4ff4376f8bb67bbf6378525b05f75420a413ae01c2b97e31a6ec6cffe6f335cd81d0117d78beeedcf5eeae4aa2d0c7358ba51279ebb8cc2883a3bb8f0eba1c3d27907a58290f12a5fe5db621975e3a92ba385b653aa09d84de6c16f886ab5c334865b294c7dae583e9d4d0fd56754451bb7cbaa6be25975f3dce0aaae1c1d9fbb8c46e1e232af147fe6126d9b2f5cf845b1229eb6ee899803f48dba4be81b8dbe21a6285ca5f38d51855f445fa7f3c3b3752a9d4b6a89e7d4c9744f3495ee0b570f0d480276f39cd11745ec8cc25892a0e22d9d7fde55a6cb8dbe29cabc650134da6d837180142344c09001039b03bea5b3418ce79eb0b38b9b4c670b58458ce79e606363632bfa60e35b3aef5ce6d2bd633b5140484078fc8421039efb0cf661b8c0c606812dea3eefee96cf978d050b28e75f13e2e9920d0306253c4dbab165084228931efaef2edfa81d396f6c6d1830b04970994b376b297b4d4d6109eef2a5f18284070e2b387860bbc0c6b774b6b4ec8df009d216cae0bf2230e0e3c42dd3a9d74627b82e1c27274ce3a6d1d719cbc5e9d9d9c1c9c5d9d9c1e9c1c590b3b38bd35b92908b111763e94ea976767238ea28d0cdc9c5c165c0f1c666c1c60525cce182d28d616727b77cf1a22432bc785112d30c51f784bb7c93d51645940abf709121ea9e70a12dfc469d5017da465fbc2809f7996cdf144b370a234ff99e885d14f19b62eaba219eadfbe14dc10762f9a4be325a17dabad3671b02c4f3490d11a737cd10b724b1742edda51b85616797c14104b8386a83e9f3494d518d72802a67c0ee940abb52aa0b1729a24e132aa6b1b385e30e2dfc42ca07e27d5f4c5d370433ea4ce12e5fa5fb017ea51fdc1e4e175310cbe73bc27d26db37c5fb62d976783edd784a85e3280c15cba75386fb0176e3a733049e8ae793c3f3c961f974cac8e04ea7d3240737766165f4f58083eead0e875cfc3e3bfc194d0cb72baacbb7c3924475f976c890012eee025c1c8c781a7d3dd4c1bd51eba838dce0e4e8a838e0e29624dc147071d41d7676707a767625f010e00d802af9eefe9705297385fc39978b142a48bcf090024689bb191c5038e326d3f907a674f5eef25a772f830ba5abf722af75652c5b17c67361383d3b3ba51b63ea9472983aa52d33ea7c4aa14ee974efbd3075976ff46edabbca94058e620a4b80b62e1c4fef5c271c7f84863b77f9469d706c67d754da0d21e2e347bbe26fe369a8188e40241289c568cbd6a11ac3ce65cbf6b271780359ba3cd82c40df5619418783bb062ef475a1436faac3acc38a535882de0b8da7c462f47da7d2681b7d55b04539fcf9cf9081d2d5ab3ea32adce58be2f9a6583edf1121e2d6853decbd4fb743f5195501c3c3d27db6d5f6c49b0207881a245cdc7d00f14185871420ee5aa868d102c41d8813303ea840713783030a360b75541bed74a974edecdc76ba548270b104f1bec9137a2e33fa3e3330db1717946e8c15d5d809bf4e584ac2453576c680f1a680db0ecbb6c3740a224ff93e3b3463d8d98103defbc22b966d07f7d9c62e5c981a5343c44e0e5137ef7d317d53a0c87041e9c69832dfa794eab6607bb6510e54b6288729fca660739fd4e50b656b3b48a979efd4d942a36fec4ea751b7c70be3c5d4e4d44db6ceb61947a96c2edc869846a96cd196e99c2eddb8b5e094c0166d9b31ec4cb2b145930ea6ae8b5725c116bf51b6e6b41dd1c26e0b83989b9b9b9b9b9b939393939393939393938b8b8b8b8b8b8b8b8b8b838383838383838383837b7b7b7b7b7b7b7b7b7b7383738373837383738373837383738373837383ab50a142850a152a54a850a1aeaeaeaeaeaeaeaeae8e02050a142850a0408102053a3a3a3a3a3a3a3a3abab9b9b9b9b9b9b9b9b939393939393939393939b9b8b8b8b8b8b8b8b8b838383838383838383838b8b7b7b7b7b7b7b737373737373737373737b7b70a152a54a850a142850a15eaeaeaeaeaeaeaeaeaea2850a0408102050a142850a0a3a3a3a3a3a3a3a3a39b9b9b9b9b9b9b9b9b9b939393939393939393938b8b8b8b8b8b8b8b8b8b838383838383838383837b7b7b7b7b7b7b7b7b737bab5047816e4e2e0eee0d0594c469fcc77bd989187627d4c59170a34e6614048c67ebba70e115503726f11fc2f36526d0feec60f92a5be8b4dd637b47907af26e3a6da5d3bb6a9bf74e3364a8a35e904e6398583adf182ade585a8595bf1c38cdb8954e9830e150480cfa1496e084a97772716d2efc2adba453091224e43df1d8d85c650bb3c56edeb44db2b1459d6d9cd73ed9aa6e125eb23dbb20618204090712244868c006a2c58aba39172e7a54f9f6f4a8f114e6217da3d0bc1dce26b3068d193262c078e1a2058b1529509c305162848810203e78e8c0618284b608bd203c70d080c1020509101c30c0b686542b40200d6d808000bdcecccac8c67a891173581965e63ff4854344dde7dd1bdf49a9777578318ce7b385ba78df171d30e42c7cfa11a59e5426e2c2126e9b6d1f4c7c185175f3daa81fe2f7e9621abfcf69fc3e5ddc49a5ad9d1d3cb5b36b616a7b279d6658ba533be8b4ddbbf06751a1fecc66509f1cbcb05ba3dac1f9611985f17c3a93b0bb62ca8c3a9bf11dec4aebe0de602c5d3e67883e4335651ffefcb7b3bb934e972e9f13c48f978198f1efbd4f3cd8d57bcb4028f81dd4c9564d3aa7b0843b3ab836a53376a1cdd809b76dd23000604763913864d774de901bb4a6372548bdc84c1987be292c61d9ba30198726fc2a27bcce7ca66cc3a5ffbff1033f15c6a346e32415cf5d2a936d853a0a74737271706f6e392c1b73ee5cb83488fdf9a446cb32a4faaf43071b33ea2ccbb0e8005e96e1cb7f69352ecb10c5cba0e3bf0336bb086c766ed4f9648bd0a02c03860c726c6c6ed4197de169cb8c3af9dc26938d197d9f0ef0a2ceb60910d8e0972d459b822d7e990dd8dcd81975e2b14dd9a24e37e9e655dd3c360721dedcdca837dadcdcdcdca8371ab0e938000447041d763a5ceab3d5638bc2ad34840a1042def858bbf0268e13a2b7e8542304531c08e960c7225a84531f21600f0898024a991244d38b520526c19d829469a3a2c4061ba482403f9968732b6c100aa62e2c39f54b10b6c0ae0a5c70cd0112e9caa21b0e9e9c00b2654f38c7008622106595bc41695a2c0099b0e20dde14597e3fae6031f64c6b29fa71a8e82691a107753fa60c99e4497af1f783869a0e210628b9f523019248617a08ef7cc4ca33d2c95577c2479f0fa18c408012c20712d69a9960a8e46385245673765a3cf948b38230808b468e1ea7900009441021a4071d0a743f103cdd1e3e9c34d15107e7ed11826861489b8c273dc6c6e8c26bf2465aed935354dceb81a29a668a180b551661a8c2602726ac19f356bd40aa4c481c57a4fa45a0c3a31bb79aafd28f3969891c4c3e307ccc3d7c70ae1619102b74dde34562831caa3037f616d97131d42ac89c3d2b49b32f80e016ecddb12fda721991ee3590a4e764c2971a7ab4384128f304c8999e2042a176f468fde831619e7ab528c504bd172e9033c66478a2c7d621089c13bc325157b1bb4899359245bd2389578abde120ea9034aeb10a94492d61e652a815728cd433159f844c055d88471de9c015eaa58cdd8820b28ad2963031b5522418718f0131072a8f4fb71699c5465805b2c84893649a69462937d516c0291951790e288a274d9f5d0001e11744a674a4dc26d9d08334cd29a04c7fdc1961486e602d51464caed4620227057d91f170eb12dcc60bf62246960b1eb878150a8006a58f87213839503d3495e211254f9f5d5468e4f1985165c21b07b3183c10ac5140e89fc6a4b0a6af17b92baaa47c1c2c3833d4e685f4081bab0f5f967890ae90a219fa3364d10d21afa92f592041033c7ae760787b2487788446aebc2008d83dc8fba2f746887a021a00f2d7868aa2f9824a4b2e2242a23ca216da20e3da602951a1013a859a143a422949a15f5e04f053a594158ac7c60da30e2a5841e10cc4b0f33245f0dc95114cf1db43e1a928d1b3ca943e5dea14517ae27832301484ce88612e8f567694e35d3ec80bb403d42e21c236794b60087983cbf393e7626f4bad0631dcf2d614104a9226d6891da958751d1228c51d7660c9b5513833dc2164ba83c411344b3df2a4ec708a7ae1a439e64326671bb6506090ec643963540334694ad6706ed823eacd9b14729c4542cc1e704ad069626222c90508538d2699b530495e1f4634f3dc461476d98135e7580d2334baa2a9d94b2cc918ee95311f0140ca52938015330b8f3a355aa93161b6b5a2c84150e21233421f20a7821f92e0ddcaa565d50e3075788592a15ef99973c583b2a397a93b2252f064e01db094c94f8a1183fb6a2d508d207cadbb07cd3d7bbba6728d883822564b512b37c5264b295e4ccddc0170a89b3c48f1b02c05089728ba23dd524e161a787f44c55856f9961f28788869154ab4148b3087cb41538c9293a818333a6ce0a53c63872f8417269c52aab413d202c4a9329294c1e4a4a49703c85ea77e29e926dfda5c8b2ce1b223776c13b554da1b5326121ce5aad52e287449c1abcb908e593a00e950a2a3850e354c324038d2e3ea0ed5a75079c43b597776049c82656787c15d90276606e06005df8db0715706654ec28a2c7272841a0123002a194272b011b6e1e343f628471c84d1a6c353a61c1b801f12a0f8e12607710c5448aa25c371898a630cde9b361c710ee1d88141d4082ca6a038e478a110825a2103c71d492b9998566f340a95b68aac858d1b587edc68e1c6608944c2ed06842372074aad4161e3960d425f4ab83864d562e3499b073e168a4f6cc4b0716008c4c30c5d456ab4ea940cda680b53434f8d227b23ae6e6a7035503851630b6d918a1a3448a502e46c83f688461c0e13c2f4a4c0a1c147c303211a00728df18de0303763d3a24e89c6c83d9861c612a9ba210efccc78f9386bbd03a21915f2660051dc99c6e1802563520607b60ad1792283cbae305860528992b16c345d20c0d510196e31febefca1b1c70e430c4425dc4424bac22b0695e1023195c41cc5b0b101c3232329c8622880214bea6809a14c030c3f3092b480d5a7634019182c3cd10a6027442a182fac2fea9080a3e08a21202f80bc10f182ac0f11872229d2c5a75bd4e4263b4a176afc70a4827343858b699b005732801c5d6488f324ceac168e8ba11333821aa448d0c25219552e9f4d6a2dc4b470b6902310259004598a5a9c05a31e2cae68418b21f760110b1d7ca3c60cc180b278e06385adb32e7ec00fc915835634b95b01438b024f0c2608596100841e5248047151c14a451e028fd48c7109a082888a132a52904af1e6cff9044b85638a3d097d327d5284214507278bdc9812b34c018642138a3228a228ea54b0aa345c6da16061a712bc96ca09496a5434d22f044e6c39a13c31c1c4d5c4a1321353d898a8c1c1448248256295e853024909165d4a9038352541e706891f2442903831a2af3a224d3b02463502039a115f222a890823a2594a445c085e211685b02220c45d0807ab40d402310804930ce2c51c08362a1f567dc0f361c807131f3c8cf2e0c6839e070e1e8e1d327528d301da8142873ce68e0d1224578ba6234ab2491c241c19e2b7c3d012291b686c48700db1fe5f4a30fe2f7d998f4d0ea2eef36ecf8d36b883dba6f26eea9480f774a7152ad455a05081aec25c05b90a7115e02abc5570ab50a1aeae8e421d5ddd5c9d5c5d5c1d5cdd5b9d5b5d050a75142850a0a33047418e421c05380a6f14dc2854a0aba3a34047473747274717470747f746e7465761ae6e8ec21cdddcdc9cdc5cdc1cdcdcdb9cdb5c05b93a390a72747273727272717270726f726e7215e2eae228c4d1c5cdc5c9c5c5c5c1c5bdc5b9c55580ab83a30047073707270717070707f706e70657e1adee8dc21bdddbdc9bdc5bdc1bdcdbdb9bdb5b05b73a370a6e746e736e726e716e706e6f6e6e6e59084008600a4b88c26e1cb7d209703c75d097a24d811775b6e1459d6c11cadc12e8189efa8fe36513d0b84b0a15de24b849786b7397b03bd563a3465d690b6573a5d578cf8d4e6870b609c758d9c26d5319ebbf0e36d865ba71d4592641938e6a0bc3ed94ad758102013ca54cc28dc335da3635e3563aa11abfbd49806b7bf1a2ce364f53b84592ad19b752b6e793839d1dd082813fe8e67ffc32b721ff0dfc413dffe397b90df96ff607f5fc8f5fe636e46e12dc06dc29a668e5e1470bbe516f511d678c889519b200c7f8a2a4b60da93503c58a66a7b530888c0052145c3222928b413eec74ba85668212dddb1308fd32eac56571e0c3cfec829a1c5062b14d9e440837f80c4cf8137343722a038d2fd13f970eccd191f664111aaa0b6476dd6803f2e70a88026fcaa80d667b76a5589200c42b2983ee4c0b302217b3b26bd30a3b5e2e013e3f23b1a62e78694992b4ec50d13284506b50a5174f1e6038ab53e1e046cc6729060e268036443f4bb1eb2657a5a6e8603366aa056c804c320f01073602d4a16a004684ab56cc4902ca38eb23e7c35f0b660649757ea63934d0bd194b2a98b488432b380d3ab0d832d52b472c02932e10d9e0acaaea7558b02e3972941660831b13af8fa2576e171a4fda5c28e363ec112d24ac168991b56ad4280c2f76a56054c0c7da8537715c01376ce7f223c1541c60d1a94608a63428f52733756111a4f680cc8e45b408a73e2a187949a26cd3abc412b507044c01a54c11b2c64f531094fb014cc08b520526c19c7af5d1f1064867d5174d646d5494d860631497d61a8b25b4044855ed938936b792a5b04634315fc0b0441160eac29253bda0f9321c75e287a6369bca0aecaac005971c3d73bed2cc6821835586bcb2e88683272768a09dc1f54cc06ac0da803de11c031888a246ad40144b6d8928b4b22a7983d2b4584843d18902ad1296ac3dd656bcc19b22cbef410007b490a0d6343d40038bb1675a2bd192313f0d4e051112ea8559d14d22430fea9e9d74b1e9d98277c283d890499ea417d75872dcd222545949e9d0d474083140c92d14a7d00cf5b8e1c60ac80c49a4303d8439cee81121094b0058ac1a89e519e9e4aa3391e72ad4d0864f102c4c691f421981000584ac2852f713945ed60367ac3533c140298a110ab284a65eac528849623567a7a5d32622063a98aa5c9818d3ac200ce0a2916301754211580d6cad213b43022410414410240f9e3405b9c0c567cd1805ba1f089e2e051818d8b8845d5b17049c34d15107271ae8d522b9066852f25a00a285216d329c389a55c1900b1168bc9c181ba30bafc99b37938dd619f63cf20343013945c5bd1e279a4090044088d9579b840053c458a8b2088385181a323c120b020a44067662c29a316eba3ef472a5474acb2d0548950989e3885821b3a5cc89512abe6a0820d0e1d18d5bdd01159c146650c1cf11643fe6a42572309b7c64c005f834883146cdca123b6ddcd0c4360415f823d429419c0036760e5ed0d23385843b679117335586c85c2d322056e8bac14ecfa0313fc4fc6424890d72a8c2d8e082eb0993190115153003ecb8186a15444e9c3b33485a21681507294b9a7d0104b740019a8a30318c2098b25c635fb4e53222b560a34e9658942a0d213340929e93095f68300ad0806f901a4f5dd2ca384128f30488d9185049143d098911676b8442ede8d1f6e9b2eb0c8f2e52ea860368cc53af16a590406a6924cad982437129cc5c20678cc9e024d50a6786bd1674527888750802e704af4c2ea64adc081c10698892c6ee2265d62816122468a270e886ee1c3349bc52ec0d07eda0c442818c1c50e402b134aeb10a14cf4f27352cc8e8c432c52acdcca5502be414133d4093e2880a5497de9ca9f824642ac802253f7a6076baf629c6d8910e5ca15e4a001310d59224e5041d32022288aca2b4250c10b40b7d4ed5314b61e4ac522418718f014b33372c47a2e8a1a11ba3f2f8746b5199110f35c6e836e558458500ac025964a44112a692033c09a02cdb140930a3949b6a0ba010004a0ea9615875208f89ca7340513cc94553c13987d78b545da00b2020fc62481932b6a8a876bd48e30c40ca6d920d3d482950e1a29264e961cf0bb21450a63fee8a6892dca90993e78b153db5123e58fdbdb52923e5727c4bd0b6079080282326576a2d21420b8e991a466424e24526e88b8c875b96bc38908187547e23e64d8d17ec458cac16543c0962e204238a571e01bc0a054083e2490da95e640c9848737394c1c981eaa189542b3c5c50dcb970a6760a99a7cf2e2a34f2cc60143a7315e155160b03aa4c78e36006830d283950326428f8a0ab6c8d0242ff34f6828ea153646fd426315a9bbe5ee4aea45252c4d2504dd4843b7a680e169c196ae3428c973e619013b36e1d326363f5e1cb920ed60024c2389216ba63c5d2604c0252411292cfeed40ba579824533f467c8a21b41b42c3854e6c4d9f40d25b32f59204103387ab3c6e221b629144b8d1810103c12905d6052c789830c8ce6a02469c9211ea1912b61463c31ca0738c815cf08d83dc8fba2074b2af525974ace01193152575efd010241ca118f181c49a6184841006800c85f1bca848825bc741587cc9138fb824a4b2e2242de27740ba3327f4066ac448b50ba985c14c49201274a200e060a026d90716d701433690a489140847c552a80013a859a1432e2897304cfd7a4534e528ddd89326721d27bc809181c623ab1b8d0a7c08b007eaa94b27e815884e5d114227f8602b071c3a8830a56158063f632756a2c4e320331ecbc4c0f5c61ed58211bd11f5ea8650453fcf6507051618d451e4e5ec6241120a76a43a22ba2beb63a6a10f52065ad4436a54f973a45947ec8b2c167c7223c48a86a7770188a722cd21689600314182c0baf04180a4267c43097168949a90146058c9c94654739dee5031f9ba22a42bc2ca244e40e50bb8408dbdc0d9a1344d3802224d60c8021e40d2ecf8f1048347a91f5e84b49caf6b6d46a106302171e668f002c6abd4a6b0a08254913eb44833bafca4a70f2226bc500565d87040ab1cf831961afd90f426065b0e4da289cf95a6440d471c627b72ed2b5536c236808614e921e898a22e1ce83146847d02cf5c893629606c3101e816e2dc960e68cd361518ca3d8115eafd10bc21838b1174e9a633e649a82d9f2118f0e58a3a4b185028364176b8fae05789d4451d054c20cd5004d9a9235dab220593b1b12e18d05b13da2debc4911a709a849a7da3c8ac584511a21660f3825e6807a4b71e1838f343d00674c24b90061aa61a2c6995d2c963887368dad8549f2fa30e2c20823bbbeec09624511701b51d86507d6b6c1d2041d82b608e3ce580d2334baa24906338c461a9c28e0adb12596640cf7c8deb480e1c4918206864a010048596a12ac82b342cf8f1241a4248b04e051a7462bb5254dd25cdd2c6c81f1cc8dd58a220741094b65d6da064d80d3600aae027d809c0a7e4032e98d1424b433b03b8202e4d2b26a07180a9c0815c100a70d0f1c2e5932d42b3f73ae3e5a8e57822535e820d576f4327547440a84390359c6747a70679780258ff2d489743c3005951b3ba3fc18da456629939f14e38554d3564c6edcc942b744ae05aa1184af9db682b3e6ca0b044dec06a0b9676f575452623571e541afc8a55c65228e88d552d46aba0053932f39248931426eb294e2c5b48c150f186dac3432856acd0038d44d1ea4783840680c8687a19eb53e65807089a23bcee1617218cbd0bb1357d6b2d0c0fb230a062ca397143b466ce9a1040d84590b373b7bb35e393c5e8da9e6126346c1434cab50221b4f9ed850804242d22a16610e978366183966ccfc50d3344305124d548c191d36ec3a55494c045d4e0605d16266872f8417e6db91354264b8bd90e0c8c4a458c9938384109d2bb8370b0469b2d1214d480b10a7ca440a348245190fb85910c09cc1ada1b02a151a57a8a6c3205b51989410b3e5e20e16db911595d69a5d7218c90c091c40f63a754b778cb8f10337322f1bceee9ce272e46c70ccad32f4825687334863388ef4987425400ef991b4e7a0dd14092cb284cb8e9c5117f46a4296a739507b01ec3c4490722393d2495b261b6787e85cc716cd01d200844f45192763a4f62645d15160ca4482a35cb4ea8071a36292a03b023a40a14b0a5e5d856c95803505c373f0a6c9e6f48ed0d09271607a72a3b8c7400ca6da0119760d648149c20a716d33684e8651408a37129430a0436096180907e27084ba4086268613312d6a05b051c7490a29414eb8a9619201c2919e56b1aae8015142885b1595804fa1f28877b10651ef7690e94523d75a998053b0ecec30388e53af50f5f231e104f28999013858f1e832219389a3233f137006488b544dfda9314a10a83252336d44196550e624acc82227add7a914838a3481865a1b0123002a1941a870899a6252f040aa179ab00d1f1f32476ae0cc5ad1a2ca173c5bcc8230da7478cab416c8c694da21002e3862c00f0950fc7073620b09070fbc80a8220b6d0c5448aa25335be0892ae6f7b220623644461d9eb4b88f15a94360569e709013c37bd386238ea152b0527dd1c406c371a5611035028ba9a718eb86bf596b5a2f09cc78a110825a11c3ca02129628aed24819117324ad6462cab787038f0fb08c1178e8d242a5ad226b516350138fdd0a276a96c4381332c20f074f3e20b14dc9352dd30a4a360637064b24121334a1d8c0c8810a044da8048ec81d28b5e68493389100ac0922c6c3ab00ec8d5479d8305c70b2c9c887d72949b2c6be947071c8aa4569a6ecc21547ac8e840ab479e063a1e8a45218246d7d4281ea212940932f5726583003e01405198bec0c486a201b02f13043571178c5b54c680b0e01b740a764d0461b18237708ed311e8853e582803302147e24089271b58e612a276bdcc8f6465cddd4debcf841b2429364625ba0e6448d2db4452aee6afd30438a470c2e9a04946975c86c8c0621b71a15c0c380cf4719592a40ce36688ff0ee860170b548b4a80fc96142989e1438d22d47594ef49c89ad0c905380c60f96b54054b146a60846a650cb6eac62d5d660102b6b54ce18203a3008c85c637c2338c8bd3163ca840a2c08467c18b0a853a231720f10e4597141439b2263289459225537c4811f15888eba7af9f162e76e8d8fb3d63b203a11f521c1f5bb2279a1b506a67c6e8719275802e9f9d3716a8b198a3bd3381caf545dd87394a845852c13cbe0c056213a4e5c31babe9450b51a1086b62b0c169854a148f5153a61e4f886d609b346d305025c094935c78bb4992956213362646ced294b80e762d00f1aaef80679c993f6e50f8d3d76185a40b262e82ccaaf08ce4ab88948746597274d6c9aa08c717f45c6860bc454127314208401363a65a4e4f2426003864746528c01c2d0905401d1aa54397bc40bc3dc26006ebbbc18d92699513b8c491d2d21946580a245ce85b40d6238221140e5c6121fb2d10f05a722dc7a64d50524400b587d3a0690d1c54a0d50150c48b2341af3442b809d10a8762d31a565cd1b0537980a584136d9c3633a810354d094c8ac374b02f40d885e6bdd500049967baba3c482929180a3e08aa11fac8c0c8e1c8a64a4d544ad439c324ac448f8000a0d19bb52215aec09683ac153e18402f6140b148bf050f25485ac0f118722a9da173d71063b0800487450bb454d6eb293bc3283cf2e07a080746005f8e14805e7468a4f658a76f919b0949354d626c0950c2046536196de925555863821e33c8933ab7d33068d452a3052fa9e5206383123a8410a047644c47d04a421110b2e2ba3cae5b3a98094278ef60d922f5a51d6f2c2880e682832210a8bd36c78dc630aed000ced13a70271c89e120f15cc2852332310259004598aec204578ce29ca53674a2c80d1d10c42cf094526b1200821e8c883f627941b427355539fd80cf990c90da458665ad062c83d50b4aac4ab532043606420c6376acc108ca79f37c1233f4f2a2eaca5d9e83c41183d10052123a25695013ae0cecebaf8013f24296c2122c4c1010e1a2131506756e8c010b883231714c00414472e4d172c16b172e5e8e52349d409a70940d8ceb428f0c4608210384675335e51b31a0d1903a1871412415b6c4113c1598042ca108f4019419330f5b27180048651994805996a0a40e0919a312e6778be9677dca4721d54c7e4646a05c06c0c87182386fef059a5e44a81134eca48f0c193dc52a99222299fd0f40960a60cf5d2152a04e050a9cfa9003d8bc80a10c1f5c9831a1232a83c00d42054a20a05fe9c4fb05430ae7942600f0b00b84c850448e893e993a20b4932fdb86bc3a31088269993456e4c89518a19db850683412430aed4dec421ecb68859aaba620bc61108231f020039a0b4a8079a9a9236383e99d1722949b6cb0f800ab94494a8f48a4e9002553d3cc8e4a15252fce0311640523152d40010ac012a58551aaeb53aa9d1e22a5ae76a29a80c762ac16ba7c469556d12c88a2c35774d8d8a46fa258d4e8f1b636eadeee4ca34c0540e355a73ce6a2c22a1a6460c5c134095a93265aa10ad0f23c6d8b51a01a2ca933a6b7d64a2c03d500ecf11a2d370558f3c684050c802a34306332e8818334baf1655315b66a8610a05d91e5bbc4eb5f1a000ca416cc51ba91518d658c1d50b6940128b04726968aa76a0e9dad04a10af2c5f82a4aae0ca6c949225668c20358983230be0a855ae0462779ecf4fd10cc306b53c8178632d5a95d647841916206e60005e5419da49f32476d656d2fbb46b5091c7270e63a48c90b3618c00004441965cc8d4a2d3283440699c408e20c34676529c4a887889e38854839f9d7a1b436611dca752a4000ade546c1f3bc2c0ce9d313616ad3903ea088d24435e4088a6d58bb844609dc82c85b6cefc01f2f4c4f270230c0f1f775604a9acf8c76025a22109880928baa66c00c61bfe027532dd1ab088af549b1d2e63fad8e23b246a8c0e3c811c88b0b308c71f8534342b2e472f2adc18a37420cbd08d3382dcf0b1d5165152eb4b0935bc03b91edc8964e9410032749045924ba62005042ffec0607f01c33ad86c1f186c60b3c5afeedc442210ac830d240e8743002c7b232b14f6d711280c361a7be18d01802d66341e7833360bcf1a85c2a0b1f88be562f1172b828d090b741fbb8bc22fb6c0c5be9e8b6f60b108c602c3fef7df647f50f82e065f3c7e35f06d46540306ff23f1cd1a872d04128d02586058916dec0b40d1d82d847b7efe02e61e13b6d9000000129c4082239bd3de071eb8580bf38144761826644f207f957d154fec6ea191058e09cf1ebcd989ecf0617dbcd06c6876a0583eb1ac1364225b10f89b890d3030f60381c3e200b41fc50ee0073efc01fc8b282470630f087b9891ede1d7337048f73e2ef8f14dcc01cece0ac916048f67fb0a4ef89784076fbcdabfdee059f8316101b02fb34738f00e30247f769608e0756b615f43fb31ac859d85664204062e06f6160b3ec004e18bdd1fb22ff02f33a3d8179051389178fcf3311cd8c7e72281bf29388be1088e7c18db11bc94903dfe78ef17afc0eceb598617f0afb6488231073bf0130ef7e7277ffcebc3171fe3061e7628c7d2e3935838b8f15380680ffe6bc0b9bcd79cc94f1c3925ecc1dffdff8af01f07f020abe2bfd93df58707effc8b3ad0fbc2fcaf0f3a813e9de385232ebc809f68b8bf38da8560673301e34f6923dcc7742dfad9ffb9a8ffc6fe27205b0bc9af2503611d403fdf727466084efc622cfc6202fc62c27e31c13f0caf3fccf54711e30fe3e7b90c95e732009ec904ff137ecf6322fd6098ff0957ff8948ff975dff970dd98bdb6f22d2cf85d7cfa5d0cf45cdcfc5c16f22eedf42ea194b9e7f8b837f8b315fc9f297a8f37ca5f86b11f597d0fb4b449faba43d5301f16b41f0e3a8f36731f493b03e43d9fbb1e8f94960f9b1387976f2f75f39fbad5cff2a3ffe2a9bfe2a9afe2a753f1502cf4a24fc54c6fe29b2fe29579e91b87a465229caa5e721a81f4aac1fca9f1f8aa3e721f059c8841fcade0f85c0ff84d0ffc4cdf38f35ff1318bf932663fe108afe10819eab78cf7c717e393fff92bf3fc4dcbf04d4bf44d3b31ef35fa2fc97e8f8415cff25fd2b19f583d0f42b49f22bc9fb95344842ea5702e04fb2e44fc2e64fe2fc933057fc20f29ea5959e5f3a3f88f60781e3f98df023593e4f1bfd4802fc4768fd4764fd20203c3f313d43f39e9f74bf91bedfc8a3df48a3df880b23a87e23807e23087e23707e23cadf885acebbf017b1fe4500fd45ecfc45e8fc2decfc45c0fc459cfc45a8fc1ff27e2961fe22c0b82fcf71569e99789e996afe1a787e2219fe0fc69f08fb0fa9f37bc0f41c77f70f913ebb6bfe213a9e5d04cfcb4e7fbf102ccfca0cbf901ecf494ebf071acf4911cfc906bf07b93f08883f48fe8128fa3df40f44f97c97e407f2ff47a9ff47aabfc398ff47a5e71b43fe0e0c7e1f8c7e1f899e6db8f93be4bfc7ab1a6a9e69fcf87ba8f87b94e030e1afb2ca31103dc7e8f257633cc758f06f28f5f33de24bf1fc42eddf90e5df10e2df73b25762eddf433eb3887b6681f6bc62d6f30a5a1b86bf065dcf2b3e3cabd8f3ac82ccaf27f66c404c3e51e5a766783e41f75313fc1a08fc71a33f4612dff8631119f6fc53347f8637ffd4c93fedf13cc2ec59c4dc0f63f9e121cf21863fbc026ef0ecc1cdcfe3cb7307e5cf03c47350c12f85f34bf77e29905f5ae2c790e597129056f8df44cf1aa23c6bc8f1bfd11f7a7dce40e98f2ac210f747a37f74c20f957bc680fca1487e28df0f75fe500ed00c3f34c0ff64fe4f48ff53d0f3853fff33c5b3dddfb39af1d90eca9fe747cc9f07ff1752fc792f9eebd4fc7906fe0b03fe1d9e9ee9ccfc3bd6fcce55bfd3ccefec70a7d9df5cfb9b519ee5a0cd163f13c9cffcfb2d90788e33f4335dfccc1c3fd380074eff10383fde97b3c0f2b3a8f2db99fa2d0c2da0fd78247e3cbfdf2df5ec56fcdda7ddf35feefd4b30ff32c8bf74fe76127e259b5f89e85f1e788620eb579afa95467e65f34feafa95189e211c7fe58a3f79e94fd2f99338fe24813f29f6b7b740fdad9cbf3df3b7757afced89f6c4df8ef875287a5e70a94281bf82ddaf83c87f77fdef34fd7790febb2e7f5da9ff2eefbf4b3eb79d9ee1f6fc758aee0efc453a2378fb73f07a365325479e3f879d3fc79cbfeec7b301237f5df2cfe1fc7360f87334f871ecfaeb26fc3848fd3846fd387efc38f27e1cd16735eb8fc3c3b39aa9ff0694ffc6201d3f0522ff8d03ff9d8bff86f0b7d1eb99c080df062d1b9b7e1b869ed11665f86d0c7f3a5d3fddabbf06de4f63d35f83c15f83c04fa3d5738055cf01d6fc34923f8d033f8db49f86f09f81eb9f31ebd9ccd43fe3cd3fe3ed9f01f7cb70f2cf00fb65d4fa6544fa67bc8a11ff3158fc3204c808f0c328f4c3b0f3c370f3c3e072e487a1fc61b8f86188fd2f6efd2fcafc2f9aff8b11ff8bbaff4598ff85f07721eb77c1ead9a8fc5d84fa5d34faa5d2e7620421bf0b20bf8be9efa2860b122e84bf8be0dfe26feeec6fb1f7b780ff1621fe16167eb9507f0bf66761eb994a28166ffe148f7e16677e165a9e83267e16507e16cc5f8e0f8bbd9fc5f267f1e26781e25968f7cb85f957485ac1e55fd1e35f917f157e54e848c1e14f51f7a7c0f0a6f4bed1289f3e7cde94601ed2373ae34dc97d5392f146737ba3cdf1799fc9799fed799b019a63d6e76dc6e75d26a32cc3bb4c04dc7b8cc17bec0d8d97b7589fb718973f284890203e7f10d32a55a950594d951a250a549fa74e9c3665c274c952254a7ff0cf9e3f3869d29f3f8508fd41557f50cf1f9cf507effc414ebf9828bf180fcf412c3ddf98f4dc82c4bfa1c2bf61c06f01cc731ca6e7061f9e1b94f042b6c5a8c5d3af2546961f7f15bd9f0a977f0afc4ba973d2a489abbf09af268e9a084af233e1f02ba9fe4660fd465a640062e3ffe183cecff7e8dfdbf0ef95f8f56afd7a9ef4d67e3db59f6ae1a70aff98072ce787e7fe3d3552a4a33feae98772c8abfecc183f93c0cf14e0c6f8972dfe65815f79e84fcae918fe6d8a5fc7a6ff0ece7fe7fc7370fa7354fa6d34f86b247f1a2f7e1a1ffe197648fd30b8fc3060b4f8fb5b28fa590cf95980f8550cf959347f1678fe140bfe22039e3bbcfa7550ff2d167e2dbdfe2ca5a4e4f9a10cff27967e27157e27fd33c9f23311fb8fc82852fd89b8fa87047f21f01f84c31f84e863d0df83c8df63c35f8df257a1fc551c7f35c65f5df15747fc55127f35c3cf47e58f0ffc30849f47f47f0ffcd1b41fbaf73791fc78547e97d4efaaf8db597f1be56f9bbf8e2bff8de87f63f9cf58f3cfd8f0c768fe31843f0c30bf0b167f0b537f0b463f0b14ff0a54bf8a387f163dff14503f983b3f9706ff165dff1658ff945dff94babf89df9f84ec1702e8ff11e8d712e3af4af9abee5fcdf1572ffc7a8dfee9839f05a81f2ffe67d4f8610cfa596cf87358fa9938f9b3a0f8979cf8a558f8f1eefc50e46f03c9ff23d39fb4f4e748f3b7f8fa5bd8fd46e8fe14837e27337e29971f1ef4ffa8f2fb60fb7b38fa89a4faa18e7ea6963f8c98ff8b8c3f8b991fc9df1f6ff9794cfa5fa8fdd13ebf0b0d7f16523f9547ff9251ff1249bf90ea4f25f5c7c2ff79ea6faef999297ebcb2ff86fbbf08fb5b38f8b324690af8db0fff9d935fca831fbaeac781f6af80f067e1fb93127e16d07f4b8e1f88d8cf97f75369fc31f18f7ef9a11c7e26ad9fe9e36fc1e4cf22e1c75bf28b81f24ff9f11fd1f5eb75f8794472b9fc3734fc365efd2b3afd2992fc3a46fd59f6fc54eefcd538bf0a27ff9601bf96117f965b7f15b97f48885f48973f76fed33abf8e023f8e3e31ae7f0a53ff94faffc8f0e7c5f9953efe3b5b7f0e464b0afdd245ff0b407f0b337f0b01bf9512ff1404ff9157bf9405ff90f70752ebaf36fa63263f0f12ff8be05f76793be5cf11ff52927fdede8f97e86fe1e50f13e90fa3e5efb2e3c702e7af3ef85f5ebf8be8cfd1eaa731fd5f74f85bd8faa948fa9b54fa8bbcff0f547f93c8cf82d1cfc2f9a788f353dd5f39e62f72ea4ff2fa9f4b7e2973febc3e7f9336ff1112ff5eff3372fc2e0cfd400efc2edfff7cf4a700fbefd8fc2a04fd7cb97e17027eaaf49f2af97590fd3650fd292afc4d782525fd4e86fc59a8bf11593f90407f8f023f8b473f8bbeb7a8f46731f107a1f00399f0bf613fb4d3bfa3ecbf91fc676cfab3e0fa9f14faf91afd70f21f12e4ffa1e3bf03fe3342fd4d32fd6184fc5680bf11463f90317f55c8cff7e407f3f77b797f2d703f14587f921b7f12e13fa47f3d543f2cf6efb0f42f0ffd2d40fd2cf4fc2a92fc5322fdb0ae1fbae3670af87334fa97acfd4436fd49463f93e58f04d2ffb2fa71acf8c320ff246d3f4ce0cf03fe2ea87f8a223f17593f951dff110cff14d42fe5f44309fc3bf23f45cabfc4ef57d2e74fa2e11782e4d773feedad9fc6881f86929f0aa59f060b315f7e17a7fe14707f94d2ff64d11fa5d18f57eb7f3262d72c59b160a5f1b409d3efd6a5d2efd645d2a24487080902e4878f1e3c76e8c881e3064c081f7cd4dfad0bfcbb75799b78cbf60e870d1a3260b860a102c5efc6e57733f1e0d3ef46fcdd8678eed8a953850a901f6ed8ac29a3248991223e7aece94d61f7c032c32fa038d01ec308101e2e2840601c1e602b5d9a74a5cacfd6fd06a6b0b13f5bfe7f870d1b3662c4300201e23ad6ac2183060d1a342c2242840e14aef15ae3b5c68b0b172e5c862c1f3e7cd604ad095a13a4458b162c58f4e8f91a10070a3b341d3a6cd8b061c48891162d5af4e8d103c2cc0c0408468c10211224e83f1d366cd83062c4e8673bf4cd3a7440840811224488c4881123468c1841820409e2c2850b173e7cf8f0d1a2454b870e7af4e8d1a3470f162c58b060c182070f1e3c78f050a142850a152a1d3a74bfcc8c4e9546954695c61120605922f12740804844800001cb12893f01022cf155f1aa785538493849b02e08d7655922110102a15028140a851c84421601070e6cce2d8796430bf9d962317e7b43802067f68b6f06dfcf7eb108107c7d0df764ecc9d08ba1178305b26c5b9b9b5b82046d6d6f6f08e217531753b6117c7dbdbd018108107c7d5dfab3cdf690ed6e7b8ba688a680a280a280fe6cb36d6d6f6f40e089e7893c137926f24cb8b94990301c2240f0f50504e2856019b8b9b92058e0dbfe7f587e587e587e587e60f086e07f87640705ff3974d090e182dd189891f8f51398fb09047f3109bf9882296ed290f95285879d3f6cc81f56e70f8be13f8cc71f86e70fdb6023068b137f989e3facc30f164a52a31f8cd30f2643c40f96e1070bf11f3115ca94e83f62fa8f82fe638cff18e2376a52f41b03fdc644bf51d36fac72e0375ef88d067ea38c0cc618bf510484020202d4fdc50b7f114e4e999ff8e72792f9899a02fd444513201c3050404080b338b79f78e12736a8440853895f0863c42f7c2174f10b1998802082810720020f083c2040d1a62a550664dd803ae0000a02e80270da74e626e6264617800a8c3dd4d9c588fbd9e15c1d700010c1803421b00c78f6b34a8c100942c407111f3d78e8a0e1c2870d1a315cb460f9af87cacff62db0cd96d178bfbe581543060c5a0e0b162c58f0df0515061506dc026ef1aa785538493849b8205c106361ff39f86db226628b678af88e5ca5f5345091678229c29ae8eb8b1dfec763371008e3783c3a8f04081020d0a3468f1a3d6ad0a8d2d8fbd9cc366800830a830a830a830a237e11bf98ba98ba98ba98baf8d9e391018366fbacd128140281c7230306ddc7e3f3c4f3449e891d2548384938493849b0cd114c114c114c114c112e0817840bc205e18258b2c7e3d757cedd0d1a08854060b54aa552a9effbbe6ff46787b9cf0e73ff2c83b3c5219bfb679b1de63e3b64739f65d961eeb3c3dc6719b0c521fb9f03070e1cdec43dff55d37b88eabf0acf0a95fc6bcbe7e1d86fa0cbd920f047167a1f573d5ffbbc8d6e9efd7c3c0fd17e0391de475bcf57446fe2da7fcdf0ccbef9910e9ed9017f9887b340033f12c7fb08e8f90ae5f949fe06f09cb3fcc056ef63ace7eba077d1f8ffd1bd8f1d9eaf64cf5f71fe35486f228dffcaf7fcbebf413c7fc8dfc6f710ec37b0e0394b7f20a5e737fff77a1e3af80f7c781ecaf9ddf63c671d3ff0c6bb08e4ff6bf336227af6c3f2dc83fe3c7c13a1fff5c91b2cecf91aea7d34f37c9dbe8978feeba53771f95f8d9c657ffc48b3b342e5bfe6e26cfefaf3db9b08f75f593cffc37f556f639267bf18cfc0143f52fadc7d7f0ef09cf97ea0a667b6c68f04be8b6affdf88e72f49ff5aa8e7a181ffc0f1394ffa8939dec74fcf5738cf6c899f0086b7f1efd9afd1d9a0851fa9e94d24f35f073d0f39fc07f89e83cd1f89ea6c70ee47627a1763fc7f46dec70fcf57b367219b7f4dfa06fb7abe6a7a0e5aff35036f2286ff7ae3ac90ee5fcbf036da7af643f4fc3f7fb7bd8d359efd1e9c05e6f8917ccf6cf34716cf0addfeb50bcf2cd80f76e21958e347ea3dffcbdf04de464ccf7e689e9bedefe89b48f75f61bcc190cfd7576f63fbecf7f6dcadfebce0f9706cb0d6bfb6e02cdbeb47aa787ef27faee7229d3fd78337d89faf939e3bc6df96ce0669fcc8516fa299ff5ae87d543e5fe3dec417ff75fa2e36ff3f2ccfe7fbfbeb4d5cf45f51bd8b33fe3f24ef628fff4fcd73def0b391de47e7f3b5c2333beb479678ee087fc7cf431bff81516fe2f1bf42781f233d5fc33ce70b3ffbe839c7f985079e8b9cfe5c1bde4424fff5cc732ef203419d05067f248bf7f1cbf3f5f9dc6ffee6f5dc4bfe56f5ecf736aa79f6db7bee3c99adf32323bc8992feebab3711d7ff47e05d1cfe7f71cf5f6dfe3547ef229bff2fd333b0cb8fbcf22e0af9ffde9c15eaf8d75abcc11a3c5ffddec6e7b3df8537b1d17f55f53c74f2bb49dfc71bcfd708efa282ff6fc5b3f1c09bd29f375884e7abaeb7b1ccb31f7c96d5f523533c0f45fc07883c03c57e248cf731ccf335fa269afdd7b8f731c7f355c2f390d1ef96e97948e6770bf33e1e78f69bf59c49fd451acf4243ff1a90b3c10f3fb2d3f347fc67f59cf7fcc241ef6291ff4fcedbe8f6ecf7e747ffcecfb9f903f79e8397feb501efa386e76bd833dbe8475278ee2e7fd73a1b5cf4af913d67bd1f98e95dbcf0ff31dfc7e9f3d5c33370d68ff47336e8e65ffb7ace6d3ffbe65dacfbff946fa38c673f066fa3a1673f2bcfdff4b7873718f1f9cae95d0cf1ff4d9f73819f65f2fcb5e65f6bf43e127abe52797e9ebf1f3c3fd3df19de4743cf572bcf37f5f789b7b1d7b39fa4f791c9f395c6dbc8e4d98fc61b2cc4f35f80b3c1b21fa9e85998e15fe3f03e3278f6c3f53e1a79beba7817c1febfbab34150ff5a810385aafeb52667bf0efc6b5cde60c7e76bdffbe8e7f9eae41958e6476e791fd39efd56bdc1de9efffa6d4cf5ecb7e7d9f8f53eeb73f6ebcbbf76e84d6cfbaf279e8718fe03f1f375fd5de35d24f3ff457a136dfd7f68efa1adff6af62646ff2b94f7d1fa4cf77fd45921f05f8bf03ee278beb63db34d7e24dafb68f6ec67ea6d64f5ece7e7ac91c19b52a07711c2ff17e3fd75e55f0bf45cdcf52e33f126dafd571a6fe39a673fbe37d8daf315d6bb08fdffba3cffc5ff59cf5ff3b781f7b1edd96fd7730ef30363bd8f8e9eaf5e9e1fe0ffa367a0d98f9471f66bd4bf86ea2c50c38f743e7798bffddec402ffd5c2db28e5d9efee6da4f0ecf7f5fcf5e95f3bf50cfcf023f39e9fff133af0abfdd7dabc8b4bfe3f3c6fa38767bfb2b7317ef6fbf0261efaafa5de4445ffd5fa26aaf8afd1f751d0f355ca59e18a7fadc4dbb8f7ec27e239fffdc6476f22a6ff5aeb9995fbc3d43cffccdf6b6fa3ab673f40cf5f5dfe3543cfc1263f92d673e6f51f07bd8f2a9eaf6bcfc3bcff41df3b97fd6cfcfc15e55febf35ca4f4e7b27b17dfffefcb73a7fa33f279a8e7771bf4266afaafb7de44e17f55f03e5a79be2a9f7bf8b78e03bfa8ff9a9cb3414affdad99be8e5bff67906c6f99166de474dcf5735ef2398e72bf45988e75fd37b17bffe3fb8b3c12fff5a7ece337ee08a7711c9ff57e70d86e1f90fec3ddcf12378f03e6678be0278068ef891d037d1fdaf47ce0a5dfc6b26dec646cf7e5d9e73aabf98e34d14f05fe99eb3f52fda781fabcf57126fa28dffdae3f9abfe56f15cb4f516fbf2263afaafabde606acf57576f63a8673f39ef6285ff6ff906037bbe7e7a1b4b3cfba53d177bbdcb4ebc8d749efd88bc8f969eaf669e83537ee4ade77ce2077ebd8f0e9efdaeef63fc7cfdf0dc697fe7bd8f4b9eaf339e83c81ff9e8acd0faaf3179cea2fe22dd1bccc0f355d673d1d29fcbc2bbd8e4ffd3735668e65f8b3e0301fcc8176f6291ff4ae6f92bd6bff6ea7968e6777bf336329ffd28bc8f329eaf0cde460ccf7e60ef61adff5af6ccd2fd616fde4414fff5f96c0cbecd023d0307fc481a6fa39c673f216fb004cfd75aefe386e7abd873c7f9bbd79bb8eaff23be8927fe6bdefb88eaf9aae7f9abcabff6e75968e55f6bbe893cfeeb94e7a19adf6dcefb78e2f95ae06c70cfbf763c1bacfe48576fa29cff1ae9990df083813a1b64f5af2178130dfd7a7f315fc27f4f077eb5fd6b5fde4714cf57b537d8f0f9bae93d94f55fc5ce7eadfd6b5bde4444ffd5d433f0ca8fa4f2dc97fe4cf6cc9ef8096c781b433cfb05781749fc7f7a6f62de7f6df2265af8af359e87827e3746cf4344bf9ba5e74ef277aae7218fdf6dc59b68e4bf967913affc573bcfc3b7ff40f240a1ab7fcdc99b18e8bf7e7a6e527f467b1341fcd7e5fb38ebf95ae84dbcfe7f6a6fa282fffae15d5cf1ff0179ee140d76e1f9eff83ca4fe6e2fde4406ff35c4f3b7fc8df626b6f9af8c9ecfeb6f1befe3a5e7eb99e7c77f07cf021dfd48386fe298ff2ae8b94bfc0de939e3fd40bd3771cd7f55f45c1cf5e70af1260af9af619efbc3df64dec74dcfd735ef6393e76b8db771d3b3df9ab7f1faecc7e87d84f57c15f4fc16ff653d4b7ace4b7e60a9e71ef277a9e7af21ff9a9d37b1cb7fe5f30c3cf22399bc8dd667bf3ccf5f8efeb54f6fa389673f02ef2388e7ebd9f317a57f4dd473d1ce9f0bc259a0db8f5cbec1083c5f59bd89a0feebae6736c44f80ec19d8e047ea781f213cfbe57a13a7fc5739efe1f5bf0e38f04bc0bf96e5f90d58f6fa2351bc8fecb39fa5e7bed24728cf571c67851bfeb50ecf2cf03f363a1bdcf123539d6d1d7f6b7a1339fc571d6fb012cf7f69ef6399e72b7c1668fc912e9e8584feb51f678524fe35106fe38d67bf08cf5f8afeb54e6fe38a673fb6f7f1eef93ae15948f6afbdbd8d579efd966fb000cf57eb5920a21fe9e6b9ddfed67b1f5d3d5f013d77a73f9f3de759bf11c773f1fa1e93f00c2cf123a3cf79ce2f44f02c04f2afe978831578beb67a06f2fd48236fa3a867bf396fa3a3673f2fcf39d04f74f0261ef9af66dec7e5f355ee4d3cf05f399c15c6f8d74e3ce7b89f95f33e9e3dfb9d7a13a5fcd738cf6c961f897c1f8f3c5f5fbc8f6c9eafd5b3c2fed7203cf7ab3f33781b1f3dfb7d791eb6ff8155efa291ff6fce9b78f65fe5dec5e3ff47e11978e347eebd8951fe2b9c6721967fcdf93c84f3bbe5791e86fadd78bd8f6ecfd7bf8973fff5c53370cc8fd4f236d67af63bf41c3cf4af893d3fd2df76cf0ff37781e762a83fd787b34dfd3bd4815f32fe3535678365fe35e0fb48e3f9eae07d0cf17c0df0dc2d32f0cc8fe4f2fc566d0cf3ec177d1365fc57bdb7f17df6d3f0cc8efd607dcf423bff1af51958e5474e79ce197e16d2db287df6dbf02e62f8ff9aef238ce7ab82e7bee7def367e0f3d0fd0fb47a137dfcd72ecf4348bf1ba7b3c00a3f927956d8e35fbb3b2b14f0afc5bd8f629eafefbb08e3ff2bf23e4e79beea781b213dfb817917b9fc7f89dec359ff75ec9dcd7e36c8db68e6d96ffa2676f8afedf3f1feef7a1f113cfbd13a2b64fbd7e8dec60dcf7e62cf59d77f1cf13e0279bea238db7c7f8b3a2bd4f2afed781311fcd70e6f239a673fea33b0d58fdcf3cccef991109ebf6efd6bb4ce061dfc484acf6c8e1f293c2b7cff3519cff9d56f3cf1dc63febebe87acfe6bf12cd0ef47067a132bfcd719cf3ff237d8bbb8e5ff43f49cd37e76c9fb287cf6cbf436c23dfbe5e75ff86fea7d1cf17c1df03c04f11f18f22e82f8ffe067219f7f8defb9bffc6deb79e8e577fbf126f6faff1ebc8960feeb9ff771d4f395cedbe8e7d9cfc9fb48e0d98fd5f317a27f8dd3db38e3d9afc19b08e5bfbe396b1c7b53d2f126c2ff95caf30ffd2df73cccffdaf54de4f5ff2d78fe7af2aff1791b4f3cfb1578668d3f988977b1c0ff27e259e8e65fe3f13cb4f01fe0713648e85f0b7b66effcc8b6e72ff88fe90d46f7fc277c1e86fd0620bc89b2febfb37791c8ff17e70db6e0f97aeb4d1cf5fffd5978e15fdbf0fc19ff6b3dffef6f1a6fe3dcb35ff0994dfbc350bc89ef7f8df2262ef9af6ade4434fff5d07337fa33d8db88e7d90fc9331be54712781e62f9ddf69efbd09f8bcf5f62feb544cf9ff5378c6776c54fc0c37367f91bd6fb28e7f92ae47d5cf37ce57bbedc7f51cfec813fcc7936c8eb5f7bf02e12ff3fb93771c07fa5705698e65f7bdfc747cfd72fefa394e7ebddbbd8ff5f846776d38fc4f0dc19fe06f206ab7bfe1b9e159afd6b70cf433cbf5ba0e71cfd81939ef3819f8df236529ffd40bc8d779efd8ebc8b6dfe3f4d678362fe35f63918e75f1b3eb3697ee4823751d47fedf52612f8af76cf39c50f3cbe8f3b9eaf76efe39ffd263d07bbfc6bfd1c64fb91909e73951fb8ea6d04f3ec077d1377fd7f0ace0245fdc83befe3f1d9afefb908e8cf35e16c90c38fe4745608f6af4d78173ffc7f3cdec43bffd5d2fb48e2f98af63e967abeda7917a5ff1f98e79ce7172e781705fc7f1dde4652cf7e75ce06e17ee4a567219c7f0d7e1ff53c5f959c0daef891a0dec415fff57d1383ff35c1b331ec4de9ee5dc4f2ff157a1b733dfb393a0b64fe481fef63afe7aba4e7f7c86c9c1ff9e05d6cffbf286f229fffbae97908e33f10e77dacf07c253eb30ffe30bee721a5dfadef392ffa8935de460ecf7e63efa38fe7ab89e7a29c3fd7dadbb8ebd90fd21b0cc2f335d7fbd8e5f9bae36d6cf5ecf767d1fb08eaf91ae70da6e1f92fec6d54f4eca7e55dfcf2ff317a17d1fc7f959e9bec6ff76c70d4bfa6f69c5bfd4613efe399e76bfc2612ff6b836776c68f64dfc45bff5fda336bf78b2d781733fc7fcee72194df8dc759e383372541efa28dff6fc9815f0afeb52e6fa3a4673f31ef61acff1a7636cffdec9cf731c1b35fade722ac3f178af7d0d57f3d9e055ef891cdf711d7f355d1736ff93bd6dbe8e4d9cfc6739ef203519ded671b93cf7e706fe39c67bf21cf4349bf5ba7b36c9e1f29e17978f61b88f3dce86d24f1ec8776d6d8f6a634e8f92b9a48eaffebe7a2a03f97dbd9209a7f2df8366a7af653f3360279f643f13ee67abe3a7a130bfdd751efa389e72b8167608b1f09bf8f4e9eaf369e857efeb5ea735ef103c1dec7b9e76bf03d34fe6c7f13cbfc5742cfbde8cfc7b7d1d4b3df9d7751efff33f32686faafbcdec500ff1f873731ff57036fe3dbb35f3f7fe5fad76e3d0f59fc07de3ce7443f91c6f370ef778bf13e529faf209e59097f989537d1edbfaa781fd79efd60bd8d759efd8abc8b29fe3f1fcf5f8cfe354fef638de7eb83f7b17dbebebdc1d89eafb19e7bc6df979e876dff0112ef63abe7eb9fe77ce7172a381bacfb9199de60129eafbcdec405ff15c4f3affc2de04d3cf35f0dbd8937feab8fb3c140ff1a80b3edfcbbd39ba8e6bf267a830178be827a1737fc7f796781c81f79e36c70d3bf96f63cc4f4bbd57a833d78befe3d7fe5f9d7283d77a13f139f81697ea49703858efeb522efa28affefc7f310c9efe63eb7a83f0f781f8dcf7e9edec702cf7eaddec5b1ffefc273aa37f1c17f25f1cc4a7f2480f7d1eff9bae80d56e1f98f78a0f0faaf45790f49fd61fe3f47cf4314ff812567817c7ea49ab3c6b43725e5f3fd9ac8e9bffabd8f5e9eaf79cf3fe62ef0f7f3c02f07ff9a97e7219ddfadcf73f1d39fcbc35960dc8f749f874c7e37a7a5e72ff93bec6dcc7bf6b3f0266ef8af776fa388673f01efa1aaff3a7c0f4bfdd7fc2e8ef8ffa8efe3d8b3dfa8b346b537a5e5fb58e8f95ae539dff8812e9e5fe5ef006f238f673f0ecfec971f69e01958eb47fe7913b5fcd73ccf0ff4b784f711d1f315cb737ef303afcf454f7f2e0e6f23ac673f41cfbff5b78ce760801fb9e87da4f47c25f33efe79be3e791b7b3cfb9538cbf2f99113dec637cf7e3f9e73991f38eb7908e13f20e24d2cfe570767854dfe35f7997df223d39e59eb8ffcf026cef9af929e813d7ee491e761d96fc0cb73fef1033d3db39d7e6486e796f037f5991df32311bc8d5e9efdf29e81317e64fccc5efa91169ebbae8b62febf47efa28bff6fc8bb18fdffbc3cb34b7ee480b35f1bfe3532cfc1253f72d6f341fd1de2b948e8cff576b6717431caffe7e70d26e0f98aea7d4cf57cddf3260ef9af62ce3695bf5dbd89bafebf03cf5f67feb545cfd9d45facf13e963dfb953adbb5fefce06ddcf2ecd73c50e8e95f4b72d698e04d09d0fb989ffd2a3d77ac3f377813d3fe6b85b791d2b31f99e7dcf713439d0d82fd4844efe299ff8fd2dbf8e1d9cfec5d64ff3f096fe3ddb3df84e760961f793d2ba4f1afa1781b4b3dfbd9791b233dfb8579132ffd575acfaffb4fea19b8e247f278ce997ee2a3e722ab3f9789e706f0b7f24d64f25fd79c15a6fd6b72cf79ec673fbc8f549eafedf317967f8dd0bbd8f6ffcd780626f9914ddec744cfd72c671bef6f4eef22d9ff87e1c02ff75f73f33e0679bea678176bfc7f4a9ed9087f189103bfbefeb52acfc21bffda8a3751f95f8bbc8979fe2ba667e0941f29e54d4cf05f3dbc8f959eaf65de4700cf7ea0ce06bf7ea4a13771f85f193cb3877ea4dc73867f60a577b1f8ffcdbd8b3ffe3f366f629fffcae9994df52341bc8f6ccf7eb1dec430ff15d0f390c17f80c3731ef103fb5d34f3ff4d7aeee2dfedfbd8f77c7df3dc72996df3231b3c1ff99fd2d960841f59e97d4c3e5fe19e874a7e37e673f1d49f6bc45921827f8dc2814259ffda933791fdaf07dec357ff15ec4dacf15fab6f62f5bf6e791bed9efd8a6f62f3bf2a791ff93c5f99bcc1463cff0d781fd13c5fa9ef23abe7ab9fe721a1df6dd2db48f7ec277c1f393d5fd9bc8b6bff5f89f770d57f25be8fc867bf57cf2ff2f7f119f8e447427913f7fe6b96e722a63fd785e7bff91bc1db98e6d94fef6d5cf5ecd7e77d0cf0ec677d137bfc572e6759f74712df4500ff5f85e7e1f05fd3f5fc577fb3781ff39eaf16dec65bcf7e899e81097e648eb7d1c7b39f893731d37fb5f53cf4f3bb217a17ddfc7f9bde60169eff8c6fe3f2d94fee39dffa8d8dde4753cfd73bcfdde1ef3167837a3fb2d5f397ff753de74d3fb1ef4d4cf25fd1bc8d419efd52bc8f709eaf40de47e9f375c3815f61ffda95b3eca31f59f73e129ffd38bd89a7fe3fe0fbb8e2f9caf6fc267f933d0325fc48e5d9a0a37fadecb947fc3de7d9d0dbc8e8d98fcb59a0f547e6791f87cf7e9b9e81217ee4f34d24f15f9befe38267bf5bcf6cfe8f8bdec706cf7e7e6f63dfb3df9b3758daf375d59b98e8bf9e3a1b9cfe4856efe1a8fffae70ef0b7f319b8e6478e791e92f80f1c791795ffdf943711c27f2d7116987fe48af750d77f25f0ccc6fa9124dec707cf7e7fefe291ff8fce9bf8eaff037bce397ea08c77b1caff27e84d8cf45f5dbd8dd0673f0c6fa2a0ff0aea7d94f57c25745638e55f633e03bffc482c6783c51f79e8f911fe777a1fa1cf570cef2283ffafc5db38e9d96fcc59e1f35f8bf1dc667f379ffbd19f013c7fcfdf10dec743cfd72b6781a57e249eb771d1b3df967751c7ff07e55998e25f23f1fc12ff5b9dfdc2f4af917a13effe6b90e7a1a2dfedd2f390cfef56e84db4f45f679ded5d7f6e7b1b133dfb65791381ff15c159a38037a5e4f3ad7fa3781bdd67bfb9f711eef99adf47bbe76bf16d14f3ecf73e7fa5f9d718bd8d4f9efd6e3c67e4cf3679fe4af5afad7a1e12f9dd74bc8963ffd5ed1918ff48226f30b8e7bffc3e2a78f6b3f5365e78f63bbe894afe6b9a77b1c1ffe7e27d0cf47c8df236d63dfb0d9ffbc9dfacceb2567ea4da7b78ebbf9ebd8b28febf1e6fa3a7673f37cf7ff09fd3f3d0d1efa6e9acb0c2bfa6e179a8e13f407d1b3b3cfb91bd8d649efd783cb30bfe30eadbd8e2d9efc0f370ee3f107d17e1ff4fcc33bbe547ae3d7783bfe1e7dce1672d3d0377fcc81f6f628aff0a7dce217ee0dfc537ff1fa76736d38fbcf0263af8af239ed9143f010e6f629dff4ae93947fa8938dec5e4ff27e55dbcf2ff113a2b7cf0afd9bd8b7bffdf9903bf78fc6b71dec5eaff97e6acb0d4bfb6e43d3cfe6cfd366a78f60b3b2b84f2afe13d0f27fc0774bc8f2c9eaf069e8738fe03a4de435cff35c0f350caef367d6637fc048acf5ff437dd9be8e2bfc2ef639ce76b903791c57f95be8984feaba8b731cfb35f92f7f17dbe6a781e02ffb5bfe70ef4e7afe767f97bc059b6ec07fb741668e747a2792e1afa73c13dffd2df18de6062cf57516f3013cf7f049e8542feb5f6f95dfe4e7b666ffcc8e0fb58e2f99af69c973f30c7f3d0d2eff6e96db4f2eca77c17a7ffdf98f7b0d77f451ef805fdd7e09c151ef9d794cffde2ef4acf40f8470e791fb33c5ff1dec736cfd71e6f62aaffefeb6dfcf4ec07e72cb0d08f6cf33e923dfb917a834d78bef67ace7f7ea1a56776d18fa47b83159faf9d9e834f7e64ad7771c1ffc7e24d44f5ff099f81643f32c69bf8f55f17bc8970feeba3b35f83fe354d6fa39d673f23ef23a4e72b98e75ce71726380b1cfec816efe3a0e7eb9437b1d4ff97cf0685fc485867bf94fc6b7a9e1febef176f30b7e7bf9f6d1f7f8f7a83913d5f49bd89b3febf00cff9ec6791bc8d9d9efdda9c05aafa917a9e1bd49f05bc8940feeb9737b1efbfe67a1b413dfbc579ce6f3f1be77948e33f20eaf9d233f0c78f4472f6abffb529efa39de7ab9137d8d9f3f5d4db48e8d98fca1becc3f39fd9f310ccef06e4f919fe9f7a1602fad77a3c0f09fc06243d0b05fd6b3e9effe4efb2e77ce607d27a838178fe3b7b835178fefb7a13fdfcd74eef2cf6b31e9e8726fe03489ed9be1fa9e1b988eb2de6e60d36f7fc173c1b14fb9189de470acfb9fe3fb6e77ce507b27a139ffc5737efa3dcf315f82e8afd7f16de452dffdfa13771c6afe977c37516f8e747ae390bccf323d3bc8f4f9eaf379ed9323f32c173e7bf71bc897afe6ba63758d9f3b5d4bb28e3ff3bf23efe78be9e781b053dfb49391bbcf12349bd8961ff55c2d9c6f567086f22a0ffdaf79ced7ed6d1d92f32ff9aa2e7fed3c42dff55cfbbf8f6ffdd781b733cfb497817d7fc7f98dec5b4ff0fc433d1c6bb68e0ff3b71a070d7bf26e5f965fe469e65a7fcc802cf6deacf696f63ac67bf416fa3dcb31ff0f931fed37aee497f167b1e72faddfc9e5fe3bfad7711efffb3f25c44f4e78a7b66f97e24d99b08f65f233cf785bf7f3cf79c3ffff9d2bfd9b3c1093fd2d233fbe247f6fbb8ebf90ae99d01fcec863751c27f45f1267ef8afcab3415cff1a83e7dce607fa3db3707f5899e7a195df4deff989fe9e7b7ea1bfdd9ef3a09ff8f60613f1fc27e06dd4f3eca7e47decf17c2df12eb6f8ff843c7fb5fad75a3d0f65fc07e43ce7e39fe3dec617cf7e09dec51dff9f96e7b3fdaff4dcc7bf93cf5f53fe353f6783d01fa9ea5948e75f8bcf022ffdc8396fa2dfff57e039a8eb5f73f00c94fe48216fe3a167bf2b6f63f5d98fc473f0ca8ff43b0ba4fb91786f639567bfe4fb78e1f97a7c6645fd480eef63a7e76b9bf731c9f335c69bc8f75fb53cb3f18f143bf08bc0bfa6e540a1ad7f0dca9b68e0bf6e7813fffc574f6f6290ff0ae66d44f5eca7e76ddcf3ecb7e44dacf5ff0d78173dfc7fd237188ae73fb56721fcafd1781745fc7ff1815fc67fcdcabb78e0ff43f19c63fdc61befe390e7eb8ae7dce717163a2b8cff351b6fe39267bf19cff9d24f74f42e06ff3fb7b7f1c7b3df89e7ecf71b1d3d7f35f9d7f6bc8da39efde8bc8dd3673f0fcfdde7cfc1b341113fd2d3d960ff4842cfec991fa9e06d3cf2ecf7e24d8cf35f21bd8d459efd5abc8d679efde277f1c6ffc7e43958e547e27a1301fcd7b66760901f99e4ec17ad7f6dd6bb98e6ffb3f4268ef8afccf711c3f315ec0d167cbe627ace4d7ea0a97731ecffb37b83c93dff01df464bcf7e66de4614cf7e6a6fa38d673f086fa2a7fffaf70c1cf223953c0f85fc6e399e87767eb73fcfc14cff1adafb18f6ec17ea4d9cf05f533c67083febe66cbf7ff7bd87a7fe2bf02cb0d38facf326cafd57176fe3a067bf29cfc0583fb2cf7bf8f75fd19ed9ba3f8ccffbe8e1f95af6263effab93379887e7bfb2b7b1c9b35f8df771ccf355fa3e367abe7679ee077f4fdfc4b7ffbae23998f6231bbd8db49efd0cbd8b14febfe473aefbd946efe2dcffa7e3393ffa8937ce7eedfad770bd8f6fcfd77e17f3febf2def23ddf355f8fc13ff5dbd8d809efda0bc89f17fb5f23e6a79be320ffc12f1af997917f9fe3f34cff9c90f3c7556e8e15ff3f03c64fe075c3db3a07ee486f7f1ebd94fd3db18e5d92fc77310ef475adfc4b2ff0af73e8a7abece792e6afa7365780f5bfd57e3d9e29e3f57db73d37551c3ffb7e32c5be44706382bc4fbd75ebc8f749eaf44de47b5673f57ef63f4f99ae16cf0ee47963a5028e95f33723658e75f2bbe8f799eaf49de4457ffdff1d9487c9ff979ce6c3ffbe4acf0eb5f6b7b1f6b3d5f0fbd8fcfe7eb8567b6057beb479e382b1cf3af41df461ccf7e6defe3def355c473b0c08f74f4fc65e95f1bf526eaf8af42de6066cf5753cf6c8c1f99cf36fc37a837f1d37fd5f52e3af8ff5e3c030bfc481bcf4331bfdb95b731faec97e199ddf1238defa38ee7abdb5961887fedc3f349fd3de27938fddd5a9c0dc6f891a2ce068bfcc8586fa287ff9a7c1f793c5f393c1bc1de94729cfddaf3af557a66f57ee4d8fbd8cf7e949ebf7cfc6b749e591d3ff2eb6dacf0ec477cce1f7ef6d27b68eabfb2cf6ca01ff9f62612f9af63de4436ff75d1db78e9d9efccbbe8fe7f559e7bd59fd7ce02e7fcc833efaf59ff1aace7af57ff9aabf731eef9ca9e0d22f991b2de44587f901fd8f7cc467f24d83b87fd2c87e722adb75897e74367856dff9a85e7a2ab3fd7897711c7ffd7e45d34fbff34bc8d469efd5cbc8f6e9eaf3ede4535ffdfa5f7f1c5f335c1739ef0b370ce0651fcc84fefa392e7ab8c67a19c7f6dfa2efeff83f0269af8af3bdec418ff95fa06dbf0fc27f69cd7e4c37f03df452fffdfa267e0f34702791bf59efd42bc8b4ffe3f3e6f628eff0ae4b908e7cfa5f6366a79f6633e03b3fcc82acf5f86feb54def228bff2fc8fb28f6ec27ea996df523453c0f0dfd6e949e3fe5efb3b791d3b31f9be7aef4e7b13798ddf35ff15d24f0ff857806cef891d4e7a7fa3bc5bbc8e3ff0bf3362279f683f15c84f4e7aa7b1e6af9ddaaef628effcfc973a9e71b67bbc6df98ce067ffcc857cff9c02f15ff1a9a3791d5ff577c13b3fc573ccf45467faeb96760da8fac7116f8e947da797e25f60bc3bf36e6f9ebcdbff6e84da4f05f633cb30dfe30bde7e1da7f80f82e96f8fff6dec750cf57396783b2fe3505077ebdfd6b609e3bd39fcbde475acf5743cf17f5b7886746cf43e1bf96eb6da4f1ece7e07dc4f27c759f83a6fe35b667e1827fadee2c30d78f14f47c3d5648e45f4bbec11c3c5fafefe283ff0fc6fbf8f77c6df4368278f63b7b0ecef9d788efa2f0ff7b7b2efaf97349781b033dfb457917f3ffd7f63ece79be0e79135bfd7fc6b32c921f29e04da4f5ff097817c7fc7f90ce065ffd6b099eb3809f4d725618f7af61781f6d3c5f213cb7d8dfcbe72f24ff5a9eb35f7efe354bcfc0e88f0cf29c5bfc4000cf46b637253f6f63a167bf2acf45547f2e12ef61aeff9af66c64df667f9e59b63fecc6f310d0efa6e819d8e64792791feb9eafc3e72185ffc0fb26fef8af5edec4e97fadf206e3f0fc37f60c6cf2239fbcc1483cffa13dbfd1df149e8baafe5c25de4509ff1f8d03bfcafe352c6fa38a67bfb5e7a180df00a4f7d17dbece3d3fcedf09ce0279fdc8416fe394673f1dcfec889f8085f711edd94fd5737ef103d1de4717cf570467f3829f9df21c9e06a894e8dd6a4296c39442c82003061999a199090001d31340e09854220d07b3244e6154b4091400065d9c64f1ac064284903106000000000400004000000001ad10f607323d1ff3e06cd495ab1c7493d74b82073fae0c073e3a2a48619b457377b01d5ed0a5f9eef2704eb48863ca81cfe1d5251fbe8371797dfb5f22b399623324310b11eda36616c0d7c1205deab1722899cf10281c09978c1f52c5a5b2b807bc31efb2d2dbf412cf59fd6a8764b92a43b143db447033df223522b0e1cd50ebd3f222dbc3da38f2ae4e663878fccd5dc4824364c13bf1d022943b23aa43b1e07ac6f394628595e0f65e0dfeffb11dc7a99b64c5bdbb8bd4eda0ae2dd79de58d88d5d1542757dc77f49ea82fc1d3ea5d32697afacf530fcc4c6e59a278e23255c800f690b1b09d3b0517c6c7ca1b3d34b8196b0b47d35a4f4619e82cc94cd6f7e399f0f2d00b6b1054f532c2a8fcb2bd5b15c337a7ece2c36558b9c4ff6a50d87e298a13b208ee529d58dc4ac3a42606fc4ac4961b49e9f068f0c3453aae4f948f15fd9e4d91bc37ad48dd6941412ad32d57593a0da9da092dccdddfece587a5089e59e09afe589702247c729438170181bafec66536339ce39e7d199d6fa61ceea4e8debdf7bbf49044b72088f6d0420bb93a19bf22911ec76ea30b7c89bd6882a082b2fb57715144e593526e93c506f0a2509f498bf609164e1762dec085139f45e799a8c421ed41840cc6fe37b17b3386baf8be04f265c8a228f327a80427c08744d005ff57c4bb5ca169bcf0fa2027bf44a3f1c340d25ae0a01d7e188813bcba9f47c5492bf9e8e8e54cb86d089862da5a735ac1c203270463b1613b183e2ca4470eb277145e73f467e18b8681cf33567dbcff309111017c45cc0da5e23332e3508c3b105d2bb7aea832db1471fd47d60d321d8264be5efdf13c31d648b2efb8c77fba2b9ddbc1ecd4c002f6327c8364555ce106dedf45aee10b720d2a3ff47e7370944cba54529a868f7022961ecbd45e0cb63190967d3b81d9b707b206a30a301fa067c14da122a6bb9cac0a7c06e030bb402fb330e53328bdc38feaa0bd39a7509e20f0be6c51e18792a0b42476fe62fccbabb6784faed9e5349a86994233e03831cd37be4f18720992d073234d2f23fe83dcd4b5241c021ccf86e4c8817642dcabc1d6b5a05c4653bc806e320969f620e7f1d3ab617d20359ac4636d8e25fa8bb75c58cec8dc3513954397e020c17772682c698ba4d3069f50ca78b98790f1bb3f2c1b9a3224b0635183b9067136bfcbaf777d2bf3a44448d8a1edbc193ee0a8574025ed8098904d5e9e0f34a8dfe0161cdf8beaafd7fb984fb8de96be6d2d22ce48db8f82d4e9a8740ff93ffc18ccae489b42e328f67c97848fc842d797c2efebee9ce57ba104e6c20359ca83fa6f726a8000238906111ac54fc4fc19a0b413285e6145e641f19c51df1c00e7c7236da11b1508f9e1813d15881acf8f5d4352d683eda39a08fa51fb1701177c8b279cc92dc0ecfacb5ecd914f3acf6d4e7c2c3d49f94620601c72e0bf31dccb01ca832374ab0af901627834d0f23ca6afdd8758e2bdd5cc449bf51304f64de11980f84b5542887c6d7510f02dcf4945e7341f95ffdcede140d81afc1e0b2db01c9dfa5c2ec94bf8bf28ac982b1b16ff2458241f8a5905bf043e73e059072c77eaaff9cdf0e1133fe6c896455799dbbd4e5ade0ad4135aa32b226f06f0eafb483d061d71034aeae58243f8bebe1bb91b9fad000e3c106f0769928cc80e1f73e8774d070d634f8076c9770a38e96c86dcc676ed94aa16db3c57372b80fba80fce9f3e745b89417541684907c433d633757551aaebdf631277fb3e1d39baead97ef898b71a767b288fc22ea763ff65cd427409518c02ede254c27da870bb646e2c78df1443cc5ffb9731f9f996adf4510c1d88fa0bb5fa2cae29ea5dd5b5317e61006d554453fa477c28120573526afed3c5c4a5f14dde1d5f2be230bd807e4138bbdfb1d2fbf6cf37c243b211dc99a0c12d85a615deddf424b2d2e8c74f4f543c202fd5400fcbfc9bcc058963694c4ea95b9e58ade7f90184ffc18ce883b3d007ede04c5a086d39685596a29a1c151e204c81703fd518f8c9d3e1e79db0a5436a6699ce507c2fa0c4f67d189ba29bfb7fecbeffe8fbafe8a07204cd2b4271c17d520a5bd4660240df0ab834532bb0084daf382fb92956cf4791741e43281e398ebae27843ef9627381e1abb38e5b7ebaef0507a96fba52a43fd0e0a286179df4b24e66ef0c10f2436be063f88703fd5177a0ecb32314600a8ec4e9b86c704c4f701edb9c381e414d43971f7186e5951fa56b2a5bfdad306083b2c4d2ddc70bd6361190f2c4f340c48d12962fb2eb4fca0969c08b4b5888ef52ddf4a74118fa511263ff3ac9b4ff1ea746f5ff6699bdeed71d855875332a1c7af42e46753ae2bff1aca7b5417c278ab477f2a509c1e6ae4a0ef0d16c4afdc26bdde5cb41e9a31c48279c9552f5ccab1b01925171cefb3cbc0ac23f7eeddb90d0bd2550f03c22a7986daf7198ccba1bb365cd57e79ecefe165bd376ef4b1f4950757261cf27186bb6d8459f695e0fbbc31fd123cf29f7f1ef223fb09433ca7c267fdb08c6c5bd3207f82c2022296e07fd9e18c3ecd8c9502fad34c9c3d311a1f2612abaa86a2447d185222bf6f5b478b06349415bdad89bb720f895fcfd6c3c536bfe1d1eb31dc89e6e67cbddec435f1e7f2567cbc3936bdc40ff4a6e006f343e68f7e263cf6fdd2f85f9c00edf5b8cc2f9991999d10324d76f3e0986e6e4df8d1cf5e317de15f897931b19723fff77cafdc47715c270decff3e6798d0163ff5160ceb7fe4c6fb7971e560101b00799a71e95affa62ff13e53c8a7514ed7fb13af6b38121791fe36459ec2cd7f21d79d4866cde041f7037cdc9e647e56d3783b0c1ea189d1e9a5618176bb748de6a27eda95bd9627d717658d760841a6bff2459d7533c31338af9cb24653b943e54c551c238d99e31de3d85d0876abe37273f0b8fc51d4eeb4a2c01aca8d51f14847fe5c75d22f16f24a04da06fb70205a971aed11e33006fede74e1441976e4b41a24966d4043efdf9b1f4c4dd4bd96e0afae53f49c47a59e2f47ee5feea3a7644372ef23930f26278b2cf777d8e35f6acfef0d1a481e8cd406191a91ebb81849343422c8130b95a3307e27e824cfb61ab6aea4db35525f389096bcc35ab172a68e4a616cc7a87fee5a4a429806923b56ea58294f07ca6671d1ab8a962d4bf64ec53a9edbedb8ccce4dbceb9175b06d2c3b8792cefbf765ee74bdb3ebc55089a4c3280f396aaff913f8986fb69e8c71b4d8adb8da1fdffbc603ce796a01d69f80db80ddf45bd62712846b5c145bc56b8503f86ccd7e3b9df24206dddc35f65ea98af6aeeacca573937c9bcd697cc8ebd4c9e98d479fbd71306e2f40be980bcab0dd002a5b1b4fa9c70803ca1353818cdb0da0a255a3147b8c20a07c31159441bb0154b63492520f1105c81f630119b51b40654b6329f41041c0f98b695f80c93f0beb95298e4f3378097c6751e377e9f6b68f37a578cb2b639c22fac8594e649cd54e88966941e063407a9f6bef0cd94081091bd1f592b562c8881c0d3c982a9e3d9cb0115d6f17d9834360fe96d53fc2e6d5c47f7232a9419a40916da52d916a8494112c228a86cf256bc7940d5ada9259b15423a64cf0f25675dff414117792b762e48297b56456db9cf8bd03b948db3f4e66350c52e89957b6cd59a3512a26195e2f293ab6658ddbea3883f165643beaa7f7f4442e62ebd613b707188567e059100cdb00dd6cdcaac8fa2a28985c1bd108cbac50b99c61f03a260cd786e849c55ac73c334f7a17d5162fed704df7e20eb6caa49bd1741c86137d55adaddb63e30d67f61a2d758b74c87e999f3635d9456905e23fce4e887061246cc6eb07e3e6e63269fa31ebabb22f1dcaddb65d2866d9c323b39e23413248c23e11bda4fa58741fe31fe4361c6487a067c519e40b22ded578252ecb936d36c8a7bbe5a4c4b8cea73ab7ca1bb373413dccb99d6861460a6846fe976a65a7d6f6deb1099b76aba6c06af8e56a9be1743fa07602f092bebfc99aba713c9e75792a34fe7833d68bb1599089bcc29a39a682a5e43a7cec12e887e401487d3dad9b65cdee286f2a535a55515c2412b326deede35e96c4074e6d1efe3f02a6c13d42736968693345dbfc60924ea4109a39bcdd57ba6379cd8d092f554eda541ecbe1cdfb4c77ce8aacbd342c61a9b29600ff03bc36f0741e2067cd8c4c95005919b88cafaf0b9221d2f0e6b3a1d7cc1f9899b7d7930e8f7e37fe53b1e5aaf4796a2e4fbc140cf7ae9b515b3f08a70c5f3b87be7318bd5d4b5b4dc9759064ea3076ff09cf0c5974b67a5919025aec5a9b1cc8c8705a28cafa5abfd327ec282ffdf6b8753417a73e9036367c85d71099207474316fcddc7aa66ca53bcee837ccb63999f81879e6fca1b77ee613efb20d5cb00fadd208f8eddae5ad73d1ed2db7616f1cd45cb981c73d6b5a8e1f3300e754bee56f380617f4d1c4c1e5cf51fbfcadc66ae9ecc08e90b2c784d8f52ff6f06a370fa6befbe977d831eccbd565e63b2cdaac5e93427371b016b6912bc0fbe2274a620e96504b36d9526cce62e691f95ad26129e551a2bace2c731c8b01e58c2565ea10395712ad8d78146dc02cb9108785b9b3f25fd26d440e599a0bf545d3843f4e33792e7f358071f5de1a57daa534cbddf64992f7ae0a2bd478c23bab9d8c0072616797394cf5e76f1bf9580ab206affdd038ddd5b938b05fb4f6c699e1faf19125c6c399f53d03cd52b1500d8c73beee3742bd6d6c98c5544b0cb6c72ddf672c1c0d5df59915b16676b6d472326d5dbd12d62d8d8fa098567d69db7d9536c65b01b2f019a74e69847bdd4037b08b8eb199db6b9ead3869ab6afba662f5a6de7255a06094ed2db97d0c68953a6d79ef5cfad759a3d8bbccac5ce0f62cb32b77d536fb1b4bd6ef59fd2c49c25463af55d2a6713667b7261f2427550399b92ae0371fa0356309dd9898349f6b6c5c94a5ccb9ebb4af3aac8ea1e703e2dc0f7d2dccd7a592f0211c073f444484b3d8797f86759a865c5b6d788276f1da74d433f3b55d6bdcc6006c842504fae72f20447b1ceab8beef75b3c6fd871898269f4d8d61e7f65ebf7aef7bc5beca34bd4a9f59b5f7b14091d20d275da8ffe4b24da2fb70a31bc5cc5c3efefb4e93abde7635157b731e69a9dbf4fc344af081ddf7ff31ffb492318196ae078850fa92e9b5b22ca5befd87716577bd33de19adfa27d840e4de7fba2722c9ff764f557b67e562e925960af5e634c9b61b8766fe830171792f3274f4d9ffd523b7c26ca75ed171f3e77d1930a71a0bba71de748be9c3ddd0d59b8af53ebd01bf7e5cfaaf0febc17e641e76a3788161f5b2ea479f09edacd893b43ad73f1d5f98f54ea078ba373a7ab76f6c28614effd63966d81bccd09e1b1570f4ed3f46835d6fb52589f63f8c8918eb08257c1d5cb138e96bf254e01debeab22d1b07437a097b79092afbe9360b3ef61d546b04358fac103cf23236d87a7b20b1c3f9e21f7f5d12caa0be5471ca75331bd90d5e7c68f4ad0d3786cccf6a07f154dbfaa7bd3f55e39c5b41b72e047dd54d6f66d57c480032eb1310d08dfc21b7145adf39cba5e083b9ff88f55bfc6ee4f3777ab5086b537584b245b5d659f51890789010734399de7749731383583d15641503200346675f3bafade5f458682de7101bc28844a35b188ebbf2f5173439f2ac323c1bbcdc5ef8dc7e9de64d7e70392e43e0cb615791b8d58b64b54e5e94e6c0eeb92a31780c31893d764f9055efef1ed50425be30e7f2eea7121a5a8457c09fac4ac5e0c7f0265d9168a70fb029ecec45dd0db47c9f1eb00f2fbe7582dd549c58f6dcf0eec874e6b87dae5d890aef5af389b7309f9963e15c0023d19a273a3722de67d420aaef13eb29ec05040a5c2a2b92e241eab7e79b9a7b36e54676c6178a27ffd9c1c12477711aba2f63373a488c1ace992e0e9bb3de3a93a43787d898083744e573de45e2078a0caa0e1cbad7e75fefabab33379b015b6c53879a76e0eff1692b2f5550ef702d6cec11a05f8b4bb6eaddc5e388ff6ccb18ce91cb1519c4ba2df7f11f16e44fa9b3325da023fd323fe5d2acf1da11461d22d6628c5249b7cdf5f5bcf51b2ba9fdc23ced73d82135bec1638b7be9dbdd7c2cf8f8e625570fba8bb46c6de003c96c1c317c30bfb4d76295f70447a0ea8daef198557819077908e8e7136fc31ae39f7d956a7796432ce74560516f7baa01af16ed50fd72fc714f2620ed4718bd896ccf0a4c4b2bcc6c09ffce84c45c3413e2e92d4e2407b8e0125d0e5f8ed8edea222d37cf9dab3bac5f1a65f1a9fde18ea2970370fd9b4710d93765701515f3e1ead431c329f8f44a58405615406d4984bdb1748fe3ea630eac566c9a20c3cf255bba7d27a59d537fda576dfd4f68a96a28cd792b3a0dac41625c513925461fd785583ce61849ba4dc3f651e57ed3f6a0be9707e3dcb7a1a3815ab396947cd6d468252911ff42a3e121e0aab922ad92356f55bcb8f3957adb12d37d1774dad7202e9c5bfc49818accd686fa1839310d691b4f77a0d6e2c24784f50b59625fa8523ead03176306c64c1f46b449f58f5cf80d970ed6f792acbb6ef6367565ed9a639388fcf5d5dfa51116da0aecaeb2c13957635e86d949801eec2bbee93386e7bfe3880e1b85159375ef980fda075ab0d2074aba8521fc5ef330e0905746ca4b12009304f89e34f38beed86248c95b1934d9d112038d4f5dc19993f6b2b70343a1a5c3e0de8e8c5f1f6a1bdd80399db9ba330b3542386e1afb904e71cb996b0b358bd8deea2e1ddc26281d1c055972a3f27de5704274b60d560110bf5e09cfc6fdd2ef0484e7ebf821da883058e5dc75605b25073d9c47779c54e8b028d7e2fdd7dd9203c19b1fa63e02d97afa429d7ba9952b65ff3c2963dc8a60bd0d746e92baedae897a02dc6db5ce4b3e6ce387304de16f8fed5be6fef4762a20b7aeeac5d7e0f5f3c36370a262fe05585078b51a6b7039d40c10b402800f60abd1e55f636b547f55af08c7aaab587e6c987f9ff778725d287d906c6adb6f52041b078c6525bb9c672fde7cae6a390b1cfcaaebf08a1d17aef461deb3edd48995efea107264dd20117b9dfcb6959ca5e3126e4e89265eda74dc5793515e7135d86d895ecb3f042aadabbb5fdd34f8c74d7f3786ae7f60b8d6fe377b031f5bc8a0cfc5e73612c3ffdffe2713bd23fd0b4c6db218c8923c91d6e612c5dee4d7a55dccda3b5e6e32431e1b304aa44070bcb0fd8098ae7c5c1591df994c5c4c9e754ce6b59cbbd97a2b056e956162cc7020003a2f579cbc5cff2c728d10a9b3595b717498b7211da83d942ddcbe6035123193ced7a7df86510e435e88780ee16158e5cd2345c3d44afacf1b0551f510098f3c3a0b921dd1569bc13cd54e8f7853d3bcae78f62ba428bb7aaf153328926ee77fac5b8a3672136fab27d03e51e485f5230a0362cfa23c31f9be89b6b73d2a15504f00cae99bcc1b122670d1839133ed478d1dc26955b8c0513e72697996eb8be351aa59d8c92cd983b87733cebeb53decc61013b822db066e71bb7f97f800be25fc527ab159dd2af7a0b35d0f4968728ea0d538636b2be9c416e7b5e3b8e03a146afa0586f2a8670ff8caec6336c04550eeec3885b738d400dfc1d9824a0f67a6ce47c13f19b8cf6cb0918e3117cb35729e40e26cb2e373b3cbdb6363006fa47000b36702fbb98cfe9f459e20becd8bba93a1a9f4fdef96a7c88c4fb9e423e1f1abdd03bb0ce3f971bbf341e55501b9a6bfa7bd0b76c8b3f8c9c931b82025c4befe55494fcb775b4bb41ef0bf6a94c35d30953d6e7e4d007cec5c35acf54997226ec6c086f96d956225ed0c129a802f98cebf36a0dbc3c71c1d62657d9d7a51f3cfaeb7fea1c4cec7861011d35fb2a7badb4e2393680fe2f1532e3a59b526235ddd2a6ce5da2cb3e30cce7ae7b4de643356efd0eb23ec6ef07ff426f610af88e4e85f6a0bfc20fffb8c5be35a3d864e59d7ceaaf235ee800fb20082d7f83f6b51d84107b9c74a5a9d78e83c27864ea1b5faaac83686c2ff84f03ff5afd736f5b56207be6b37a29b41f6c7e167cbad938a867d21fcc706f1210ebfc41ff16ed5757b20ed5f81a77c4e99fb0183eafe0c367e0c075569d500e224ea113f10e44a65ebb8fb84187b8a90f74a29afc0e8c99af5a27d2b007c205af4a28ee8afa63cd7cbdf821d71b5ee944266c19bc85ebae4d137752373832ef083f94e4ce133b5d609772c0b4d56a9a059cd70bb6961b03345d377f2e166c281b06f39cfe241342854ebb0878e541efa13d963befe34d0ea1fc2d9bbd254ea2010b26d924049fe58c63ff6c7c5cb8dca78f8b0b2ff06b3d71d3b3b16a303fbe01998d8499d2dfe0f08c09dc1680097ae48c8d64b9b2dc70f2aafe29cf417c0b2e6afb60c70398cfcab66d605dc5f575e6fe41997242360a86bb906dcb8f638572decb3030ea6a19f0403586dfd07ff376d34888cf20641d4f1a2d79e2f6c309bfc9e9fd27ff0baded303b68c7f063aae34cf7429abf37095627faa985d3aa66f48c6cb29344137deaca5a7466e2f384b1e985e7fe69573fd5082b3b42957fb20a473515ae55b15caa0cae23e2d407d0d948ad748e01f1f10be65eb484f4d637ef9417cad8b6131c91970d47cf6a2c2ff0cc995b4a1ac59c1db753d92538d9be9afe786f1013f81edcdd6a52158d30a5d55e8b4752984b2c6dd2785c82e9b2166c36e9fdb0aa6fc53b365d5972ff1b6d44f379e8438fe698c5e528bbd13db5ef39d750e4c8d29d466db7d44adf359e2b70e480e6a1f055c17206aa1171c98054cbb5067dbd5e6e84077c8f91c9aee15913c1a9e9f116cb6f0480664ae9becebdd9933e64950e2ba304fd0e87b087253e0b7bb1624c4cd3ed42e4f72727d262f9bcebd5b07de8c1e13f59ac4a3f3a25c99dd50ac0ca5d510ac86663534bb43af1356f227d19e81e74c557a6f29013fff10302e1403c18f74d4dce6e886ae5fc45c6e90042a2199265840f5ae6783b1d0c11f68f857bc1100344602e746f3e228732e54bb4f27741619c2633ec1bb9bc909c79506647abde091ed7231ab1c79ee2ad667e57cbf490bc6d8f3a682a5dae08b731134b0dbd2b109109905c524f31ab39bf0178127d78fd1cd369ce2e9d41cb9a47ae3b78fc5cff04b4c23fe463c27f75a7887e9a2ffc3857bc575dbf5356d87b7c45d53c627e43613c66ddc7fdacde7d6694b5bdf9f32273641bbea53e5e0202ec878d6c188d95b1a73b13acdb077b3f4630b690ac18eab4aee9df1d87cd066f93b8f1bd37d14e190b2edef06651bffe61ac1f7233dfd79b83c5b2908e7007b8aa21dbb231fc3307ce20fa9a639241921f17d1753d822084bfc0f7536eee4a1a3d3d6df1cff1ebbbfbe45f15c25f851154335c2370ac267d83822e017f279cf91833e6cd99d838cb8f95cfec2aa4d5501f854e908e7c63cc22787987cb18a5ec3b3996d838ba7293a145a802d5fb844ffb6e9d6559ed267fc2a0eda3923b11005c7225c45672698f1a768db1e2f10f027fa100ded30818714ab6c0350fad27bfd4808e84524f10f081ac43ca6fdf79ace3b74ee07e1d3128fd36d40b1e81604df5a9889c596d6431bc3e5fd4e998188ced888e3ec33829656caf4a047fad1875ee95d09b195d8f26c88ac1ce91edad12594f58a4fccbd447f75b60becd9cae487c0907b2eab3b8a45ae21f9230f95ad3f7b4418969eb8c63062cd1ea8d90d51814de91abb516e01adee780a4b8edb2f0ef02388385ce281cec1cbc7b2e882ab3830634d3d639ece9741aee9c291a6a63565e3c6ae3ac816268c065d913ad14e3d8108673743b29239fcb3101ced61b498e9016b8567d23c2f23373be218be4534eea9bae8722d353c805ca9c01e2a3f590fecec279aa568af6df50d7a1a2bdc6d1727a069daad564cca7e079261654ffa69f6ce3a8b3b62a8203b69242e9c459e4a91a33406d74a536796025a8b0e17874284b286a081bc45300904cebcb88cf649d3ade69ab0d698591d1eb8d40cc883a58799735dd439bb72723703042a2070041374a1e18f89b1f820bcf2c88e23c4a759b43b1710770788c1031c1a760a00ed0a2906b3a43bbc13488be48861a2696536634bdd1064e63be1dfcaab8b2e3067133290370483b2dbe878315c159a31946bf113e286ab8e3dfad0cabb417a74a927de7f35ed800a02c3c62c59954a799b2aa38cfa71a507e4520354a012755733db4b27c8603313ddd8fba6554dc85a420a614929ebc22b505ea6dee20bf769116e007dd9967716b39d24c63352ca0cc37011f24117b7b4a0b4892b4b26896e5db3b101574e26c6f53d2e3962a1dddd0cd22055287cc9152dff745928bdfe6f27ff46e7066c87a44cecdd4426b9af3e2b4c5559393ee2d133d9d053458bfc8003fb5580379a0264a052d5d598d4faff648581a0f9046737fd944155e13ea7b8366449b6d8bdc64d8f2523fe088c67d68e5022de83d329704a2a3215283f0f3fb2016691d62dfbe75bf703cee3c12aa6891c1b4378e67c987da3d6f331bb45779e5b062cccda0d87f348aec14c6e1ed0ebdc86ef4dfc4136f4a510d1259d3cab8132f9cb6343c6da184150b425424f7a5167ffe7733d7b0b20acd01abe22393f12bd1be76aeb9d31b3817d6a6820df44476f2e144e4c6738f4a5114958889fa6f895857864fdcfe17cfd0fc10b062c442a15c1b46319e1f5a3393cd6790e8db84d71acdb845b7d71a9110b8610cbfb357019875a18c66b8f5e3bb11da43915368555021be2266d4d836fab4c1941994e592fea433c800f7a9b9fbfe12ba6a6068e1c79f6ef18c8a6379468b23d0caba6abc1a59dd02037401a93ef23e597eb40a8b57358eddc7d6a56206f2737c44e25d83fa44b825ab13d86fc90be874985ce6a9b33570f3b853093a07c7b23a6c3539461d7ab35d1eec3c2916d4e2cdea457d278fe90cdd2d1aa06182e87cb918835427e131437e554c25be8a7e94ecc6fa6bd40d592ef9ae03a743160a70c941172b518549a9c60f37bab480bef8a0cf8d96c80cbc250444e68cff81d9daaca8c20b3b5f2b61866c4ee8260f477d821ca3474d825e25750fcc30ddd4cb826211f38eb42d979f563ac1b3927b4015f5e4f0392538cde0c230dd31d4517e0c587d7089b1de11d12986101f40d4285c8402e284a871236b82a1b1075d2b4e27817502b953cab7132f7457c5195ac60d0027c4c294f4ee4ea93a831882a311e8ba535b19a13454450daa574c5cc4b955a99bad2e3ae7d18dbd4d0ccd30e2e39edce49da0c4a7cbf51d02e115311290e3573c36b3e1bffec621a93e15560fccf5d94e0b192d78f3cbb44aa276cce19aeda1846e96969d65c3968480511272a8a27180af7a61170d6357f3a70880e0518012b26704ae485e49436724369f1127d84b6143da0116f61cf919895109c5d90e9a08afcc5654b3a35d8e40c818c6a293980927fcba55b598dfd734a85fe41244dcfd3510c3c563f64a5316ec0ea75ee593595da94c0e6434efe7d6b4f76b8970a764dc0cdd95a8595a8e8da0ecf9721121d12c542f091b63d21ca35bb168ee309c0f5a508712a98724bfe5e2bbef2d04e3a0c35913087cbd50e8de391b50398367e1e5c390e7947e43883a078727a601a6c23e5107ab054e3c380ace4e04a2503abd5079bc154dc3301926976283ad2ddcb9bec07749b3ca95594dfadbbca97c0cd24ece9cd8753da9e1832a5164c0a24c8c952afd2c9cc8948c38b812443aff69f73a775dd24b9b1c694fa05ea94c4c4e148288eb9e711739a07a56b148ef964f652dae14ab84f93e79d1e7a872dec572ecfd441c7e918951e56cdbb2063675c9188d4bb5b8bd8d2caa171ee2874f03e8ce52d5de766a23184c6dbe819562e7487580671b4b23ccb463905da0741bdaeeb4d91864110aeebbad694691084c3baae6a2dabeea2a6d6295576290f4241505755bd29d36018865555d79b221d866150d575ad29a7611804755dd5da651a06415857557d5ba64110865555afb76512846150d5eb7a5b24611804f5baae3715bbd5bb1cd2d29e5bec6d5dda52cd468cedeab5b5ac79541b19359beec92cc8904da47ae46859a3a952296ab49a3b74ca5c31e55c397a872cb2478dd43db26859a257ed346bb6ca1daa695d5aa335b43f0b6769d6cc7ad968585874de7e2715eb3a5d76415bceaf94a3b3e674c1c2942ebb7829c432d8e91d9a4d6b762884789884948b6326763ffe2c426279cd29b088e0ad84abf5f0591c0244641d3beb9c73dab498c640a342b695f1abc54c5d012fa5dbc82b7f0fe2d1b8167eeb94269cdf1bf505ffa6b31684986dbf70c180d9b5794e58c9a0ee5b9216c03d83a1bdcfc0084284c9ab9257650c10bc91a1c19e4ec84e6df0f1c49caf6783a95302e479a9eb164fec015cb4a3faea4a79c1e259e783f45db0cd00b6d09d7f53ab0c2800ed3fb4f407176e383ed43a95ab60e796153650987c7383e044dd4c22b77703e7806621c2c476118b1ad7da558a57f86b1400ca0d70b27cffc152c31a9a63b23f84985b9c3dd82d0f3cf9d30e80c757db6a13bf43821150e31fa3844273b430246d99ea030535856330c1dec534ab54488e86e2abf7bd5f7458d937317dd735326fbf789bf74f865ea9d507a97ea66454bfa16376c31938f72288d58a3d3944f93c45488af9a970a5c9387c97a05cd9232b4691ec3650f2e01662b6073499c0bb5d9f8512e238e840e2b8c60f2dd5e81f73d7290d88a88b9360966a29ddad29f6bb6cd602b46f65b871c97ceb76917ca3fd23a747200c4a923088946ee3734a0acfe1806b13a15dfb8268097065109903fa7cab49865df264d07015bd6ea0c70f8fe233ccca27ebdccfff3fc527708e237f65c1464ad5097ade5f2f553b419f393688361c5634197410ff210bed7394f354afcca855cd554e9dfeb8cc53d8297a2c9ac4c6fbd94e11eb9fb7a3f3090640629725cb4296f6860e95224fcc27a0dfe0f65e89bc760b8caad18e9dde3e9e97cafa5a907d68b1eed5d91e6083601af98f3dae118c77e405b4cb32359d18a70de0f1b8b2e66e20e4f6dfb7fdcf7b10f60bbe249722978db85e382d0f7abc0f67aa9658be2a2286ff3b35e00ec4d7dc58f244ac96b6626a1d34b66beeb9bece1773dc490a1023d0572709f314a3d0027b8001a02f00448088eba0ddc68bc3af77c0ca99294cf1d1fa534c7790b6151d274c51232ae746500e59b2abd8105c8c35223551bcfd7c5d5e5d73e5d79ac31f0646128e79236008b69870cb9ef6a8a39181c54e5999647e7a267103cc911a65278ef31dc7b3f75975c4c55075ee8dff655cb8d8541e8e42c494d2b6b3c1dab5fd778b88ef6ce79771ef4207114ce877a0b32cf13f24deae0db0c1a938b47a37ddd0bad110804846da4a9544f3404c20e2f4a44117151d13415640377de91a150aa3df2b680e709bf874c51448b31a04318a50a332a4c3a843c02a51cb488af4ae46a093a3567aa4cf284102968e2af5f413271d7789028954aaa144a8740f6a15f491ab2e29967634c982894db71a258997065dc1405d35ac59c6f4cd740c5625eb31aa94e942d2204f4d6b9eaae9ce650daaa46cadb54da3a7b8c10ffad6a8c2699138708deb532b727a2974b053ba4e52a7f3a2d60153efba133c0d291ee425af099aa7cba20756d5eb1ed9d3a7ee01247c4d573e3da40f24edeb8ef8e92af583277f0dfaa75904103e0ad8ce12a8931a086011ec4a05b55e0661d3c1ae0ba16628217452d8b516ea673184530d1b2b87faa187c004b1dd15511324112c35b14551d49b2a42228bbd7551af30824f199b92463d6b231cc4b12775d4bc3c82a38fcd1648cd5b21f165d59e5e5690849b67db457c8400bb10ad6ceff27a85b7e085141c004d6ead4ce443167a3f7ce8d09b54d74f8a91f83e1f31af4bde7957e5b249fe87afd72ad34d4865b105b32ca3d7a19cc0d3b729b648727b3e92fd6d1886fb9ef5ffa7ea8cfa032333b314662e737d12c8a634889508beafd618f04155b2eba69cb23a6742f0a808a007b6e5c120808f3ca8ab212eb04460405827696caaa6c296bd20c5ecb3ff8329ab66e01b5a9ee122f7fa032054306e75fdf7464c3901c9cac292c1735a07586e43145b46b168deb8b808a3c7341a18338d0cb0a9148fc71759b7cb177c7e536963788f40c652d9f3ae14a7f5f3b5b83b80072ea513007ab089cb6f8eb4928fe11d972235fa3369e2b433d9710fb4240d9b381ac30126195624d60d6b81bae98ee5ba749b06ebd2410e72f4222fc6622ca2f6622e9ebb4f153221ac0df55a05a25b6cdc8ea6dce20aabf6dc5110d15f4f3943d7d1634c2043e3d9bc88a2cac1d2e3e182e01a9645094a413527982ad8afe9567fe9a907b93fa235c057c245fd0fbc810d321e99f23f7dfcc77ffc61ca6efc54a89897f89e632a2be9dc1425fa33d45ab7a623112ad64fa3d2c203e9752da4468f0d5291c2dca2f093006140fe50825653366bede4ac98e19dfdfa52b6771a19f9b21aab92be0e7a0b453b650998bc0c21e02a33ead0dc67c4e8a3281106b338fed95919e6e6036bdde8b99b0fc3ddc8c05ff969bce10138fe3afaef3242e40bffcc742fd4e2b0739997fe0e517dbf5be341feedfd0538f0d8ecbc8be2023f6aa2fa6811f6b7d3bb2e8e30fe97bd57be12c27efbc5f95517025c8ef3c903efebcf9fe4f1eddeec65803fcfcab63c9364a15f3b0d6260d0579c113c217b5fb3a10e276503cb1a961733cb23fccf480ac10137e2803775ecbd7ce3b798a4cebc853d4d09929d6da34883676abcdd7cf1b165834d8c2bdaa19d6873a68555dbf60acd748c82803b3a789222406b3128abfc1d68b10d1cf6c93a3c336366c16ebec02f2bf404f798994e7e171c3fe78659162ce43bd7991be797bdcca4fc8b616733e2710e32feb4631577f39cda6e053477370da64bab9c39d21d0f38c50335818dbb7a42935acf871cf0591cb4fb5ac5ac334957fc7a1cb222c068b8679e3be2c21e6df16f9ec6d7aa13ea98f1d63ca79efb3944976ebfb6d7ee297a4b6df2f671844d5ae7b5b79ac1be297f92a151e3ebd7b1793e1ac34cfcd15f0d59f541a11badc0c03046fa86b8fb693ff3b86d677055f8485ae7646f69f03a0d9cf74dc0410ceb601777ac96c9db25c29e350b94e28514d6dbee14b9a26b27dad8c6adee8cc6e1d5ef502635da1d0c7ae35a9b83cb474db2e09ad6026c81b191b1ae0f99ad4fe3bf32d8bf379cdd166fa769209bdd372cc96ceaa85fd4b66eb2f7463fe91b0239e53c3069a4957375028b32d16b74e935427347e5860dd6faaffc5ba791f622368c6eb8f0c4f8724a3e307ea4c7fe99e31a7ffbae9846d1399709e18341b64d7b00b7c85eed63ede5adcfe9676a5c687d64ac3c28937c6360fb152d892d3f8b59f7e86b80f098165b0c18ac07eb1612b23388db00031ca61660fa1dc418f74fc990ad7454ec72fc43efc9be028ded78d2b6dc780f750d51f6979b7280a72cdfb31eece4da8a4c46ce2397d6c58626dd6da8bb3b78deb2365d1c2b6fed4a0f217955a37bcd9cf68b8282baf9dac1321031d94f0f153be701dd562d669c1d6d7667e9c961ee88bc3d4db4b27bf6b79fe6cce9c85fb7c69b838f0c37214df326a3b97c3b9806f89fe063e26c46748e69b3e9e84c3381f6db7c02536dcfe9652def966cb2bf8030b458e0e99ccf16c603069c94b1971d077dc6fd62802dab155f99a31dfdc64f97bcd9d9668058da86f69af5b4397e8f65dfd9de6f56b59c4fdbdf95ffaa66cc7aafd3df85b7912ff7f47ed86f5f4ffad06b8ecd64dde2c406ae7536c1d6781b0ed2900b9fc4813a37f27d6bb2f02b3fe39585dc464b74f2580b6cdf4dc75878b2603a9d3ebc768e7a7c13e6e4d8387cb501f334a171dbe19961d78b9145ce54c7b44784d8d36495a1e85501c3a8d5c175eded5be63fd42867b1990cacc1cfd87a47076ac37fce598259f67b6bbef32fe20521fe3c6f998179072f7ccb154d35e23162ebda788ddbb73d61a1b68f084c5a10ba37b27ae5c97266e752ae533f98744de4c5b6c999b567fe68e56c77ff29104f8a0307cbdc8a73047d882d27fc0001fb4e773462821136c71aaef395878ddb9ff9bb35a2957d5f3b62edadae70c9a664d51161588bc74f1892055dfbd61599323b5b465ab83b54c7b9b141d6eb6b30afb1ef75d4e397e9335e369a2e95162ef604b04e6548578f593f387086c06c34c1b96eb02589cb38c9b5fcb9ef410fdb344c7e1df69a35fad3f4ea5eaf6a32c0ad7782167decf81b4fc212a399c5e8ad68951d9b8e7a4c67f5d8b16b6cb50eb2e96e4fc7bb2073eda5a7dd0703ba80971abfa2bfb961728dcfb8964757861986fbc046f52497c4d593c6aeb7d1800eac87c8cc82ebbe3c52bfdd87638e1e9eb333abf50eb0a58e33034386071b8781e6dc569814bb6ead1eeccb846df0d4f5b7e8c2310374a9c13ff66468ce6b527444b2fead30c0ecb3c15a19128e8b0607969edaa38fc7ccc79ea0b5d1bad3b43360abc9cd744f3f1387b9b9bdcd4fbcc1b3bd5b35a1491cae594b5d2bcdd85f16fed7ea08b88edf94ad9bfba4790c915964d3a6bbb3693576844c091fd0d9f5b05c64038d3daf680a8634fe14cd1e0f5fb788a36e65c073dbf6719d7c6999cdb67e9dcd6fa42b3ec0b40fcc2c7bc6dcf635aaadba78f156f78cc3a0d634afdee75bc233df395fa02e845e9a6e25e747f4bc5fef728f6589c7ae84f6e0d53d5e3b37199f33dad32a3b6a1cec1d9c68b7b5a339fd08de6710073db1f6e6b0326cedb0fb15da867bdef5022ebdd58c5873eb5d6992f9defffeb19459dfc9361f4bc735feed1855ec3663ac7393ac7c2bf39fc1c6d3f14356cb3d66ead8dd9041133a3352eb0ce25ca366c06fb486336393eb66e9b3345b9795f18209bb3ed486e5996d5bb3d1d51737a8efadd277b85a5ed39cc15836039be658767deb9140db7678fa3496ef99963d99937b598d05ad353bd3b5fc73cdb2da9c6b0adbaa7635c6096cbcceb7f5af763678cb697cf3ddaa2d1d36883cec03b88766da9ead9d56fbc63c56c814cebb4337f6124b6ceed47b074d1be1314e49af609c13bce77d994c33f76db367847da666f428e5ea2472c56910e283ea281ff467dc8e70dad57bb4627ed59a0570dc836a8b690ff0cc674be8f1d71ec38f6fb8a08b00db8f384956e6f8c25f7f10a71ac1fc3a2a3465d9500ecf706b2a3387d9719fd9c7cfcc871199155adcf89d0e6e77db1266e484efb7c26866d4e6739fadf9cbf1ff2aebb3f9c77bc2420e5eccbadc0fe6bc516dc416d3f5703a28faeecf6b5b2ee8dbae8a6d3e013a8859c4bd1916ddc34c2fb8494347bc09eb6d64cdd8aedb6851971a786397ce5157ffd4036ad0cc7a714d8e99665cfe36f0d3acb7b7e6fb7574463395e66a4cbcb770ebd675dc0598e7dbebf81d4db2dad59d8f9e58874fcd1ecdf80704d69d53df60b3d5f6ed30da7a8d5e431b45ddbd67b33e71ce12c184cb8138f79e7794eb3d5856e9b3dfc92866f3e91bfde40d78d837c7da4e180c9dc966564fbcb17918a029cbcf7b98d92abaf940dd8cff27a7257ca1316f93317705eb0db21a129b39d24ef7db92bfeccd5db3512b27cdf691804de233613618d028414398f135cb109c26a39a653ee8bcfabc919d61ab9e5bddcc92de39eeb8700316d39c5958de786615264d5423a0d3ddd6dd379f93a7d3e061c4738f7238d9b7cbc199f257cfe7dd77cdfa8d6bc6cdaded3a78dd626a6e956d271fc8b6e3637aef1eb5cd38d536130707010dda90ad1dd75aae5a5513dd61e2435868572efa67c93a7bdbd7e7da286837cc2906c91cf0149a564f8dea740cdca4afaac5a6896fb4469894c76590711aac9ea83ad5a456d9db3f0466de7bad6aa2b7ae980ca80bc74674ca36cfd60dfbe541be84b1d5c9e91ab0fd36233659fb1bf7264c37fde831e5c277cdd5a375cc46db90c5ee9ced30105677db2bdddc7a4f18fac29b30ced1c8c0c86079ae58844369da19e124e565a08de488763e81718eecaad76bf59bbac538a56418703b6667c72acdcfda12039d71ba0f64d0c1acffe485c531bab61a13a7d96e61b203969f9c49f708c0d62b3cd7ba080c67d230e7c5cdb65fe336ce362c76c340c8be81736829db4737a1e5cbfad5f4290734a7e46f4ae3d64d587fa918ecce7ba0bec17cef2ae6e2b21a27f403750479dcca8f992f77da472e56306e0119d7d6332473a365b613535d8025b32b774d93ad76e4e1de0831654e6b3b119b568cf16bb5e300ffdcaaea6eee9e5b5b3bd31327cedbb61bbd61ec83b62a074e96d95dc711a38f30ae03ecfbfab11a7d3f4616f7d8ada7f9ee45d3eebb8bb9f405ed75fcba66d19019b0f95b6d046fb1ab3779cfedfa364c97d99170e9f18d3b4bbde33f74e85db15f3da61f4aeb4453ffba5bc5bca50e4d6da663299271d6196453da828186b79e5df2946e95f766e8ce7ed33b062d1fef716b30f38d0164e34d5b6a41d6f069fd0679a430505c68c3722c1d145bba6b16c6d473256cecaf8cd1dc5f7618bf702bba93c1e92b3404f58634d95cdef6b27bc47fa8d156e7fe275ac678fa3ce244c65caaffcc4bafd01db5fe113e6f87744d5776f4d6b34187426bde1998b85624185cf676b17a8875d94cf85a2a709981ba7468c5623a27a101ba663d836ce1627a86d3d541276fa3b62ce553619416a2e8cd023969285094b8091938b8e498824372b7621b2628dcf4f5d38466e0c2db2e188d0f292b9d8497ec87f0223768c25389f7f2b7d0462dbcef1d2a5d7f76b44b20595780690c6b7ed8e569c035f90f91f9ed052130dc243c751e4824075ae261847e4fb225a81cfe251abc964cfa028e937f27a948a57928f8b0d44e83ce89f718dd2ca2d307d14011db5ae2f55fb34d439d3aba8b49ec65950f44b5c188cea2d55b7e0622eda77952f5c0ecaa3d382678b5e7c5f81da3280db6ea55c8a0d386be4c0ce3c0b4d87b58fbe8112ebdf80298b379bdc5a50f96806e949aeee6f2602ab1185201f2c6daaf4a45e39b965d3b2e66c6531325a2278edfce9ae4028208c24cc43192a59e010cdc1cb164589dd0ca5d1e8c930def3974f5db1ebb7a1b4ec0b9b60bcf6b35044bc10a6e952cedecaeb2b42c60d023cd3fccae994d58bc3b543650cb7a7ea08182407cc95d905ee744ca6e1f443f81be7f3a1ab4e4483f05acffb99d4917bb8bb86bdaf885305d5514b1466c5739012e9dfd7e055ab0ac82860140f858a905ddd412ee022ac7c2fd75457c0d1dd421ebf396f854dbd45443346ae693fb44c90b75384290ec39a3429b45da031b2541ed3442732f95ad489ae1a39b7f577dc4716313e537c80a03adb1e03fcd255a00125cefa0cbb556175dadb8e98fe5e06253f45d5600dc47f799167f6674a20a13ab48403d1dce353de1400377dec02ecd4a258aee08ac8ed5788f20697170a3fd7e021542315ba3bf93188bd00cb5c2d9d2c5311d7cdabce3040e82177f7169b43c7918d8e24315399edee46235fb23879d673023756e0283b159a5df0c431c2187358e1a4332f9db45a3871b92dd90ed7d9c8447c52ec374ba83582b119dd4c5d59fef99975043985ef352ce07470ce14f532cd0904e7f422db2eb26ff079bb6ed3c95bb82e17f1405c35afdb78c0d315be187187e0524b607e947ee86f91f90f0d194c17276eda06e80cd05a2b71af0d6e2db83c00b445595057a0691c0aa0eeb84234602adeb0339aeba9070140cd6fbdcbef331e8a64350bce6f3e74e3a76071c3ff97f34a9bf89908e1498f4b5bd348f253c3667f02fc98b33976f54f405fc329c54e386c506889f8b64aa0b1116bf78550d087f011c0fb7e64d1cb32cf3bf3d7f91f4374241e9a413b5f003458da79863bafcb8bd20bfbc4f11044eded39ab2b19593ef749afab269963f0aca8c743d3d8c08220b1b7cd0b41e343745629f2f8c93c8d3e634ea62db0e4443d090055842707c470c03c856fe10d531e6ea040b0d9b967434e3146e2517471fb26a50726c56c69fca13b5de5e627d2e2fcd1b55d4e88e1a516813ad093484803852d93fefa6ca997e58ffe461507ea38e1c244dbfd3c95797e6882ac143bf85ccd94192de522ca47ea9473542cca01e59f31dcf11ca7b99910795e05358a10cf421e618e091f05fa73f9df81c900ac9701b239a2da4f126de0bd45e30f2f3e15e832b1874df600dad60cfa784e876eb776c72b469741c69dc9593b37477669df96b2306448698e8cf330945ccea5ab808bf69e0482d96687d9fa08079d5753346b86eb66bae007a18c83d34813cf7c4a650c220ee03f2dc5e305811c1c590c5f0375d5fa61f3be73e2cb5162dc3cbdcac9c49011f085a81e0069f9c4944f778bc42f28b0654a4bbddd7b5b452309475d45f7729450e8544febff5c97c2512c910d5448ba0fb11511a97874461192adc476c47d8c7c37d4384d1304e20bd78a2eb16d31dbc28448e6dfd021f1e64500a56674a1e2430dba93186e8d71932637d6ce4a28590454ace37675bedebb9632c75864fdad6456a25d68dd92ee079cbd196340611518c09b6aa1490881f8a31ed2ce27c1e5506c26c4243203f2e2a30e158ef4472bf124e6daca3f72d8f34110e19f5b4c517e42b8a35620798529509730068e63a81922baf11e00a81a18d696d60360cb52d1f5c672020e38ba6d5a1e9213fb42c45ed3980fefc4dffb56231e7f5626fb808597f7427822988857921b96b7dce6ff193ac2bcdc7fada93c46a4e74c2305f15f39fe2f894a42ee692b91d19ae6f8bfb4ad8b1a900f809cb1c4a68b7cbe1d67a896a21e29e7325a12171620ff14a1b601ec80b5bb514b110b8510926ff86f1994b9d184ea61397a93ede1485f4f21bf35db8e7173b50c5756a1138008d5bf53ad6d7d8fd8034ad60733bbc1dd72d6e5260e9662998e74836059510545b832e90c758382b0918a81f2fe868108618eb5a8268e99b09899195a8999b089b774c886ea5dadda5259ca3146d52e2534bb111f03d71b1dec8d4b42fc53f50f51e376a2f34d2262c5298cb709a85eaf6ae7c61f8d8f41e710667f073c3f15da4f7078a8e8bd5a05698c949e41b8cfd1517f8f260971e1438f11d063e1c017dc27fcb2dc70a93c0dbd465f3875f0125b95e4f2d810b1d4458b1b5d236e71a643e4884c871fc1e667127b9431dadc6511b7e379638794b2d21d0ab26e22f936d30f9593c5bfa6d5fd18ec5de342653d939425d8532815e9a49c8ccfd707c3702d8d1e805184b3f98fb69d36b7a4fac995e066ad43975d7bd86f17c1cbb138bd8a2f9689eb35ffb6c18ab52c29e53dcac38fb1007bb4a97d520332038498c320c42630ecbb65538305891109fa3682fdc7b8f268d731bfeb1df8a050c20862635f06c1ea73e1792fe61541a09aea1957f63402a28dd708b2473011d0fc65b1f36b83c6106bb2fae21160133aa47f2b1f33a7716719d77fc9dcaf8a29f87d16df5913856b949376f8815691767eca3b63d8cd1e517d65cf9898fae284586790ce9580d372f36a02006000b17049b3c03e9459b7e23eb1e9c1e0e8527cb715884ddcfc92697bcdea9053158803429ca92f24c2490a5030ffb5aedc71efc1e35f4bb08fafb53c95035138d98cf3b8aa5d868a7731599f3acb7bda61b070976e918aacf04cbf4ae18c6a295ce1199b69d143fc8a116d3c2638b65956de991cf3e5da693f1597c68426560542c993860b825cb1ac0a52d3132af4f94f599c49901d48ed80123e038f7031fe26ec3ae24299407f6b591c2a0ba90f2e57b1b8ce1c209e067e82aa33a04c2f3b242c663b8794752fde042492d73084ccc2b51a044891eb7405a80ac0841d80ced1c3c0d5fb1b12cc78cefe3a7e52dbeb9044e433221db8f491dc801f04c8fbaa5a567577cb0803f4d43bba8b7b0087312dbd94295e10ee47316a334a640a6b5402074a9f917ed38c93b36113cc1aafea5bc8317d66495102f7d2e55108510a955778d35770ee8abd33601d5376cd2f6a170813fd101028f83ffa66910f5cfe0b499a6ba00cace688aff3dcaa340f629ef3b2d9de6029fa96a3b24e072d000cd52060979cfcae320c3b96a9267f989fe8620e20f1142b201bdb4285f284ac88e1431a81ca4ae0d2e7a79f9f38e9ae239a6780f7d017ba8cd2b1182453a2b969c26e214a6aac2355ce0717a420f8c1f81695a7674741a246ad96aef4ac101d94642c88f2140b613dfd5190ad1e36c9cb7b66004f07c9765388aeb8ef425b7141c8bafa50ffb4131caf9227350fb675607397979148591171ba6b966ce0ff72b113bb4b767226d5fcd6837b5f6e7a00c8540f13feb19cfb65859a4c1ab531459420eadee76a69b0da2f15f462e16a6f2dbdac932ca4b2b090589218b7a23f059e0d2124946a5a88b541933d848f2692ca6c648c2ddc9b429e855ae7f27404c8b9c26b060cc0cd06e18b8443403d3c0d9ab8ce604b79845fda0561083f42aebf1a247269e86e4a21771f984da0fa1b22e817496ee2d45049f4a754e01f15e246cc17fe46c7fcfa60b5981ae04b495cf98e405681ae4b420e450664d9888be289942cd030882d2b203ecbe1b5b279ce0635bb01252fe5d7292483188255b791c4546791367bf48fe2cec05de93cba0224c103094a88531083d3ebb72ef95b4631ee60b5a6bd14fd33eeffdffffbfffffffb7bf670cbe06d353d82c2d65a2100d8884260502b7b4b4b4b44429aa8406ec1ab5d6daffc11ef88f4d884c017b206120b023ef717ccf0cef0cf81e355e54152fcd12ef4a83f715c4bb02e27d2db2469380f76450cae2dd41e199d238bb29e363ac351c291f53df1d023c9324efce21df64c13b23df83c67b5ae33932ce6cd078a5997777f82755bcaba2f795c85a16c7ff6c7196d3e46387bca820bee9f5a65c785b51ac3581fdd217ef8c0ddf3df2a600795dd95acd17efa97186a3c4c79078537ebcae5f15015e57eb3d39f9a041d6563b4979ae849705c20b4be23d05e0831a79797ade242aef2ae98595670b82f7197a59322f6c87d70601af84c3d90d131fa351b2e2d5f1c033a52fab8817e63a9b71f29d1eaf6a8cd7f581332739af182c5e96d29b34010d196fea88b7a588920cef8e06be0985b39a1ede93e2e501e14dc2f2a61c795d169ce5ecf14bac351c413e36c6d9cecd3315f149a7885f02c17bf2e2b929d69af4fcd20daf2a7d5d80bcaa365e975206839cad08ff247c79427893b2bca811bc2df00c47ccc7f8785594bcaf11d676c878a611ac692f3c08486d907196837eac03af4d055e09e9dd30dec7837795801776c1bb52df9720673716f8d8f7a638785be98bd2be498737c5c5dbaac0ab72bd2f105e95d2fb92e15551795d6abc3a077826f0dd79e199ea78b512fe279297b5f3c2d677d8f7d0f1ee34e09b3a78d7e37baef818bda819be098e77a6e63b37de9df099c038cb09fa58eb2c2be37fac586b52f34b32acd95479cf032fcb8c173682b31aa5f716b0a673faa5365e56246f98a1351b18deebc0bbfae07d1df1aa54af0bcb9b42e26d29608de78b570ccfcb13803789871755f34d3e9ce5f07c4c917725c5fb92e44d05f2bab4772701dfd4f3a2d06f32c0bb63c937a9f03ad9de3121bcac306f982cef2a87f775c7ab4ae17579f1a250f82630d632a5ffa9e33d41f19c11af8c06be7be24c868c5d36de875cb349e1bd0dbc3c54dea42c94bcbc2cef8549f1ca40f15df9ee60f14c63dee1f81e195e161a2fac04673b6c3c93d2bb53816f02df15eb7d65795141dff4c4ab4df03f819c31f1f284e01523e5dd117a2604bca790cd74df21e03dd57c901daf06e57f2a795316785d21bc291bde1617af6a8dd74582339a46dea3e1d5e6f8a02a673c42bc6234a5a1176581b765c16b73c5cbc1c3abf1fccf1fef7d7c0f0f6737697cccc9bb43826f9a61ada682f78856e95cf14b257857dafbc2e1dde1e1990ef06a707c900cef4ae985d1f0da60e09588789da0af52085e9939bef3e1530d94f708f0ae9c785f15784f6a3c37c68b8ac0dbe2795117785b19acf118e015e373b619fc9309ce789c78c5dcbc27309eabe25d81795f0e7857e7fb02e4eca6c92badf1eed0f04c7bbcab07bcb00fde1d107c130c6b4dfaf825295e5607de249bf7b4c37333bc37fb4a60bcab2cefab904f351f782f91b39b4c3e76c2ab5df9a05dd359fd921c2f0bf5c2b0bc2cf08551f16a7a7cd095b38ce67d8ede94d2eb8ae14d29f2ba566738657ccc8a3775c8ebda596b32c92f8df1f2acf02639f1a2de7869cc7879cc37498eb31c2c7e295b6b42c22f99af86c90789f1b216f086a9f2b29678611458e341e415f3b396ddf13f5dbc3b609e0992339ea557cc0b67371af818f96a1ef8202f5e9b27be8d02bcacef854df1ae2af0c2be37d5c7db6a7a591478c3a89f4e957cb77aefe27b465893f1bef3e03dd97cd01d6738187cccca9beaf2b6fa78592bbcb031ded5eb8589b02663f49d09efa9cb73eabb93816f3ae1e5c9e04d9a4129cc7b22c073482fcb026f182c6b3738bc12085e9ea137298f578b7d901aefce11cf14e6d58e3ea88297077d93e8785548bc2ed7cb93be4975bc33357ce780b59ad47b5aded7d75cef4ef927437437bca805bc2d9ab526ad5ff2624d07e897f6f8a4b1f020e4bc32517c17e5d549e4994a78b3ed7fd2b5264f7e0987b31c998f1de05579f1ba2e59c301e463628451e145f1f04d7cacd984f05e246b59eb7fd07827e37b54585bf50ff2ae84de571f2f2b8f370c09ef36bd0fd1a7cce87f9078537cbc2da6d7468f57e2e0e599e04d22e13db1f15c01de5396e77a781fe47bd2b326ffd2082fab831776c4ab81ff138117c580b775f36a667c5094331ca08fedb03693c77b386b3c0c78c574b0961de07fc47851aeb7a57a5562de5794772688efc69ce1fc7cec86331941be2b7a5731bcaf35de9de09f9cd63200bc0f116b363bbc37e6dd41e49b2e7851167cd3d19b2adfd6122f2b91374c0c2f4f0a6fd212ef2a88f7e5c88bd2c0db0ac0cbf2e28579604de686efbed7c905ef1800bca80abec9e85585f1ba925ed413df74c9d91f5816fac202795322785d2ebc2926de16035e4d920f82e2d5863e68e7ddb1e2993ef0b20e79c3bc70a6538267327a678af84e042f8b016f181b5e169517c6c6bb67bccff7f2ccbc493f6f6a82b765e55535f1ba26f0aa48785d589cd50cf19e17af0acbeb12e4d5c6ff81e4d580fee791f754f45cfaa270f8a63b3e6543fc8f126735aff7027953476f0b9097757a613a9cedfc4c41bc3b8c7cd3064fa4bca7093e889097258137ccf9f2647993f47835047cd0d0cb1ae38565f2a6c0785b92bcaa9cd7659ee594f14b362f8f086fd20faf6a01ef2b837727886792c0bb82f2bec07807e47b7c784f493cf7e55525f2be2c78f7e87db257d5e4759d6bd906fee78d57fbc0077df1f2f0f026b5f1a6c4785b93bc1a1a1fc4c2bbc2e27d65e0dd29e2992cf0a68ade161f6b4dc2fc9218ef6acc0b9bf26a1bfc4f22af1eef0d97578abd4e7a7895c4ac654cff63c73bc3e53b0dbc2825be8992f744c6735cbc2bf37dfda1a4c459ce101f13c1bbb3e5991c795389bc2ed59a0c06dff9bca94c5ed70aef4c0fdf49e0654df286d9f29e2ef8a0435e950caf8b8d339c1f3e86f46a577c50785683c47b61bca83e5e1a3ade1497b7a5c75a16e67fd85893b1e0bb27af4d22afc4c3cb9ae185bdf1ae8a785f107877a63c931c8bc2ab72c0fb0ac0d9cd043ed62949f2eaecf14c13bc282edfb48017d5c54b43c59baae36d89e0dde1c037adb06643f4de08de1425afcb7bb50efe67016733df77867c3a59f21debac6601efeda134c77b04bea78eb36ccaff1cadcd98f1deccbba3f44d55ce70d6f858166fea8db725e68ca702af18a057bbc00745a1b4be2bc7fb0c60cd490cef98f2f53a415e99a4efaa389359c0775fde930ecfc9f0a2b47c53222feb891716e63d49f11c015e1b4a5ea988331c117cec8ff7d7f7cc719683f331435ed521ef0b0067380df8d81aefea901756c1ab95e083d0784f3b1fd4c73234e77be19b1af3ba54785364bc2d4ace70501f83e14d59f1b660ef6e7c0f0ccbcc5cf15ef66a6b7c900baf13a0770c082fcb8d37ccf6ae247961e3190e171f8b62991a3fdeabe355a5f1ba44b066d3c27b1c5032df9d38be297bb3a0ff415f561a2f4c698dc690f762785928bcb02fded4015e97cd7b1a7a8e87b58c8effe162ad0907bf74e51d03df13888ef7aa22795fdf5a93207ec9cbab1de083d657cbe483c67852c2bb03c43341e04521e0a501e45d59e085816fc6fd8ffae994f41dcf9a8c0fdf896f0a92d7f5c1198e071fcbe1d5e2f8a0195e94015e1a3dde1d4abec984777df8275abccbf43e5c5e141d2fcd1a673c48bc6268943610867c57e0fb92e265ad5ed80f67370bf81808673981fcd204af4ae875edf1ae76785f7a9cd980ef4d72f698cfa09077d57ff2c5cbba7961e6998d1caf34c1cb4ae2854de05581e07db5703607329ea6570c0cefa986e75a38abf1e1bd29de191dbe7bbd2927de56035e994cbedbe25d4df2c2467887c0f7c0e135f19e521ff4c71acf14af98d47bfaf29c964f337cbc977386e3818fedb1a6a3c12fddf1fec4a362effafc939ff7d4f34188bca909bc2e0fde54ea6d51795171bc3468bc28f09baa786fba57a2e4654d796167bca72d9e83e22c07fc18ec6591f1c240f0ae2a785f452f2b002fac885785c6eb0ac1cb23c92ba67b751c79a6155ed6062f6c881725e66d01bd280abe098877047ccfd099cc03be1be25575795d09785988bc6160785525bcae2c5e151fefebe63d4d796e006b3557bc77c68b6af24deacbf2f2c21c79590778c3b0b056b3c17b40bc2c0c5e989797c5c20b3363ad89ce2fd1f0aa30f0bec837c5c8eb02c0ab25f2415c5e55d3fbb2f2a61a795d14bc9a94ffb1e4bdf95e29036faa88b79580676824f09e0dad2e2f6b026f18f445edf1d2c8a1b4f4aac4785d1e50dae2ecddb5e37da2bcabd5fb1a3ac3e9f2b1205e27e13ba63bd301e499b67755c80b9be055f9bcae2f6f6a85b7a5c4abc2e47db1f0ae4c785f5abc3c1fbc49e9d96ee07dd03715c2dbfae1acc9e9974a5072e34dedf1b696d63602efb3c3d9cecc330db19673f4b14cde9d2c9e0993b31ca18f5df2ae3cf0c2a2bc2c0fbc4938afd6fa20225e9596d755c89baae26dbd5e1522ef8b82f745bea78c77e7789f13ded396e7ccb52613f825305e1e15de2426de1d2ccfa4801795c43761e05535f2be7cd6de59d62fec863795c2db22c0ebc4e71dd3f3a686de561d3e1abca80fbe29895765f4ba147957147861deab52e47d5df0b29cbc302eef0e20df943abba9e363179ce5ec7c0c91b31a28de2bc09a8dfa1e08d66e6a78a54cde95232f2c00ef6a022f8c7bb5a20f72f2a2f47869e238ab49e4bd3cde9311cf19bdac235e18ec4d51f0b66e7887e47bf0503ae35525bd2f28af13f355cae49d11e23b4cce70a8f8581367d909ff43f4ca3cf1dd0aaf867ed0cd9bd2c0eb0a5f14182fcd15ef567c8f77f6f82b66adc906bf94c3ab2df24145af4e26cff4c3cb8ae2855de0d548f81f0bbca89b6f3ae3785535bcae38de14d3ebaaf2aa06795faa352728bc63507851347c531c6b3442ef816f2a01af2bf5b21879c35859a3217a8f5ce321e415a3c19ab6ff73c7bb22c10b93e15da17f92c5abea5ed713afa6c1ff24e0ddb1c037856b38ddc77a78554fbc2e0abca70c3e28913735c7db02c11a4decbdf40c47f5312a2f6b5f180eef8e23dfc4c1abcae37dd1bc2847de16cebb238067aa626d0b791f2bafcc06be83e25de1f0beec78551c785f299cd548f15e19efeae87d2de0dd397a26d78ba2c0dba2808fd72693579ae23dfdf09cd0ab92e47d856f0ae86d6d7951e637017286a3c5c7a07865a4f84ecabb3ffc13325e94976f6abd27349e0be35551e07d71af0e22cf34c2da0c19efc9bc282d5e9a285e96005e98126b323edf69f0663cef03e675b27ac7a0ce6a44f0de226b1923ff53c6d90cd0770778530c785d159cd558f2de212f8b036f52cdcb33e54da2e245a9df34c8cb82e285497296f3faa500acd5a4f19e1bef89fb6d7c7938f08a31e15d31f1be2039cb71e4977cde19f5bb30afca00ef0bf5a6fe785dbb3458bc2b04bc30009cd58479ef006b2fedaacbfb52641a199def7ade9d149e498d3725c4db2ae46521f1c224f0ce78f9ee0367333fdf05f2ee407926365ed606de2499b39dd53325f16a867c5096f7c4e5b9f33d5df11c136f0a84b735f4cee87c47c78b6a7a5b25bc3c37bc49629cd9f8f14a31bc2c3bde30e3bb28fc13a1778b781f9a17d57a5ba93725f4b6ca7857e37ba6bcab35de97d2cb2ae38589608de685f7b85785c2ebeae24da96f2b8e302bbc2b2eef0b9117e5c54b53c58b3abf29907745e57da971b6e3c633c5ce6c64de6bc03b63f3dd1b9ed0abb2c0fbf25e14ec6db1d66ab2f7d6573be4837e785398bc2e286f8a86b7858512162fea8e97c68d9705c30b5be355f5f0baf47835107c10192f6a8a972689b31c273eb6f4aacad725c6cbeae30d73c29bd2e36dc5d6d6f53e3abc2b05bcb027efaa8e1726f3fec8f7b8717653c9c7c697e78737698f77a9fc1302bcaa1f5ed7015e1e2b6fd21767384f3e56c3bb62e17d15e0d582f8200cde5517ef8b03efce10cf4481b50c90ff01e35d89f0be947859ad370c0f2febcb0b6bc0bb13e599de38cb513f967436337e87c8ab92e37dfdea14f24ce1bba2e07d11adddf4f04a2238bb49fa5809efca8df7c5f43ec5f76c67377f7ccce78c8683f73af071b2d664875fdaf26a4d1fd4c6bb93c837617076d3c8c704f0b2707861769c399979c5fcf09ebc3cf7c3cb427a618cbc3a753c53ea5d1de0853959e351e41513801725c64b93c5cb7ae30dc3bd2a2d5e5706ce683279cf7c773cf04d52de9500de17126738487c8c00ef0e06bea984d7268f570ac09a4e1fcf44c1194e063e76c7cbc3bd49456f6a01af6b82359a0ddee3e05d1dfe09156b4d52bf54e55d55f2c24c7859402fac8957c5bd2e27decc82f729e26555e00db3be3c1978c58cefaae67df1b0d644865f1a7a593f2f8c88331943be337a53dedb02622d1bc1ffd0719613e69740787512f04c249ccd54f01d1f6734b0f76a78378df709df1312cf79511ae25d49e0856d9f64b2ef2a78b7827f42f3ae94785f127867b2efd038b3e9e29576cdc994770c94570701cf24be1bc33f41e2b591e49584389309e43ba2359cf1633ebc3b10f82601bc3b23f826195aead9cdce2bb1f1a62af0ba047086c3fa580c6b3c6dbc622a78350c7cd0141a84f09e54784e5c93f9f96e837779f82756bc3343df4df2ae78785f7bbc36625e098a97e7ca9b94c65a93167e697d4f453c07c4d94d201fd3e0e5e17993c49795c20b03e36c47f03e3fbcac06bc617078552cbcae02284de06511bdb004bc3a7c3c1300de94096f8b883050d69a84f04b3bbc3b463c53242f0ae79bd017f5c54b63c59a8e0ebf748837c48bdae2a591e27d8fefd161adc922bfa4c5abda5e57136b3643ef85e05549f1ba2279751879a614de9d259e899277c687ef20796708f05d09de5318cf59b176094eaf34c68beae19bfa58db4adee77cb50ffea79167684e780fe8bda97925335e2724bc63c477077da604bca999b705e5d5a6f8a0ee4d25f1b616f06a5f3ea8e7656d2fec8965645edf15f1aa30785d4767374c1f5be12c6704bf149ed5a4ef55f1b2c4bc494d5e14226f6b5f9b3a5ec9e7d5943e288db31c9f8f31604dc7845f02e42ca78e5f4a75486739503e668197b5c50be3c07b2ae3392f5e54ea9bb29ce588f14b346b3a54fc12093e9d4af01d00ce662ef8ae8f97d5e58529f2aeaa785f18f854c3c67b6f9ce56cf031475e5498b7d5f38ec2b2e478c37cafb6c707e9f0a6a0bcad24de151d2fec747663c12bddf19ec6f7b0b0a683e59714513a7a535ebcad485e4d021f84c4594e097e495cd368fe278f3785e56dcdf1aa6a5e170fef4ef94c6ebc1a241fa4c4990df91e25af6af5ba86de191ebe8bc0bb33c93791f05ec9f708f26a23f820335ed504de57d0ab9a795d3abc2a3cded7cc9a0d10ef91e0d542f0410538c339e263479ce15cf2b13c5e1604de303bbc63f13de0bb63bc8f082f4f0f6f921b6737887cac83571be3835478b5087c1001ce7018f03135ce6e7a5e098fb3dd5131f1ba24f06e2e6a00dfe4c5bbd3c43365e0ccc68d57e2795508785f4ede951aefab0467332e7cd7c8aba51f84f3a28cbea901efea82f705c48b52e19bc478771af9a69f3735c4db32e45591f1bac4e854606d4685ef14f0aefe78613befa989e7867851a86f1a7a57ae1716c2bb0ac0fb1ae2453df926215e9407de56082f8b8d17c6f46a947c1015af6a7d5d0638cb89e16393bcaa155e571867383a1f7be1078a97358017d6c5d933ec6a8f178673561381f70cf09efe203794ae7c9a01e4bd26ef0ed03361b1e6a486778c94270178557fbcaf26af1310de311cbc2c235ed8eb4d05f1b60ef0ae9a785f1478b789f741ade9a0f04b81bc2ba0f7b584c7c6cbf381574c0a6feacadb02e34579f04d48bc2b1bde171d2fca8a978689b39aa0f7bebca7271fa4888e1e6739477c8c044f5850bac05a8d97f7b8785302785b59ce7034f8980def4e08cf644557e55dc5f1c2f60ce7918fb1f1a68cded61f2f8b8217d6e5cd2e781f235e1e296f52146b4ef61543e5dd2bff648ab31c0b3eb68077878867aac0190ed1c7bebcda0aff9381f737be2786359aa6f7d4339938be137a5542bc2e06bc3cab3709fc3463c97b007867b07c7781351ba1f730395b2dffe48df734f34174bc3b2c3c531cef8e03bec9835713ff6702679bc5fb3c59b391f2de25afaa02efabf3aebc2816bee98c359e335e310078793279c5949f4eafef765ed596d7858017e57dd3146b3a3ffcd207ded3d17343671918ff33c5da0c17ef692fcb861746c73b637e4781d78909ef18f23de57c901eef4a8f1776b38633808f9d675907af41d08b3ae29b2ef0a672ded694331c313e46c5598d13ef91b1a673c12fd5f1aa5eef4b002f4b8a1736c97b1bdff3c2590e15bff42b93c777ebab427a5d0a785596bcaf13ce763678a628ce1603ef639ed520f21e1e6739377c2c0367371cf81809ef8a8cf785c9d98d231f0be1652df28699e15d35e08571f0ee687926069ce130f1b124de5512ef2b02efca8bf7d581331bf13d0cbcab0bbc307213f02e81efb1e34c87e999bebcc878750879a6ef5d85f1be3ef0fec5f798f0aef67dd93013c4bb7ae27d59e07542be63c017e580b7d5e43d59796e8a1206ce9ef29904d668c678af5cb3e179eff5caf4f1dd0f6b3b663c1309d69a68f92520de140b6fcb8997e7e64d12c0ab1ac0ebdae25545f1ba2cf0ee2c7d530d2febc90b0be24549f14d49afb6fd0f045e9ea03789cb7b02e2b9f5452df14d95bc1a08ffc38077a6c97773bc272ccfedf0a63cf0ba06f0ae65574fde979777a5c5fbdac09b8abd2e195e1d053cd3096b273cbed3ce7232f85823efa987e7aabc3a8a3c93092f4f076f92f96a0bf82020d6dee1a403c52f8de05dc9bcaf2baf4cd377659c0d805d8bef11d778ac78c5a8d66478f82e5cb3c179ef01ef82f13e3f6f6a04afeb85b31a0abc37c83b25df638077077ca62fde930fcfd5f0b200f0c28a94b6bc2a48ded7f6da68f1726479558fbc2f0e5e6df53f7dbc2a2baf0b8e77d77f028692166b3a02f8253dd63217fe07e94dedbcad17947e78752ef04c37bca8ec9baebc2b27ef4bcb59ce1abf94f3aa8e785d107879be37c9e8ddd1e299327935ef7f5e2f8ffa26d9f1ce68f9ae92b39c427ea9825713c10795f1cedcf0dd03de9318cf69f1aaa6785d615e99117cf7c559ce7e6c90b52613fc520d2f6b8f378c09afea8ad7858197078337298637d5c2db82e25d95f0beb2785191bcad0b5e9d0e3cd3798673808f91f1ee48f24de4194ed3c704e98878b78ef729cf7216f9250cce6e0cf918072f8a8a97468937e580d785c1bb52e48561f06a491fd4c51acd94f7b67765f4be14f0a634785b3ebc3bdf3379717643c8c77e5e9e0ebc6206f0ae26785f5bde95f24f8478c5bc29ed6da1f0a21ef0b676d69c4079c7a4f0ee94e09b6858db44dec786351df197fc787792be298a52182fca846feae2d5b47c90ea4501f14d8ebcaa9ed7657486e3e463329ce1e0f0312d2f0b016f9816de55d30bb3e1ace687f7b0785112785b4ed6323dfec78b1765f34de6bbeae38535599b81e13b069ce5e8f0b10dbca9d3db4ae175d2f38ea1e04501e09bbaac6586fccf18ef693fe88d4f3353bcb72f8b90378c9477c5c5fbbae45585f0ba9258cbe9f2314c5e55ea75adafb6c307696b32427c77c2333349ef51f0f2fc265df0ea6ce0997a78751cf04c2cbca72a9e53e24d75f1b62c709613fba5f1657df0c29058bbd1e1954270b6737a2630af6a8ff765f3b29ade240bce6ccc7825997727906f52bda902bcad4a5ed6f8c2ae7857e9fb32c05acd93f7baac35b9e2978c78575ade9721af0c20df7139d381c033b5b02673c47729bc2a365e57095e56ce0b43df54cddb2adfa5e09fccbc2b18de571a2fcf25af98125e96d10b53c0271a20de1be17d85cffa7859a917367486d3c3c788949038cb09c0c71af0a6e2785b98bc4fe07bee784f2b3c479ee524fd12f7a6d2b705c77b529fbbf29e34f820052809bd3b333c931e6b37585ea9046faa82b785c3cb03be49486b3c70bc622c78b39ef739e213cd12ef0de0dd8bef29e15d9dde570e6bab80f7b9614da684ef04f09e84788ecbab31f241466b3a35fc12025e12ac9d12f9eee6d50cf041e7d9cd1c1fb3e02c87848f4de00c67928fd5f1a26af8263aded514efeb02efcc96ef32b06683c17b127855595e17216fea87b775c7cbcae28559f2aec1ea64e09976785545af0b9157a604df85f1a260f826365e5413dfb4813715c5db7ac0194e1d1ff3e26561f226edbc291dde56182fea002f8d1fafea83d775c4ab43c733a15ed498b715f4a696785b8d9cd99cf09e06d676c2782611ac35e97ea9875775e575d9f1ae46785f57bc207853afd7f5f3a23ef0b644785724bc2f2cd6749afc121cef4e986f1a5f1510af2b917785e57d11f26aeb07357965c47c87c559cd09ef19b186d3c7c7c27877d6675ae4d56af8108c5823e49599e43b255e5409de96f8b24a78615b9c39b979c56c79517fbc3476bc364cafd4c58b52e3a51163ad0920bf34c5590ed32f912f4b8f374c094a57bcaa11bc2f18de15ec8501ad3519f34b677478bc9a93ff01e4454df04d45efa984e7b8359c143e869ee544e097387877f4f8261c25205e940edfa4c7cb53be494f7489bca7d50705f25e4356f380f7fe78b5323ea83cd309c1337539c3a9f9d894b31cf3631c7867ae7ce77a5581bcaf9dd7c9cf3be6c93ba37d97c6abcac0fb2ae14df5f0b6c878b50bfee7006e64f2a20c7969027935a10f9a39cbe13ee67af789f751bd0be59f20bdaad3ebcae15db5f1be96ded494b7f5c4590e141f637a5376bcad11bc7bfe132e5e0d00ff6380331c057c0c8d57abc00731712673c67757ce76b26712e25dd9f1c2665e1d033c13f7ca8cf92e8b577580f7c55ad3497f2991351917bedb5eed8c0f92727663c5c772d66ae4782f8ef784c37330bcacf5850df2b246786159ace1dcf1b12f5e1518afab032f4b91378c0cefc2f04f8e78531ebcadf50cc7868ffdf0ce1c7d97f4ea4cf24c38bcda201f949edd60e063e2abd25e170e2fcb016f981cd66828786f834fd911ffb3c48b0ac03719f1a22ef826a497d5c20b43e35d09f2c2566738371f6be1d526f04149bc2a155ed717ef05f81e282faa91b765f3cefcf01d25af66c407fdbc3c34bc495dbcac262f2cf59438cb41e4972e78b37d9f2e67376b7c6c82331b00bc073bcbf1e29764de15fabe0239cba4fc8fd19bdade16d1d90d191f5bbd33447c178277e78967dac0bbcaded70d2fab8d17d6b426137ef7c19a4c07dffdbcac08bc61cc7747be678d97c5c30bdbe345b1be69cbbb05789fed5d0df1be1e795510785f1ebc3c406fd2963725c3dbb2e2ddc1e19902797fc0f7bcf1ae28796125bca933ded606dec5e37d5878d7ca3f69e24dd5f0b6b6f8a4c3c42f890003315e94956f9ae36535f286a9e1cc26e8bd30afca8cd795c96ba3815722c0d9cc93effe7877be3c53045e1e17de242bce7290f858093e653bef13c48b82c0db42addd98f04a1c784f633cb7c59b6ae36d79e03ddd7c101e2f8bf5c2b4bc3b767c13cd5a93317e29899705e58589f189267c2f84b3cc84ffe1f2ae1cf0c23a50aa63191a2fef9190c75a13a25f327a75c23c530d2f4bf5c2b29ce128f2b1335e14266f6b7b5558bc2e4a7e69726676359a3ded69e6ec6c465be6949d9d3abd349bd1c89c69356bff0c0dcdcd2972bcab0ede57116b3a507ec900af8acbeb3264ad8920bf44c59b02c0dbb2e1558df0baae38cb39e16314785195bcad0d5e54966f42e44d95e0755179798078930cf0a22af0b62a7877a43c131c6f0a83b7b5c3190e161f7be213cd10ef95f0f26ce01533c26bd3c82ba167a7117e6b3abbb9e3633d6f4a85b785c41a8f1aaf180a5ed6d29b44c18b92f24d66acd578ef09f1b226786144efce0dcf04c819ce051fabf2cea4df457296d3faa59f339948be3be2eca68f8f3d795735bcaf39de5de37dc6b3817e1779572abcaf2fde1313cf817959e50b23e39d59e23ba6d70907ef180d5e2d8d0f9ab22d2eefa9fb8d3ca3e1c07b3cbc2af17551f1b20a79c34c79653ef0dd152faa002f0d17af3680ff91e45571f1ba36f0aee67861dabb23c333e5a174c3598d23eff5f1ee3cf24d1fbc2cd81bc6873305bca9df9609ef0e0ccf64c7bb33fc1325d676e63388e44d35bd2e2b6739357cac92b39c2f1f03c1994d07ef5560ed66ca2b7de04d6df1b628f0de14bd12d38b0ac1dbf25ed40ddf64c7bb0faf3df2b22079c3a46737633e3680331c047cac006b4d6cf8a51f3ecd20e0bd9d7765c80ba3e0b5f1c02b39f1a60a795d37afca8dd7153be3517ac5b8b0962df23f057853376f4bca9accf65d076b4d12f04b57bc6bc13fa9795546bcae079ce558f14bfbae72de97f9a9a692f70c795732bc2f363ed104f21e0c2fcff926cdf1f28c6f52106b362bbc67c9ab02c0ebe2f2aef27861362f8ae96d91f0a2c26fb2e24595f04d5cac3519c12fa5f1a660af2b006f8a7c5b41bc3b63bea985b395e47d845e56951716c7ab9ae37dedab12e17529f16a757c500def6acafb2ae33d691f14c7bb2efc9322de94eb756df0ee9ccf84c89bd9bc0fd28ba2f926a1b5268ffc52172feb821776f4a61e795d19bca8f29b0af0ae027961a957a5c7fbaa596b32f44b5dde1515ef6b92778702dff4ade1c0f0312cef898ae792785324785d30ace9c4f04b07784f323c57be33407cc7819765beb03fdea1f89e10d678a078c5a0ded50bef0b8d5753e2833e38c321e36355bc2c11bc49ab17c581b7f5c159ce1dbfa45acb78f89f21d6648abe2be15541795d603c19c09b32df561a6739361f43c05a132a7ea988b39d9c6732e25595e07dc5f029b3e27f9e785330bcad29d69ab8f14b4daced78f14c2178515cbc3453bc3a629e69fd9429f13f4cacd5b0dedbf26a0cf820a4339937beebe1999947de5bbdac215e18045e4d880fcae0e5a9e14d02e30cc7898f29a1b55ead900fc2f2f2ecf026b1f1a230f0b67cd67690f7a9728673c8c7ca785306785da75795c4eb7abd2cd71ba687f743bee787b52648bf84f4f2f8f026c17176fa7e1be3d5b87c10ebd588f8200dd64e817c37f39eb20f82e36591f0c2b4785391bc2e115e56172f2c694d3bfd0f1e67393d1f63e46c183e83e44dadde960cef89ca7350de1923be1bc19b6abd2e0dce6e04f9d8066f4a92d715f4eefcf04c8d9ce568f14bda8b92f9261edec7f89e14de55eb8509608da700af9809d668b2784f85f784c273e07b14df23c23b83f49d07de9d0e7c53f9ea4ce099a87ccabefc4f12afca8ff795f3ea50f24c5796a119e13d11ce72521f4bc09b92c0ebea40298db31910be1be4dd01dfd3c6d929fc8d8c57c603df59f1f280e01513e52c278e5fda795595bc2f129420f06a05f8a015ce6ec0f858eae591c02b4600cfcc44f29e9317e5c237a9f19e581f648057a3e07f045993b9f21df8ea60f24c59de999defea78513f7c5302de1d249ee902ef0ae97d31f2ee64f24d2f9cd548790f89b5265dfc1201ceb29bf7f1b2b60f789f2befa9830f72e4ece68357aac07b22e2b92eaf6ae775a5ef50341926be83f2de74f04a90bc3b987c930b6faae76de97086937e0c88f7c4c57351bc2ad6ebcaa294c4ab59f141e0ab45f2414e9ce5681f23e46529f1c2207957227861545e56015e5808de151e2face65d19f1be5aaf0e069e298797679257ccf7ae7c785f7e9ce5f4f14b3c6ba741bea37917877f12c58b12e4a5d9e3d51cf04141bca8ef9ba87877bc3c13ec5d0df2c258efa6ffc48bb52671fc921367381df8981eaf4a01ef0b83b39c093eb6c8ab4ac0fbaae04d6d795b79bc3217f82e8957dbc00761f1aa60785d69acd5dcf01e146f4a8eb795c98bc88b6ae19bd2586be2c42f0df14907895f0a81d2f92ec8f7a02fab8e378cb896cdf03f5fce72cef8a59b5735c3eb6ae33dd9f09c0b2f6acb3729f2f224bd624e7847e47b84785723786131bc3c1cbc4942673b1d3cd314ef56f13e152c43f380f7acbcac39de30e0bba3c233adf16ec36793bcfbf13d3dbcab485e58a864c799ce089e0988371580b785be2c2b2f4c8e77cd781fef55e1f0bae8787736f04d29bccbf13d349cddb43e16f4ee48f14c1c589b89df01f3ae0cff248917a5c8db3abd1a131f2480f78c97478337e986351b23de5b529ae165c9f0c2dc58a311e13d10d69ae4f04b5a9691192f9f6c2a782f029f74e0f8a5d8cb03803749857795c80beb59bb115fc912a5a6b5fdf23e326b4d2af8251b5ecd890f12e145217d13045e941f2f4d1d6b5906ef13c5598e003ef680f724c207b53ed170f11e94ce00ef0c98ef3279662693f72a586bf2c12fe9f0f2e4bc4922bc3c25bc4945bc3334dfb5f1a62a795ddf8be2e09b8e58ab99e03d2eef09cc7344af9301bc63467855e7ebf2e39511c1775ebc2c0e5e98112f4ae89bfc508ae4d52af89f415e9b03bc920d6bd923ff73c6ab01f2413ebc17f23d58ce6c7cde9bc09bfaf2b60839cb49e1631578573bef2b7d51317c931bef8d935792e36585f1c2c6bc07f23de6dacd101fd3ce788c5e319fad11ef63739683815f0ae165f5bc30a4b51a33de6be3fd84cff0785521785f2eac9d1af05d9397c5bd3028de95252f2c8577e702df34c2aba9f1412dbc5a161f34be2a0b5e5797f70600af44c7ab02e47de9bc3b7f7c13eacc868d5762bdab0dde9710ef4c97ef2e7979586f52f8a22e795b1ebca930af0be82c6702bfd4c1cba3f32605bdaa3bde97ccab8df03f15586b72fe12d1598e968f8979554f5e9797d78905ef9809ce7216f04b19acf1bc5e3120bcdbc23f21e25599f0bab4785157be298ff7e4fd36829207af8a01ef4b8397a5c20b0b634d0684ef02f0a288f8260a28bdb12643c47728ec02de54166f6b022f4fd19b04c8ab6ac0fbfa795320785d51ded4d2eba2e19dc9f21d06ce76847f32e53d7df11c15ef06f89ef34d35795b2dbc5be59f30f1aec4bc30165e96ce0b53cf6cb678a5d76a32794f9157b5afcb86339c047ccc8c970703af18f05d79795f8dbc3caa37e97b57ebfb1a644d67ca2f0df2a6c0bc2d04bc2811bcad6fada68ef7e478b3dfa7e8d52ef9202dce6ec27c0c5ca389e4bd1b5e194abe6342297d664602eff1bc2810bcadee5591e07d5179554e5e9796b32c7b1f20de1d139ec98cf7057ccf196b3c0e78c578709663815ffa606d8685ef1879531d785d25bcac335e1809ce70bcf8981467351c780f919765f3c27c784fa80ff2634de7855f22e4e5b1e14d0ae3dda5f7d9728673c6c7ae389b617d97c7d94cf8dd21efaa7c5f622815e095897d37c6bb73c533897951ab6fd2f2aaaabcae375e94246f0b83b59aa2f7b4f834c380f754efce14cfd401af87f7a4c47341bc3ca737c9672dfbe035d8de15045e18086ba73abecbd64e877c67b39671e07fe058b391e1bda457e5bd2e285e9e09bc624238c399e3635dacf1d8f18ae951faf2ae68ded70e9f684eef61f0bec5f78c2fca8c97c68b77e6efcc785760bc2f0f9ced7cf04c54acc99cf05d082f0acb3719f29e9a7cd01e6b3a1efc521e2f4ff62661f0b29a786116586b32805f3adf53081f04813755c6dbcac0594e968f7de0d5a2fccf06de93ea830079796cde2410ded424af8b7b514c7c9306d668f6bd9e57e5c8fbdae06555f226ed6bd3c72b7df0b2e078c3746b3520bc87f4a610795da8370be17f7c785356de96162f0a8e97e68cb39aa5f718795593bcaff13d95cf8db06643c62b9d5e14256f4b83f7943e97c35a139c5f9ae12c47898f29bdac1f5e1802de1514ef2b92359929be93e14581f04d4abc9aeb838a38bbc1e2634d5e271bbc633278553aaf0b7d5942bcb0475e1d063c13947767e89916f0ae38f0c2a0bc3a783c13eb5dfdf0be10b02693c4772abc360e78252cef4e1ddf34f3ae32f0c206f09e00f041077857b11756658de6c97b01789d50f08ee17955b1f735c3da8d111f3b9ddd50f1319c17e5c337fd7136b37d57c8a7d305be53bd3b8a7c5306aff6c307a5ded310cf15bd2c9e17b6e5e599e14de2626d678b6702c1bb19df539ec994f15d0eef8e1fdfb4f3aa94785db077f5c6fb6a7a5501785d43bc7bc7fb4879575bded721ef55321cd8c7de58a371f29e066b3538ef65595b2e3a7a3c9393573b3fc8e6ddc9f24c8cbca83c5e1a38de950eef2b8ff714b42c175e581aef0eec9b825e1e0dbc62c87775c5fbaae4ec66918f7d709639799f21ce7260f8d8055e1406dff4e54d9df0b68c7855eaeb0a64cde683f72c709643f3b142ce6cb8f724393bfc6d646d41f827df9a0d12ef31bd1a013e888377c7876732648d2604ef9def6a86f7f5c68bdae19bf6785994bc49bfacd89b0480775580f795c97bda7e0b5f14cf3771795955bcb04ade5db11e79c35c59dba07f22be4e4678c7842fca896fb2e4d554f81f4a5e9e9d37697b5538afcb877791781f9c351a30de5be1e541e14d5262199a9cf79eac6513f81f35d66a74defbe1ec06898fc9bcaa2e5e9725af86f44116ac6dd2fbacef8a012fece7d5bcf82013de5395e75858db2ddec7e7dd61fa261b5ed506af4b8877b3fc9331ce7232f9a5efe5a179933a7817e37b5078598ebc616e389329e43b205ed509af8b8b77d5c2fb3243a9cb9be2795b34bc9908ff63be2928ded6236fea01afebc96b93f44a4dbc2a175ed719afd6fd8febd57cf8209a97e5c40b8be4999934dea3795145dfd4c8bbfaf2be18f0b2c81786c5ab1ae27535e06c0683effc38c391e46374bc4e4e78c790b0b67dbccf0c6f8a87b735c6ab12c0eb42c20d12bc2ced855d39cba9f2314a5e9515af6b9257cbf241a8f794c5734ebcac2f5ed807deccfb1f2c6738217c4c87b59a2def65b1a673f34b6fbcaca11776807785c4fb82bd2c9f1726849204deede19f60f1ae81ef21e4dd199f498c7766e63b36de151bef2bf6da3ce095b29ccdc8f09d236f2a8cb77581b39a22defb62edb4c777a757278f67e239c301c0c766584cce6a2ef05e21efe97cee8635992cdf8db0d684e997d678b5d70719f1b2327993502f4b016f181a5e541b2f4d01d632f47f8a785799bc3017de1925be5b7a776c78a63fce72b07ccc03cbd01cf19e09673714f898f72e0dff6489b3457a1fed6515bdb044de95cdfbeae14dc1f1b6c69ced58f04c4dbc2c2a5e1825af8d14ef06266f0a8db765c99bbae16d75718633f4b1a35745c8fb5aade970f92531ef4eeb9b42784f341f54c7ab59f9108a7835473ec8cb3b23f3dd1a6b4daafc5296331c4a3e66c7a79a2bef45f1cae0f19dfab28e5ed8025e5414dfd481b38b781d0aaccd94df2de06569f1c22e39db413d13015e8d8a0ff25e14202fcd1d6fcac9dbaaf26a441fc4f3ca60e0bb25de8de37d4cf84453be17b47613c4c7b2b39ad67b80bca8325e9a2e5e993dbe1b7a5167bc345fbcd3f13d565ed60b2f4c8d351d1a7ea99077d5781f716dbbbccfbe2a0dbc2f13d6b691f7c1e15d9df0beb8786f3c78250a9cf190e015d3c2bb24dfe3c7a705e47d68785704785fafb59b1f5e49e9dde1f24c0d7877b867dae25d81f0be08f0eee4f04c067879f64dea79b5493e488a77878e6f92595b0bbc4f0fefc9e783167977a078264bde93ce07f1719671f13f517c9a19e43d9d57a68eefce7707926ff25e2d8f0fca614d6686efba77c6e8bb0ebc5a073e888b35adc9ffecf1a204f04d4bbc7bc9fba46739dec75eef26f13e39efe9f44172bc2c3ede3003786752dfdd719603c62fcd9ce55ce09704705673be47c53b23f4dd04ce96f25904de1924be537a59197893b257a79267d2e10c07e963600259cb3cf03f71bca817be698d17a5f4b6c63519f4bbf1cd30781f02bc2908bc2e9ff7e4c67366ac35f1c02f99f1a694785b8c9ce17cf9d810af6a03ef6b00afea8bd7c501250dbc255e9d469e4985339935bedbe15515e0758d795536bc2e39de9d45bee9c919ce0e1fe3a2d43adbd467c0a4d4c6bbeae17df1f1ae86ded7015e1d0d3c130faf4d162f87d08bdac0dbe24089866568f878ef856768b6f704a0c4e55dd1f0bee0787742f04d54d66a6ade1b7a59af378cd0bb50bccf4a6992f744c373529422f0ee5079a63bd67056f858faaa7c785d7cbca8d8db225f95cdebeae195e1e3bb2cef0ed13339e04d9d6f8b8d9745f3c27a78bfe1330c9c39d179c7bc3643c3770d381be2d52cf0414d9ce140e0636d9cd568f11e1aefca83f765c4bbd3c3331572e604f58ed977c9789fa0572bff4703ef585e7badd588f1de1aaf36e683c278578cbc300d5ed51baf6be9ac4688f7ba7851237c93166faa01afeb82b39b443ee6c1279999ef2c389369e4bb20de5519ef0b04af0e079ec95cbb19e1952e39c3c1e4637dd8f19e909ecbf2f208bd496fbc37f23d697c5a31ef83e565a9f1c262efea7b5f519cd990f05e25efea8cf725825727e999d0b31914be5be4ec0b7e4f78770ef04d13bca919ded615cfd0ccf15e0b6737627c4c759633e697bcf73480e7bcf766835782bd2a1d5ed71d2f8f00dea4a1772702dfb4ad6509f81f325ed4eb6dad9ea189e1bdeed542f89f06bc3b0af8269feee84581f9a609fcf1b2e278c378af8a86d7e5c65a93a05fe2e14d11f2ba6aded41a6fab03afca8ef7757a4f157c1002de95cffbfa7256d3f45e236b3465bc17e5d5faf8201e5e9e9a37e9837785c80bb360ed4c9c60f08ea9e0e579e14dbae29559fa8e8c57f3e3837a78554bbcae08bcab3b5e18cd8bfa9b70383b8dbf99f19e5c780e85b31cf2631278531b785de2dac98fef64de9d2ecff4805795c9fb9af2a688785b87bc2823be49927785c1fb3a7a53e1db427acfc0f70c72564382f714f0ca287d27c6bb633ed321af8d21af94c35956fe0f10af9310de311ebca918de56154a689cf140f28a114189e8078b57abe283be339c373eb6c58be2e3a599e35d45795f63bc33eb7792bc2c302fec012f2b7d610638cba1c02f79f0ce9cdf55e0d56e782033d6b23efee78b57d5c2eb32e37522be63bc57dbf241abb32c85ff297a5328bcad23ded5efab86356d8607e1cbab49f1419c520767376f7c8c82f784c10725e06569e04dd2de1dd73789f06a5b7cd008673c45bc6264de9425af6b7c7710f04d14ac9ddec87242f04be0cbbae185d5f19ea23c47c2bbea795f462f8f056f920bef6c7c8f0b2fca866faae3e541bd49dc7b127aceca5a93287e8988f7d4c27327bc29f06d1dbd4ec677cc777673818f85ef03f8ec8e1735c84bc3c7cbdaf2c20ef944f3c37be3cbb3c19b94c39a0c95efb8b505f33e33ef597c4ff8ae32785f482f6aca37a1f1ee30f14c95286df0a21af0b672de1d179e890e8f9037f581d785c2da0d94574a9a11e2dd897d93959787cb9be4c759ce1bbfa4f3aa26785d5bde53019eebe2e511f38a51612d87cbc7c6bca92c6fcb8ef704f441ae57857a5dea7b946511f1c25a4a547cd241fa254c5e96206f98f2550dbdae41de1da4677abdb7be278eb53de47d6a58e3c9e215b37a5733ef4b87b319007cb7c75906fe4f96339b7d8f01efcc10df81e05d85e085c1f0ee08f24dac17a5f34debabb3c8330de0cdbeff193acbf9e0638fbc5a131f14c2198f12af989a77c5bd2f275ed409dff4c5998c23df09b1d6c4915fe262ed068557eac0590e077e697b65f4f80ecbcb937a93ba1725c437c15e1508af8b00673418782f873755e56d65f1a2be7c93eb5da1f0beba7877ac3c531faf2dde1d067c53005e5406df14c4abc13ee808a538de55ea7dadef0c0edf41e04de9bc2d17de1d1d9e6990576701cf84c29916c383e0e55d2cff248c3795f4ba4c78b7e79fdcbcaa0b5e17106fea8bb715e6453df2b674de55e27d9aacf140e015f3c19bc2e16d797186b37ecc68191a34de93f26a407c10005e14d03739f1b23c786104f874b2c077a97735beaf2ade15085ed80b3e5a3e653ffccf11efaa9117b6c1cb03c39b84c5fb01be677d775678a6365eed8f0f127a516ebc3465bc27f23d647847bcaaa5f745c35a8dcc7b589699a1e4bd095e1e2c6f121eefce1cdf743abb79c0c7805e16132fac022f6ae79bb0bca804bcadec2ca7915f7a7226b3c8775ede531acf89718623c4c78858dbb39d099e69894f3a5d7e69cc9a0ef74b7cbc292f6f6b901725c23735e1edf0b2ac78611958cbc6fc8f1cafcc25df49f13af1e155c264adc91abfc4c4dacd0baf2466cd2688f794ded422af8bf55e81efd1e375b2c33b66869705c60b13f32ef49a03d66a7ade2b5a6ba2c52f1df1a6d4785b1c78750c79a6f1dd31c037adde130fcfd1f0ce7cf94ecc9ba2795b2b9ce140f1b1255e56d00b73e2acc690f7ee7879287893a4bca7279e2be255c1f1ba98ce64daf88e873735c8eba25986268af75078f7867f02c59b22c0db5ae465f5f0c2f87857e1fb9ae2dd59c03769f07ec7f7dcf0aabcbcae05bc1a0f1f745abb315f6904efe6f13e53d6b20afccf1a2f8b046f12cfabe2e07511b1766ae4bb9c3775c5db8ac0990deabdd65a36c9ffb4b1a613e59704795763bcaf31ef96ffe4cbcbdae185e921f3c49a9328ef18155e54212fcd1f6b1995fff1f2a622f0ba3858cbacfc4f10af8ae775fdf06a623e088c770a7c4f1e6f8a8ab7e57a792279c56c674e9cbc63562f2bcb0b2be45545e07d89f0a2c86fcae25d2de085f97c3a4de03bd43b17df43be2c0cbc61b8bcaa185ed71aef0e1edf64b3d604865fc2f2a6a4785b1038c389e3635cbc4e04f08ee9e0dd17fe8911af66c707d940b3c56ba3c82bf5f0b268786170bc5a063ec80a252fde25c0fbdcbc373eafe47a53e8db6ae3ac4689f7c478778a9ee99177477ca630de95015e98ea3dadcfe9f0ae7ede1798b39b087c8c3bbb897d0cca2b4382efc0785124785be18bcae19bf0783fe37ba2bca80dbe8988d74900de313e6f6a8ab7d57a53dfdb327a676ebe83634d86fc0e0425315e2d870fdaf3b2d60404bf84c6cbb3be49779ce1a4f1312cce6c68de73c0bb0af3c2c4339a31eff9f06a3fffa3c8194ecec75c589329bf0352da418e1d5ed607dea49c9767cb9bd4c7ebe483774c005e9e01bc4948ac3541e297847879727893d0785741ef8b894e91b31c327ea966cd46cb7b22786f3478a5d7190e093e06c85926fecf0fef7c7ccf0eaf0c07be9be245e1f1d2bcf1a6e8785b2150aae4d510f9202defceeb9b805e56f8c2aa38d3713d930bafce1dcfb43ac3717dcc8d331c1f3e567466a3c62bad5ed4216febd74905ef1827ef0ae77df9f0a632785b3cbc2b22ded7035e96256fd2ccbba3c4336140898c57479267b2e1d508f920f58ce78857ccccd98d161fd3517ae20ca7888f19f19e52782e7c55dfeb8ae245d9f1d2b4f19e36f8a005bc5a181f84c23234a9f77c5e273abc6364f8740ac177139ce518f24b16bc9a181f94c2ab72e27541f23a097ac708e0d5bc7cd0056b3a3dfc1222ef8e08cf74c5194e1b1fd3a29be12ce78f5f72f26ae1ff48e065fdf18649e10c07041ff3e313cd05effdbca810be89899f2bba04bc1a07ffa38057d3c0075df1f28c79c5407935127cd0192f8bcb0b43e455d1bcae1dce70a4f81813efb25075bc346cbc27189e53e1cc068e5772f26a3d7c90cc33323cdf61f06a461f44c1bb93c4334df2a69ebcad1e3e6544ff438077c5789f105ed603de303abc5ad0ff3ce00c67cbc7bc9cd978ef5d40c9e8dda17f22c68bbae2a569e2dda1e29992d6321ffe878817d581b705c27bc2e2b9265e4dfda09c5755c8fbe279d7fc275bbc2b395e58f6aebaf7f5c4ab02f3ba1c797789f7d1393b86e579f226d1b09609f23f617c3a317d47c1bbe83fe9624de7fba53eaed141e39794de95f8bea8785547af6b9157e68fefb69cc910f25d97b32633bf64c2cb83c92b66857731f827a99775c20bf3e25da9de1796b39c223e368277f5811726652dbbe17f8478797ede241dd66c90de2bc1d9cdeb63dbfb1fdf23f4aaca785d98bcaaf0754db1d644fda522cf8957e7906722df9d129ea98c77e78e6faa7979c2bc62b8d78603afb4c45a0d15ef99f1b240f026a5ce7228f898025ed50baf0b8db59cf26391bc4e78de31aa577bc00781795184bc347dbc2bf23d057859585e18216e8ce0dd71e29934f0eeecf04c84bcbbc23ff1f2ee44f24de2bb23e69ba6bca845de96ccebc48377cccfcb92e40df3c3590d13ef8df1a272be297db78cf7e1ce7462cf84b4d6e400bf54c5bb29fc932eef2988e7b6bca82adf24c7bb33816feaded3cf0731f2dab45e49cb9b42c0eb6af2aec8f765c5abc9f14134bc9acfff20f2aa3cf0be567877f8f8a626af8a7c5d56bc2933de96065e56082f6c8957f5e57531f2b272786177bcaa0e5e5711afd6f33f85784cace998bf9480576be3836038db85cf26f0a6b8b755f4aa72785d75bc3ce49b04e6dd69faa61b5e14956f6a632dcb799f2f2feae79b8a7879747893d4786da27837c6bcacee85457146a381f774785317bcad2baf0a7d5d7fbc2c43de302ebc2aa6f755e54c07f64c2fbc3bdb3369f1ae56785f61bca910bcae16ce72daf8a5262f4b8c1786c9bba37a2624decde19f48719693c62fe1bc3650bc1b5d5e9e18de242dd66a8cdedbe25dddbc2fa1f73cbe278737e5c6dbfac07be2e08318f0a6b2b775c27beae23929d69ad0f14b4fac2d24efc3c39b22e16d81596b62c42f05f1ae4af0c26638cb56ef23c45a13347e69895705be2e29ce6c6cde7be47d8eefa9f2f25cf026c1f0ae34f0c24e58fb83df44ded30dcfbdf09e9c788e88339b2f5e295bbb89e195c6bc27f3391b3ed12cf2de0caf2ae87531f1a2a07cd318af8c1ddfa1af4e24cf64e58ce7cb2b265bbb89f24a1e7807e37b06f0ca48f21d12ef3fbc36e65dddf0beea7835373e88cacb8ae185b5f1aa8a785d8fbc2920de961faf93ee1d03f46a28fccf24673601788f02efaacafb8ae35555f0ba8ade91f89d9c978784378988351cf0634267391ff8a5ee4d5de0756daf0cd37705785351de1613efce08cf44c67b7ae1b914dec37c4f1f6f4a84b7a5e5dda9f24c7cbcab28de57989785f3c2ceb39d9a6722e24dedf0b6c4586bb2c22fa5afeae675099ddd9cf1319e571be2837cd664ac7ce7bd3a2078262dafca83d765c4ab33c83379efca82f7d5e50c67fb180f2f6b8a178681f7a1e5f1de2420d66e80582d900f32cf9a64bf44c2ab638167a2e17592c13bc682339e215e31a7f7047ccf189f681c79afcaeb4483770c066f0a03afab5be341c02b6683b526e32ff9f06a517cd0f66a02f81f47d66cc27825ed75f2bd63b65715c4eb6ae4d5ba7cd004af4c06be73e25d85cff6789d9caf1208de99d3776abc1a121fd4c1ab75f14125acd190e03df40cc7e86342bc32747c670625b7aee42aa6e6469a2769aedb90c875517228415515c70f244ff503bd758b6984454522ca22949a5d3ace48452291b8acde8843097e64879e271aa229f8759c3a24725fe8450889cb2ade509a20488a27ea799d788e1ce90d899c16541d42854e1bca9443c1511dbb543cc1133dbd70851aca740dc98e33bfad0bd3f414cd21914bb308a95dc02081214a0108080c916925f4a85c112aad081165c1a29ea92a25ba9a9b187e694aaa1b386ee090c8a9285191d0962d264aaa28510c4451c2f1a84b914743c985e0b8ad26f78de6d8a5a33743a99ee996926ad771ab9786a1178232946b787edb08aedc67aa28ba995e1743b985aba87ae6b89adc06aa9ed9711c95121451d223c3113c3b72fc488e1c612853f23ccd55fd5031fd42d04c8b4678a13c49d22349731c456e04b9f14b8b5c28bfd524bd2e25bb101db9cefc386e6ba13cc50ff440543d37f14cd3ee0cc129e5867e2aaaa21f4a7edbb692e090c8791d0be59686263aa29b0a7a1d98a660c77152ca5505c593f4b66febba71dbc22191034388529aa7098a1d7a9e2249a626971aa565a9aa6af78da9697e1e888aa6372472aa1720b21079113acab2912b9469377aa6e89d1f9a762027a64322b7a50b972c42a96a640e1d8d9c08a5443d7224b933f4c0d154b76e2d42ea3a125428cd755bc32e55c5711bc771352a3acd0d5228c3b31b37b4fb5491e4ce8e0b878e442e44a1fc386e4d3ff34c410f0557721c123995a8680b2a44a47e5185b8141975e13a90ca15a1114e28cd6d55d52ffc4455445195533b14065086aa487a5d878e21e86d691a0e895c97a2a22d421ff77147485eb07c1d47e58a500b2694223aa6e2c67d2848a2de28a24322379a369c2a7c5c48e58a90584299a2deb66e6bb88a9bca9dde39247244ea96944815fa3a5045b36c39025520a11455cf03d7755dcf14edc0d41c1239242f5d88b2803f56a40c1d992c00018560670e1d01011da9273752b922d465843244bb313dc74e3dcd900cbd6f5d8b8490b26ce97244be4096e86672e17aa2e3b7a5e6a79e53c6f20c43f14b430e44c9d323d7cde3c29043d52fde14b1544d6ff3b8aeeb4252243772050222432a5784ceb01457350dc794044fd2ebb8300491b8ac1e5882e2b9a9e7477a1c776ee4c70d89dcd091972e4246456bc8bdf09526788ee329a6a1687ea7e77d432267640ea11b27e49524b971dcf9999b7a865fe86d432297129942424529d7a95f8cd423d548fcb122e527851f283f567eacbcf083aa360461d240e6d095e027821eba912918a6dbf6a943226784742444aa48598c6ea8020404348254ae08f1c0959bf779a1da8123988a1d496e58bc9cea916964547a20952b42346c65da6d62f891a94a8a26679ee090c8ad425d4ca32088bc785cd805959d07a6eab871aa1882dfb7894322a72265310a02e400a8e4b8ae03b70f35bd114d43551c1239a18e4332b91815ad211297d50b45283f8e1b3b3414d30ddd3ef53353a22c4110792922121ab9114228d5500c3755dcc271dcc20e4cbd1f2b527e3acf8b51d10a0484c46505fad93a2f3f5bba70c9f263e5851fa10eecc2258bc7c50b9187c465f54e0194a8baa627b76ddd778626789e432287c46515c24294450b9a85c8ec42aa4645e847e58ad00642d98d2a796e1c48926ab7aa9d382472422287aa5f8482308d4490ca15a1f1032e4545425b522321211b0f8c848c8a8c8c54a1232f4642421574c009156dc152b485020e9e04e0c713da9245cbb641071a78e0e3c1931032e80003107ab60b8490b678315735fdc0820e2a38a280820a00a00535cf2d5b8a8c4098c0548d2a70a26a1142124225c2808753b150c03a423aca22847ac18acbc2854b9107aa2ecbaa1a75d12065018a2352bb74312fd8e102a0a30e91e548c86bd2191de57060d62c46ea17a10d7052f5e848483512328f525415e2b48460d705558f848a90ba803142c59b0ea9a8c84828558dd4d4a84828043b358b1112083642a84317d4ac485e846838f4280b170d66beb0472604bbd3136dec3821218a824012c2429465e8c81cc220bb603921212e42464548265ab4658b298424848ac12a34544b48b22a1793e8a462e28b1e706e1522480a6723f84c049f85e033107c96c967987c36e633319f7de0330f7c96f459073ee3c067977c66c9671bf84c039f65e0b34a3ea3e46c92cf5eaeac109c5b83c4523885d50d4aaf6e08e4d50d60a264f01390ab1bce57375cf0ca06495ed960c82b1bc0786583d12b1baabcb2a1845736f0bcaa8102af6a10a4a6070f5ef560c1ab1e70e65981f0aa0799573c90e0150f1d78c58324af7870bde221014d7256515c78850590575154a8c9e0cba80cba8cca408b0c2b7587cd293b3c90b2039253762c6205d3292b924e59318153562ce09415789cb2628d53563c71ca8a2da7acb0615426329cb242ca292bbe53563c396505eb9415a7535574e0541514f851a826164cc042a550990c312a13a4519960411df1e5d411eba9237048ad36a754a6536a25a7d4d629d591532a21a7543e4ea9769c52d798b14971c0c8290efe38c5411ca7383063141f14a438a0e21407419ce2403dc58195531c4039c5c1778a839f7f07c58525a3b888e495160f4671f11ac50523a3b8386414170618c5c51e3f14a8231a19758401461d71c6a823b818754413a38e301a75440da38e2061d41121fc39165480031432a83eae8cea0386517d9c30aa0f6e541f198ceac3c9a83e7246f13182517c50328a0fd8283e16308a0f3f46f131c7283ed618c5c717a3f8a862141f4c8ce2c388573ccc318a8fa3517c6c19c5070fa3f89061141f5246f131c2283e4418c587cf283e2818c547ce9fd2b16135b9a1d9b1c9690280099cf0ac54a9511b40416de0649406b1511a8860940697bcd2c2334a83088cd2a091511a18324a8339466930c6280daa18a58112a334106294065a4669d0c3280d6418a5810ba334f04669e0334a030a4669a0334a036d47667593d20100ce9526af7208c1ab1c2cf0a356399caf72105fe5b0af7098c02b1c0a7985c319af7078e2150e32bcc221e8150e15fc3e610514f8eccca07c8c78f54239ca2788513e44a37c5419145edb4ed98071caa6895336419cb2d1326a83ef948d0fa76ca69cb229e1940d08a76c263865a383d384677ceff8d77bb7f885f26352453b699996d1e0f8703e1c0dcff02e0378568abc3a4496232f4f8ecc2738a400c30823a8436439caf2c484129ea84364391a813811d421b21419a943141911d5901279298a9212d5a06e394d284845548e5475ad2155b76431a340196184a32c5d4c212ea810e7c1a3cae23f32024ab764f1721e994f908a889e0c219932fca30a553cf0425fa837d050812fb0b34a17407488828a8d8525842c60f81afaf0e23d961a2224de4acd0f31c48349e2cf2a280d1511b068e1a9d8a035fcd9c517b1de0af9387ce0a980c308159e869e57ef10a2e26980e1d5301e089f5f9f3c9535a298e473a8e0a95051a591d7e1863f5dd06323e26f818615a054a9c2c20d346c3c1beb2b1ee070830d3558a942c30c32c4400586175c68610a0b52a2942b4051811cc510fcbc8edb36340259219452f4336810fdcc082110082a905156602184131edfc60c7f92e34f427faa218b16523835e04f769c70f8930d6d44f1272a2717fec4c229ca9fca3fad7052e14f29fc09853f9d702ae14f249cc653f827f04fdfa9fb13f727ad02af51402b24913f34c81641e4b5430a21e4b5413403bc16c81f7deca101cd6b70686ebcd6c61aafa9818619af95f15a015e23e3b5315e13030c2f5eebe2352eb4c8c45f0e4a0ca50aab0b5252d848307823b9f935e41701bf857c06c667697c86c667577c08407c06851c583e6b03898da4e6b321b2203efbb291cc7c66c51b89cc6744ef86156f24a7cfb6309191918df1990f9f9d9fa59fc1f1590f3d59646bc8b17ea6461646f6480018f9009cf13e5bf8641180ec9fc0f1b3f1b7a1c69fc62606265b2c0393fc06071b6828fdb61f38f11f34f11ff14f840f3de880c2004a000a81830d7c5668610a0b52a2942b4051218511c8510cc1cfebb82d084884100400c2071e74d0420b2db030e3f33334d030c30c32c410031518fe272eb880c23fff0e252c8037f019018887b79ddf747e6bf25bce86f3dbcd6fa9df50bff1fcc69a08a17fb2fa27db04bf392161cb7edb3f8446a4fc24849ffce427a027856421e8674a0f3a58a103155668214a0a21a850b6d0420b535a9832654a39254a59b610c20a2ba890420be594164411c429a508650825500825082efc4c07a2148ee3b8effbbe30044120a01042104184eff3bc11462049519c4161068519147e464ab9c2f7799e00cad28a942e5280429022454a092442082d4c8912254a942851ca29e50a53a2941d17a5ecb82865c745e9b8196488810a0c33fccc0b335dbacc74f999ed675a6801a88516a4489122254a942851a24499d2c294124884105a9852aef03350a04081024505155450e1675a985202a590c2082390e428863f53823f5302793f538a1042598ad0c294520020fc4c0b25d0cf78e081073fd3c2945284101a99e28045125184055707531ca0800430b248228ad0f100425c0608040f3dec80230b30a0b0828a2a9e70620926da58030c018640e20822668078190a0840032f5301482020738197e172c4cb685165bebc0c1252962caf72d02087f86e74f05be59783286e14fde9853fb5808523473104c9510cc1cf5329214731043faf9bd380e7fcb2910c5d6428ca720a8165cb16241888c0a4455834e0495115b89870fc5a60d032174d10a22e30d860830d361c698274465260d0d269c0e3791e97aee382ce85cb962d9a2056a4c0a085052d588e12658b14185cd032174d10a22e3154f0423b5b0cf0ab4e0e1dac22f470f4d7e07536189da7600e7d1e398a21f8795d00e902481740ba00d205902e807401a40b205d00f9374cc2c42eb9248c4b2491c4124c22c900269734c0026b74c002314c20592416014422c0c82011b063036e1ca246181bd0639134381006220d706401978401c8227ec4f8b0830f3d2e01048f981d6b5ce2814b24a163031d885d420725bfd1c1081d7260128625721c22c71a61e2c024160bc38e37e040c01a98ac114b03930d8ddf5c69c4b60658a1e2a960408c077eebc06f1cf8ed92df2cf96d03bf69e0b74a7ec3c06f17f84d92dfc2fc16c96f16f80d92df22f09bab01bf39f21b037e6be437467e5bc06f8b207280df0601840f3c7eb3e3373ae2f8ed8ddfdaf86d8ddfd2f857b04165014b0d961a2c35586ab0d460a9c15283a5064bcd468e62087edeb7917b5e0e1750a1c8974974f8e2ab90f05530791e2ad182fe0c618021f35b34f055bafc0c203c1659bc880f26024fc31e54a061a509161b95922c1ed9f261a9a1c203158d0a174438a92c293f6c6432f0f33ccfebbaaeebbaae23e28a1e8af81a62c024094b624881e152c50a158ac4b05f26d161b55204954968d8030c0f54a06104c64a133003c0623304824ac90d1964f108160fb67c5db460a9a99202151e66d042459b810b152e6c80c4860a2aabe4c8c1c951a5503b3a3938363433325a764a954aa550a99d944e2a278593b249d1a46652327b70c3f6397cf054f6a950e0a57b2a384f4584a772f33640f2546c9e0a1529343e55c6a7b8f894169fcae253507caa894f31f1a91c3e85c3a76ef8d4984f89f9d40b9f92f229143e157eaafb54d0a7803e25c2a742f894003ef5c1a73af8d4069fd2e0534f3e85c1a77a3e15c9a72af0a90b3e25814fc13e05814f39f2a9463eb5c8a714f914cfa7589ff2e1535e3e85f4a9a34f197d0a884f75f954d1a7883ec5e5535b3ea5e5533f7c2acba7863e85e553eba7d44fa59f423f757ecafc94ea53a94fa13eb5f3299d4f35f954cea7703e75f3299b4ff97caae653349f9af954059fe2e3537b7c4a8f4fe5f1293c3e75c7a7643e75fa94f6293a3e35c7a7b24fc5f1a97d5b448adfa0f84d8c2d8c2ae5575961539971c69b41861934bc192d3c7933566f86ce97b154c6235f0603be8c432e838d32bc3c1994fc18577e8c127e8ceec708fa3102f06244f26244e0c578e3c398e3c308e3c3f0e2c188e4c180e4c178e3c1d8ff0293ffa203ff0523ff4596ffe2fc2f7af82f66782f34f05e00f25ef4f15e5cf05d20f15d04f1593c9245229fc594c7228bbf828f2bc6b0428eaf428fafa2cb57913e1505782a84a022ca5321be147f140f88e28887028502857742cb3b01c32b21c42b61f44a10bd1219bc1216bc12399f44ec93c0e493b0e49398e49188e29120c023e13d12aa4722e791580248f10498e109107e1123f8228e7e0812fc108cfc10480f869007f3c78319e183c0e483b8c00701c97f49fa2f07f82f75fc1723fecb97f7a2c57b99e2bd6cefc583f792c17b01c02355f24814f8a328fe8889379ae38d7478a31cde48ca1b9df04624bc51f846207c172dbfe5fb2dacdfb2f35a20792dadd7c2c86bd143cbf95a9e3c160f1e4bcdaf1f78959257c3bc0ac8ab6bbcfac5ab4fbcfabd1af4a70b7f867fd6bcd0212f74f442e60bc9f042ab17c2b9874cbe07027c0f437c0f46cf03d3f320e679a0e47980e47700e27720fa1dc61d765e8711bc0e48bc0e32e4d0c1e7c0f339dc7c0ed9e370c9df90c1db00fb2a937c9545be4a1f5f4589af92e5abecf05552f82ae357a9e0abf03c0d2400c1cf10e66790e367f0e26750e267d0f233a83fc3094f2590a772c653a1e2a934f15472782a273c95f0a978608619af85012f82f9c308432cc45f8e80b8682cabe7151c6ca8b2420a43801122882f5e908e8c80e85244c4658b961fb20c6159d5143d3d204731043faf030202020202020202ea388ee3b88e1cc510fc3ef080dbc8510cc1396e0e4803016ce9162184f892e54b96155e9882c39429367022387221f7795cf764058e143548bd76c66b5b1c4506028f5c144555e18a952a34146d99b21dfc432c60b9c34b117ac7d12652172e5ab21c8acc3922135e03d5f88bdf93137c3499ffc93ee093ee7fe23d41b16a582aec2aacfe271921bf99699161f10fd173c1ca21441733bea789cfb87c5680ef21e2b31fb2a1cfd4cfccd7cf76f83ff0590d190d7f3201017b51648fd7f4e809c0ffa5a767a49e09bec7c96756d685b5fc587238edc93e9be133197eb38669d0808a0b2cac15bfc289a0280004ceeba011c6ebc8f13a79bc0e035ea77b1d1814f919242419255e467d4d0d305e33e43504bc16c86b85bc96c86b356cfcf15a1eafad56665d7c94a1afe1684f9e80c99200f30f432b1051a1b2e58517d25798e83341b6fcc2599ef2e4c90a456b1d6882fca47bf2e4559df1ba40f0a2b86f8ae24de5f0b6be78b5463ee8e8bd11c02b59e04c868befac9ce16ce06378bc2a99d775e5acc601eff9f1ae74de177a5633f4de156f8a8db795f4da5ce095c09ccd68f01d20af4c07bea362cd2687f7c49cddb4f13100bc3a1f7826f5558d795f52deedf81e1bce70663ec6c26b53c92b19f11ec9f7f0f1aec4785f625e8de983d838c3b1e2634ebca7259e13e25d61e08591f02e1aef03bea8d337edf09eb478ee8957c3e283c4b39c397e097556b3c87b7a9cd5acef59f1490692ef8c585b04a912ded610af0e24cf14c3bb8ac00b0b7a7792f7f161cd090def9828ef6ae985597953446f6b8f57c5c8fb7a729641f99f2eef4e0dcfe4074d1e6b35e17b605e96f8c2ac78652af9ae895715e5758df1b212f086a1b2d6048c5f42e25d27ff447bb514fe0703cfd408f29e1def0cce7771bc33477c478297c5c50bebc0ebc47bc704bd3c94bc62c237a5c5db82e45d71f0be88586bc2c82f6df1f2b4f02655f1ea50e0996478518cbcad9ab59939deb3795561de17f8aa88785d0e58e3e1e215c3fa9439f13f4dbc2728cf8def547c0ff7ea08f24cdd9b92795b2a9cd598f05e11af4a90f7957a79ba37a9cbabfac0fb2ad76ab8782f8d7775c90b53e16589f0c2987835d6fff8f1ae42785f499c1dc2af22ef4ed03365f1f2686f5206af6a8bd7a581b31c427e8982339c0a7c4c8e7795c2fbf2622d73e37fb47859217893542f2a8d97268cb31a38de83e3dd39e199d0787744f04d31bca90fde1696373580b755c47bbaf25c0cefce03be0984776700cf74c6cb53c92b465c73f2c23b66855735e57595f1f2886fd2975795c1eb427a67d0ef2cf0a2a27c5319af76c2ff5ce0e5c1e14d1ae34d51e07581f0eeecf14d3967361bbc07c98b1afaa643d67446f8a53fce6a48788f88b31b4b3e36c2eb44e8551af33a79f28eb9e05d75e085adf0ee0cf24d3c2f4be885f5f1aa34785d41bc2b17ded719af06e6839ebc29d6db9ae13db5f15c19ef3af13ea9b31b283e66f36640ff73be289f6f02f3ee08f14c1378513ddfe4e50c07848f5d39bb51e263333469bc9a201f842a71f1aa6e785d73bc2b2bde17256732587c47c3eb848757e9034a55acd92cf15ed3cbd2e185e57176e3c4c76a5e193fbed3b296adf13f59bc1af73fad359c2a1fcbb2c6c3c72b268357d3e183322535de999cefe478353e3e68873785c7db2ac1ab29e083babcaa3ede17ce1acf1faf9827ef4d7e405e56102fcc016fea85b725c5cbe3e44d225f1505af8be82c67f5b144d67450bf14c79bc2e26d4960adc91dbf04c5eb847bc788f0cee4f05deb8ce689f74e7859237893582f4bca0b2be35565f1ba2a399349e33b1dd678c478c538517af2a256f826325eed8b0f1ac0a7d398ef9cac6595fc8f1b6b3456bc97c29acefe121b6738437cac881735c43741b2a643f34b6e7c9a29c17b16bca8355e9a31de0df99e2cafce98671a7abfc0f7fcf11ec6f79c709623c4c742f0ca60f29d16ef8e1cdfa49ded04e099a4784f393c47e5d58af8a00dce721cf04b1a9ce148e06370bca97d5b0378b7c0f7ecf1b2d41726c8bbdac00b43e1dd81e39b76ad09eb97acbc27017cd0033ed110f21e95d74907ef980dce6e0af958005e9e13dea4245e9b31af14c5cb2382570c0baf4a02ef0be83d59f1dc126b4e7078c7b0f0a68c785b899ce5e4f14babd72602af54f4f278e01583c2ab69f141e45a0682ff99e3555df2be50785189bc2ded0c026b392c7c2cccbb42bd2ff55326c5ff38f1f258f28a21e1acc683f78e5e15d1eb3ae4d528f0414baccd30bd77c1bb58bc4fcfab61f9209d351d27bf34c7aba2e27549f2aa0c795f137cda12fec90d2f4ff8267979b5301ff4c4abbdf14131bc3bdd3371f1ce24f15d6ced54c877352f8f0c6fd216af8d23afa4be361d782526ce5004f8a649de13fa1c0e6738607c6c8af784f34179bcab23de976b6d6d3e830efc487176e3808f89f0eea8cfa4c8ab63c733a9ce9c9c5e3131bc279e0f12e4d52cf81f4268b438e3417ac5ec9b6ae26d39f2b22e799368d66c54efb95ed522efabe74529e06dcdbc5a023e68cbbb63f44cadf774c673602c33f3c57ba757d581f7a5c2ab7dff037b978af7a1e0e5a9e04d9ab27683f4b17d55525e1719af66c38b50b486d3c2c7d6351c3d3e06c6194e241f9be35d11f2c278de8de27d78d69a70e097ca7851e93709f2a60c795d3aaf96c1ff18729691ffa3e545a1f1d280f16a967c5016efcc0edfc1d69ae8f14b517c9a51c07ba99747cb9bc4c78b5abf899097a7ca9be4c59a8c0adf89f02e1def83c2ab82e27555e0e5c1799342f051dfaff89eef4d65e07581ef0a8df73582b31c293ed6f4ae1e79611ebca93cde96d2bb53c5337960cd6688f7626f0ae96d19e05d15fe09102f4f93370948c98757c5c3ebcae35d2bdec782331c463e96c6bb63c83755b0d604885f3a7a556abcae11bcac2b2f0c8fb39910be23e4d51af0415fce7278f89825ef4e0ccf84c7cbc3c29b34c59a0e05bf44c78b22fa2646ded4136fcb016bdacdffe8f1a6a8bc2d2ade9396e77cf08278f7827f6273b61efc13ef0c67e96306785522785f2fbc3be93325f2aebe785f49ef6a8bf765c9fb24df03c8cba2e30d13bea7189e5b618d6701af180e9ea1c9e1bdefdd89e2992e79550f785f1dbcaaf17555b1b60d789f1cce7234f04b402f2a856f0ae34d69795b78acd9d8f0de075e96052fcce8e59179933438dba1e09998786d0879a51a5e16015e58045e8dfc1f0abc2b82f7c9f2a604795d326738a78f4979773ef04d2c9ce1fcb1f2658d799374d666ec78efe665f9f18641e1d3e901dfe9bccbc4fbecbc3b243c5301ce789878c5d8acad1eef23c3bb43c83739596b52c62f29b17692e3bb7d530a785d4ede93071fd480f764c6735f285539ab497a2f01ef09840f7ae4e581f22635f16a747c909577a7906f02c0bbf2de17146fca87b765c6598e091f83e4d5eef8a01bdee5d913d173e8abb1f1412fac69a8ffe1e3bd8eefa961991927b4264bfc129857c692efa27851327cd31bafca7c5d7dbc270a3ea8901795c54b03c5d98d141fbb39bb09c1c750785759bc2f0d9cedf43c93132faacb3731e06ca39ff9725653c37b4fbca8ee9ba438bbe1e2633b9f686a78cf7b6542f05d172fcf085e3153d66af0786f8ef734c373515e1e0fdea4f3b541e4957438cbc9e1631a785700785f5cde94cfdb125282e3134d1befb1f0b298dea40ad69a80bf24f4b2d678614b6b38247ccc7c9d80ef18ee55fdbc2e306fd6fdcffaa254dff4c3594e25bf24c2cbb3bd4944af76c40705e07d8def69e19d417d67c76b83c72bfdbcda131f047426e3c57735bcda231f24c4da0d0daf84c9eb44f58ec9de93cc07cd718673e5635bce70ce8f75797988dea43f5ecd84ff09f36a18fc0f025e569417568017b57d13142feb8a17a68177e1789f12d66ccef732794f167c90216b4d2af925325e96961766c87b32e1b9ee5d59795f729ce93cf24c53de14075e1709af8e049e098637f5f3b6ced789fa2a8de04565e06dfdac658efc8f196b4d847e89cbbb35fc13275e8d031fb4c5ab63c93309bd1a0e0f84c959ce037e698317b5f4b64638c381e46371bcdbc13f097a35453e88e82ca7838f39e0ace688f7c0784f2c3c67c2cb4a7a936e5e5411df14e62c87898fc5e4e8e1d544f81f07bc97f13d2bac3551fa2535de9d3cbee9e64d8df0b684785575bc2fed0c87838fe1f0cadcf15dfaaa2ef0bec457a701cf24e5655de00da3e5e519e14d1ae26c8689ef9ad678e678c55cf0b26a786173ac3591f24bea5a930afc52183f55acd160794f543ae2dde9e39b745e9e2e6f5220afe6c1ff30f26e947f12c45a930dfc5201ce6ef2f81806ef847c8ffaae007961a8b31c4b7e29e8dd219f698c5715c7eb6a7a353c3e088777af789f0bce6c4c782f036753d6f6e79f70f06a4ffee7909735c81b26caeb448477cc07ef4e0bcf34c78bc2e2a579e24d89795d297c9a79e3bd9af774c273dfbb627a61359cddc4f1b10ace6ebcf818ea454de06d4df0aefc78613aefaac9fb3adf151f2f2ce7134dcd7b19bc3bde3375f1aa76785d78bca7289e3be25d5d795f78bc3b579e499077d7f7c8b1ab8677a6e83b4bded4066fcb5cdb49e3994aa014c499939a578c96d709eb1d937a2f3ff363ad668ff7e8785915bc3020d69aa4f04be83bb3df9df1f25ce015e3bda80ebe89002f0a92b70580f72abea75bd3c1e1970c39bb669dacaf12095e15d0eb5ae23d1df1dcd18b12bfe98a7759f82760ce6660efb15e1409dfb4c59a8c08df71f0aa60ef2b841705c53771e05d2df1be26b0a693c12fd9f16a5c7c1009af9312de31e31a4f1eaf180cde05c03f91599359bf235f270078c7b05e5515afeb02de943785f3b65858db9c93cccd77179ce154f0311ade14ea6dc1f0ce107db7819727ca9b04c5bb227a5f0938dbe17926255e0d8c0f3ae1eca6041f53e11d8def91f2b2665e180fefdaf04f9e7857dbfb6a624d66cb7724acdda4af4482f7f4c10739e06c2783677ae22c47905f02c0d9cd073e66c28b9ae46d3d79772ef926282f0b8817b6c8d98ecf3341f1b24c78615cace904e097f058cb7e5e0311ded4186f0b034a56de15242fac3bcb71e16392acd180f0de076b38327c6ce8ddca6a4b7c1008673417780f871775f44d0f7859e70b03e4aca691f7f8785766bcaf10bc2b21de57035ed47e530e2f8fcf9b64e555b5ded7072f4a836f1aa2b3e16595e04d72f2ae80ef31e36565f0c2bebc3b957cd3003e65ebff187176b3c7c73238cb49e4977a5e8d8c0f82f2a6ee785b24785397bc2ef26ccccbaa7961422f6ae69b7af092785978bc61467835f3836a5e94256fab83f734c57348bc6fe07b0ef0ea54e099667853e2dbf2f2a292de9600de551aef8b04af06810f1ae2ac0603ef21e04d8d6febcb9b92f2b69a78b500fccf226bd995ff017356c3c17b46af06c50705bd3698bc92142fcb8717e6c7da49fc4e106736da7b8ebc2b9ef7f5c3a79904bc877a53366f2bca598e081f83c0598ef8b108bcdad207adb17652c077386b4d0cf9252b7a7078752e79261f5e151def2b3ba3f1e03d0fd69a0cf14b5f5e16162f6c03671909ffb3654dc6cb770338cbe1e2974eef8e04be897b794c78938e789da4af92085ead920faae245cdf1d2a8f14947885fcae4cc46e73d08bc37e07bd8f8a1e2bdf995be7897e27b80ce70943e16c8cbca5e580e6b19ec7fd2786fc057d2c0ab25f1411ebcab455e58066f2a8db775c92b0382efb8389371c07760ce6e46f0b114d668f278cf85339cec63515e96062f0cccdab6f13e24bc290bde560e2f2a93b7c5bd2b385ed82f6afc262c5e94936f227a6f685ea98cf764f41c967785c90b6be14da9de560c673b3acf74c459cd04de13644d8685ef82ce7266f818065e55f6ba6e7853ebdb0a859c5ea5f9a1dec671aa478edcd79da0ab0cc371ecd25534531154d3b36b9529a782a1178a68ea8164ca8d1028c9ed4c3bd253b75315cd501d420ce2f48052edc035f5448f14b71f2b527078a4dc5474edb83555512eedb88d7440499ae0379e28b78a1bf8a56303ca4d5dc37125432e15d713ed3e474a4f35d76d25570f05d353dd440694abd7995c1882e2f885694a828d9428b87961e76da3e88168778a4322a76e318f368f88132325b8925df7ad23a98aa2b9a9e4024a711bc72ee440ee5cb7cff34e05949b798ee167a6dbf7a52488868b949f897aa4a7aa5fc879dd866e318f84380e3cd2826641da38454a311dcdb453550e3d49534cd3444af4e340af1357d3233f9053c50494e06976a7ba81604aae26ea8d8894aa4aaee3e799a1876e2a7a1e52ae20fa7de0f87969ea81608a1a526edeb8815fb876def789a0b922a0fc56113dc3ce23bf6d153d2f2d045d41d50c378eecc414cb01cad54bd52ee444f43b4d2e44b91c4e8494297776e3e88d626a7e9faaaa83941ed9715e28a2aaa78622187e4322a71a79e9d2719d2025097ae2a986e1e87d21b8a1ab014aee5bd57504d1ee4c53533dd340caeddc4e2edc3a153d3912153b404a4ee4cef54cbbef34431344c13fcaf4fbba50244f2f054d9354cd931f25f875e32a7e1bb989e0b816090d1d992c7c7d94a9f8ade1297eeab6a2dee681249cf8284591fcce4e454550dd42d33cf72853f35b3755ed4cf153532e558bd211cb498f921bbfcfe356efdbb6955347f39447098ea6f77d29b88a9bbaaa5f3724721bf7c3098f1224c56d1dd14e34c57335d1b573e107ca0f47b271baa314d7cefc3c6f25d7d3fcb61014b79cec28bdf4e4c2d0db423424bfb44b8744cec84815ea6272114f7594dc689aa4b7a5a6ca89a0d8c5e452fe40f9b1429ee828430e0449955b450e4445b343436e8ed254bf0d1dc16f43c1aef3bcef0341394af0ecb8d5e4d6531d55d04bd70e8f531c65789a5c4a825f3876e67786e357431112d1142a57846438c15182e7c69126387aaaba79e9ca9dde28d18efc3cd5144532e44413698b17a42d5ea0fc0001010101010101adea0a404046eaaaa69bf8692438b951821e678a5eb872231772e906aa716aa35cc5b45bbf74edb66e0d4d8e3cb151a29d897a9b677e68ca81a60a760638ad519aabdaa9a0c8a966ea99e6b99dd4283f7205d7331cd3152451b3eb845091bc70111ab92e8d32e4428f44d7ad1bbf734c49f0088d525cd1d304d76e4b5772544f67946717721f0aaea7677ee1767243228766d97204047432a31cbf941cbd0fecbacecbe3544639aadbb88ea0b99a274a6ea37a2a406976a36a86dfb89a670a8a2b2a8e604e6494dc9676a3b98adfb68d9db7a6a7314ad1dcd03434d1ee0cc790dcd621910ba1c000e5e7869318e5b99123a97aa10772e0770aa31c378e03d38e1c43ee43cd95ebe20446d9ad9fb78adcb7aaa6b882e2f645999adde98d1b697e66aa7e1d3a24729ea8e5e445a99add0aaa63e871e82a7aa8ba7e5e17e5479e28887660b7ae27d979dc911b272e4a511d37b34b470e3441120dd1d31625b77ea0297aeb686e5e689aa778d2a2143fd553377514552e1cb72d5481ca15a1214e5994a979ae9c4a7aa0979addd78ddfd091c9029289121541f942218a82b002043474640e8d5894ab088a26e871a448aeabb9911f3780d315a59a9e2837a69f4886ab9886db90c811097546272b4a114dd3f15c377415b9ed14c12a4a343d53d0343791e4c26f04498e0b8a83bac570a2a2544d7034476e44d7150cc335edc8234e53941eb98e5dcaa1a9fa9926d99dde7792a24cbb511c5311dd38d2fbc094ecbc38455176aa7a72e0969a2917ae1e59e50445c99123d799a6797ea3f7a51df9e5f4444986200772e0378622b86d4245e9487472a21c4d70fc524fe556520dcfcd3c35516ee677862967a2de89893244cf5434c74d14c32f243befb444d98521caad29888ee0e6719e12a59986296aa21cba92ebd7a9e42989f25b3db01339734d3930dcc4318b1312a5aaae2b777ae23aa6e4079a6253e98de7a892df2aaaa0ca7961c85492e7787edcf799ea478eea111855cb071a694ba51872a169a25e68822b4872e2172b4d8e43d54d14bf74e3462eec8644a18d43322a3afa34a5b2e34e534dd7ad0bc36ffb42b004e52a92279aaa1bba9da3c9a920094a743bbb30ecbc2e05475086ebbaaddc49765b088264068626825244396fed40125d39d0333b510b41e9a5e488762927aedfe79d9baa81a00ccf94034174ebb8d0434175cda40c5514454f725c3d55ec546ec4a40c372ff446af0bcfefdb36551d537e2bd77d1b67925b88a6a887d4f9a0892945cff3cecf0bcf0fedce500c8744ee28cb1675fc4049a29d8982e167ae5cc8ad23e98152354911e546f02343130cbd534b2a4d9433c18fdcc0d5fcc6aeeb3a50a2dc177ea8989e29f76d27c7691c28b9d55b3d32454fb15bd54f1cbd04689794e4c8a11ca7822229a2a6096e432257547655344bca2f1cc5efdb40923cb98ef44242a101e88b917ac475605022d5c8c80b2a86b601d44bc17125b7503c87446ed440b98567888eabc7a127099ea9aa65a03cd791fbd20e15c1504c4faf23aaa4e440f324cd3315b754dd3ab5a3a45c373435c70f55bfd10b8981121cd3d0f344f0e4c2eedcbad044325321920397d02629c77133c3cee452eff4d4ad2b4ab7ee02251782a07a72a24886a23782e890c87ddc11c91d2165e192654b38832649e9a9a46a92aaf879e40a8660b674e9620a15a5e1105a98525d43f03c55913cd3550c577348e4364e2cca22d4e5088ca43455ef1c47504439d2343f8ffb340b94e189aa6287a22627a2a9b892e38f1529404069111424b54a17ad02a5799e63f8ad6aa792283a7a219189761c054a0e4dc7745b41ce133bf2234323d38b1423158cf902121a24e538aaaae7a11f187edee871a736815225519204b753edcc6df5507048e4908a8a8c38aed324509ee9896e69d8855f1872a3786e836811283b13ed527215d5cee3ce51110d568ea8e96de6c991e0da716b4822797995e2988edbd9a1aa989e6a2a7a9f219aab54d714ed4c4e25cfaffbbc900323449151975639ae27ba9e1f4886297a86e1d97185681028bb0ff5d48d14bf3525d573151f50aa1bb77a5f78ae9fba89dfca79dc23a5da795d27aaea089220eaa1e8d735a239a044c3540cc951e4425504d3f368e41a507ee718aaa1b885a0f78da248728e94e206a2293986e0c87d26077a9ba731a024bf55e53a7024c930e4547224029342f94943af91f21bb90fed3813dc4c944bbb949172fc44b1fbcc105cbbf314bf8edb03680b28c1ef13c96e4b4f5345c5f50383d014506ee2198226c9919dbaadabba0d89dc88a42d52a25fe781eb179e21287aa0c881a129526e9c6a76a2f9a1aab98529c8454ba4ec4494ec468f1c515535c98d4b40b9a9a03a72e2b97d5e279ae0f96dd1102949af0bcd30ddd2cf135774f586444e8822158080525588a2331489b4434a0e0cd7754d45530453941b3913c95c61ca6884d4e54833a4e4c0cddcc2534cd1740bd7f13c85ba2e55896a00024a1150aa1f48ae2018821c1a6ee7868246485d78d00a2941141d517323bfd0dbc06dfd48aed30e5076e128a69dd9a99f3824aa5b4ed3088d9052e54eb02343f043bf313c49f0fb0629c75505bbf1f4c0f55c3dd44335414a53e450513d3ff04453ee044336340394dc087621979a61e86d9c287a8194663aaee1f675aa7772e4468a46eaca09711fb70cd00029cf8e1bbd0e15d594db4e944ccdd0fe28478edb44f223c7503c4dd2fbfc28c150fdd6935c373444b98f43fb284f914bd1750b41545dc56ff5d6d0f828b76e3b557245cf7005b71414f728bb6e14d18de3c091ebbc6e25534d8f72f34c53f5b8355dd77014bb8ecca35c3f5355c171edd0eff33ed1fba2e1518aea788260aa9edc8682277a0aa1a6441e27c475dc1da5276e29b88e24a886ebd7ad3f507e42cf8e725b456f25c18e4ccdee1447914452ab6875946ba78e5eb88ee2f951f981f2c3c28f95177e8080c42f4040e2978e3480464799a21c1a7a2b398adfb8a5a43747b9a99bf889dff975a7d799abf786264799aae7ea7deb9972dd776e1caac5519ae6f891dcb7ade4869add6776709467b76eaa478e5d0a76e397866e686f94e73892e7fa75dc2a8a9b789a432227d6a1b9518621e9ad20686e69d771de760e895c1733bc810ead8df204c37423c7502541933cbf543542c120ba3934364a6f0b394e0dbdf0fc52921b3d6d8d92e4d68d54b751454f4f35c14f53a334d1703545731bcd7448e44844b4344a4efd46134db9710453cffcd0219143808646297aa1f77da8b86ede897adc3924725c97887646d9ad27198ee0b87d5fa786a43824722951d129040645528f46eec78a94a320c8ef10cd8c92e338154ccfee2351b3fb3c540849c5227248aa942bb432ca133c5554f44872e34212ecbe2191fbb80294aa9a86aa8a9e20288adee6a94322d7316964945e179a29b99ee4ba8a1f7a8e519a676a76de6776282782607a8ae3179a18658a92a0c7a9e0c98d2b87aa269a65cbe945062020202020208e134d232fa66887164629a61d97729ea882a448a2aa51102a51c80d2199327c434866970f0c52a7820646f971a8c7a56a1a6e62178e2aa9760163050848dd62a2474661685f9462e8ad9ec7a55e3876a7ba6e9a17e5baae6037ae244aa2684a829c205a17a5297ea7d87de9266e9fba9a2417a5b99e5be88d6ada8928796ed962a21f51948ed3b6283bce1c4dd20441f2ecc28ddbb88c485e60208a026a5a9421c891aac789dd3872dc379a5cb20c1d01a95b4e13e9cbf6695994e1d685e2679ea667a2ea8a8a4322b70a21a947aa51c765318dac64c192c528060d8b9233c593dcbc351cc7b33bc18edbb42b4a0f0dd75144b72e254d744c4d2b4af403d7d03b57141c49ee3cb52aca4d0cc9ed0b530e0d4d72ec3c2a4a4e2451ef3c455535c9f4fbd41eb4294a50554990eb3c103cd714355720a0113c3a412a57846ad0a42843f10cc735f456f1ebbcb0f386448e53a1eb46f0e8d4a228bdd14b450f0ccff0334d1425cda1113c3a1a833428ca543cbdf53bd7d30bcd701bbb274a74154574f348715d57f5f34ecd89d2ebb6b04bb76e3bc90e1c4d301d3923f5c823416ba2dcce91dc5473eb4c5125516e1b12395234528f3c8d89d2fb462e25c14dec366f34536e48e484309188c01c8520186a4b94674a9ae27a72df877e21f89d4322077e31684a94e42a7a239a9ae48776ea77aa36684994e197a2e9969e1fd8a9a4b76d1a12e548921fca792b9782e1a692699722a3158080ba1419a5aa6a84740201914d65ea819f88869b499a6adaa1dd18ae195379aa6838929bba9129f8a16b3a247263c779114a8dd421b254010232528f80808080964a35dc54ef04b96f04513425bb113c3a122a42023b544d8ba08ce051153214a95c1172218b956b0776dbf8819f9a8ee6d8ad4322d7754a25eaa15b9a7a6b3a766068865e911091897a25283d522439eee33cf223bf55043791ca1521352341697e1f2a82aaa97a21288a1e1a8ea0f4be0dfcc6cdfb3ad3fc506e14a95c11ca9289a014c14de45470e5507214cf8dcb42507aaabaa55e687a27189ee62982a0ecd67315c535dc4c4e3c5590cba43c550f254571e5561005396fc5a4043b91dc42cf4b438e34cff31b53aa9ca8ae2909aa22c87969aa7a9e985244d5955b378e34bf0d4dd3f30365fa8160878262caa55b6782e0f779a044d70d44b7ee04c1140d41d4f4bca4f23bcf0f15d5f3143b506e2718921b2786a9996e1b297235641c28c571f4528e53d34ef5d62e54bd4bcaeedbc42e44d56d544dd3f4c62191cb2c2957b2db42d4e3364ee4c62f4c3750861f7872a047a66afa69a044418ff454ee3bd36e235190cc40897aa9c875a6388a9bf9992a2a829594640a761cd77de467a65bb78a9f474939a2211882eb46926a0a9ae80804f461a0ec5075db36103d43555dbd494a0edc46f0f3440e1ccd8d3bd7cebb40d9715f3872a2ba79eab789660a4586177e404232494ad3344f33444d5245414ff5ce30e5a97ea7b98da0398a5d24e5e79de43792e697a2dcd99164e759a0f4cc93544d4f1dc1b50bb72ead40998a9e2a76e306929b19ae2305caeee350545dd3ae13b710fdbc0d8a0c92b223439354b7d044cf75033fb09b4099825cb886e63a8664789267da49a05441750db76e13456f5d4f6f4bd114a0283f507ea600017531b900011d19a94040462a529628b20894ea1a6e9b8982e0e6a5e2d69119ac1cbd513c49703d53740d5152fc5e65c7a5e128862b088a26d979de7747e62ac313f5cc50ddbe2e3cb753f4ba56499e9cb89de84792dc11044a8f53c1d13c556e3d4df304c123ef0165fa991db78e63a7aa2188ae5b15d92325da89a7e9a51d487a26a78648472a92f9829844e680f2f3ba74fcc873fb44d034d1b5eb1a509a69378ae24a76eac6a51d37769d2325879222b8816b086e21c96d6ac78052fd3cd53453b31bbf46cad1fb3e941bb915e438cefb56464a343c538e33c5ef0bd10d0cc9ae5b40896eaae78923da7d64ba719dda750a2857cefc3ecf4bc38d1453f41ce5c81629c91105d1b113575504c9140dbb4e9132dc567455cfae1bbbce23bdb54ba4e4c470fcb6af0b4db453cf10dc1250aa262a92e4a89eded6995f28723d64889424ca9964a8aa66087ae908a687941b47aae7468ea2078a27997e8694db38aa5b2a7edc48a226baa19e9721a03c4db40b51ce145192f3c86d9c232ba44cd5f15bbf35dd3cd444bb6ff4bc0394e9769a27a77ae2a87a1e2165c7899fe9ad2b777ee7a99ea7e70d52769de879e21972db487a5cca9154ae081d9109529ee2f671dce7815e178620e919a034cd6e24cd505ccff11455aeebb627b2404ad53c3bcf14c513f4d674553b40ca70f3d6f0143972f3c43034d53455918c8aba9086ec8f5244456e0b5574e45675ddc2cd8f72fdbe0f35d13155bb94e4b6f48cac8f3255b771dd3c343cbb5555498e8f52e540af5b531154b9b3e3ce318b6c8f325d3d9233cd8f244d34053fee28d3a32453530541525439ae4b532ef5bac8f228d56fec3c9053d313f55653f4c6c8f0283973ddbe90e3b8314d4593cbee28c77513d7900ccdee33372efd323bca8fdbc28d33bd341dd5b553bb34b23a4a75ed542f154d7545d56ef5d229323acaef5bd56fed54b02353f0ebc039ca35e5d2b323cf2e4dbd30f5be6f94a31cc1ce0b49135537b2ebd06d75228ba3dc44d1333d5135b7f54b4f12fce02849310d53523c3f74f5cc94f4de28cdf5e4c2f43cc74e45c7eee3dc284355345393ecce2e05d19f54f4f2a31a79392ada82fe9ca2971fa1b11bdb28c1f053c1cd4c552e24436e5bd9283933e4d6f42455724b41d14cd728576ee33cd2534dd2133b2e0487440e8cea02a97a6a9426a88e62e871e3fa9166777e64646994e1a8aa24397ea1e98522ea89de8746c99da2987eab19a6a99a921fda9d51a2a689aa6a078ade178620b97e1ee8456646099eded975a4f97962d7a1e006c60869f4c01859e9be0001ad9195516e6a089a5cf881e0c875a01816a0fcd213453bf45b55eee4b6f57c2123a3f442d53341904ccf30ed423315c728c7adf3b64f4dcdd11b55aee3c428576fe4b6ae03bfcffbd06f5bb3304a4fed402fdc5074053930e4d64f8d0c8c12dd4053143f2e3d53cef4d6f1fbbe28c3f534bd500c43d353d1d14c91bac400043452b922f425f3a2f4d2d004c570ec38afeb42b0eba2e4bab0233b9413557445d7f50302fabe9042dcf745c645399aa3b79aeaba921bba7d5bb64569a26b38aa5b378ea229aa211b9916e516aae21786200a9e62d78164964589925bd8912719a29f9aa6613a24729161519adeea9d9eb8a29dfa91deb6665794689a9a21a79ea818761b3a8e276456949ea972ab07aaa92a8e63078e6a9a55518aa96a86e40a8e5eba7621a7aa671623f4485d41089424a3a214c78f5cd1d023bf5325418e3b2259c8a650539d4c8a32353f721b4195ec467334b1106501936e5c1112d114b0eb6272f1408ed3218ba2e456510c4d144539355c47cffc81425494058b4a0310101050c70971e01f1914a5d971a687766a1aae2407a65d141a7e98c89e285530fdc8cef3ba6e033bd55cb7cea32373c2fc3671edc0affbc0534ad644e971e7a9aae2887ade0692a497315192aa478ae7ba9d63f8752718664b9464277adf397a2728aa5d1aaa4322a772f172747a391241ee28dc3a2a578460c89428c1cf5c4392dbc0f3eb3c303487448ec8c8cb49c210da902551a6e4f68d1f2a865e076e2ab77a65644894667a9a22a7825cd889dc1992432217723f565ef8410202fabc6e1bc36c53198a9be989241986619892a2c95486dd399e1be8a9e0488a23d92d95dd399ee1b88aabb7ade1099a9e172bc17103b7145dbbf504370ee43c4fa934d52dfd481424d1ce5b535024edd81294deba72ab2a8eea3a76e8977e5df7a52341797ea1e9ada2caada42aa61c3882f24bd7edf444702547cedc36741441099e1cd9a9ab1a86e04872e318827253bb533d5530fd4690e4c60e0425baae2b8a86e90982298772a2e7655272eb669ae9fa859fb7aaeb360a711d26a579aa1d388ae78672202786df98120cbd555539d25337d223476f48e44231e54a7a6a78a6e4a8866a7a9ae707ca0d54bb0eec3ab0fbc8b53b4fcf03a5c8a99b4a8e9e18a25b678eab97549ae04a72e2baa6a70776a177761d284d544551320cc1901b4f6e8b172e425f152020208e89e540d991e3ca8d5f7882aa87a6dd7949b9811c287a660a92df697622372472a91016a22c475e3c4eca5a52aa2aba89e1d8a9216a9e9eb96da0ec3ecfdb4c143c3774e4d2311d123922750baa7e1122b928ab8132e5c25424b99414576f3cbf6d48e442750527360325a99a6087a65cf771a9687ae6d875316108b92e2617a18d43da4aca6fe4b84e25bd4ff556f2f4404a4a6f13d593144dee1331508e9c7a86e9988ae6289262d14a94450849c51216ad4453b6a84444e6101424550a233b49798eaa08aaa899aee836929cc845453a0273c4791728b7ed43d54fec509424c313e5dc58494af124c32d0d5512543baf03d55568e31eb061ca70eb3ef5134f54453f8ee3ba219168cad1511846527e66a77e6a08821c67862b780e89dcc70171507eb6f24786177e2c50aedfb882e6a8a21b1aae61ba0e895c2a74940508888b90aaae2ca86a941f283f475fb29837fc5879e1e7088948252aeac2a5c848c562129930a86a142e280e40405888b2a8eada890ed80a94e646aaa349925d087ee7b96d2051d1162840402a51d11631a440798ea0c871a6ca999ea7ae9d0a49998a9c9a72ea0aae1dfa79ddb87142dc04ca930351733ccfef0c53130d372ea810421b146e8695407986e8666edbb6ada029761dd7c534ba2102a5089e27aa8e1fb7aa230a8aab109853e8f390b29c63c7091d7d9cc7c14a8ffb3c3554d7d423437115d75749aaeb4672a1aa9a6a8a7261b7aef24b4f72dd4e553dd1b3fb3c232c425cb7ad12fd407053d7f4e35455ddce50a47245686821508228b8a1287a82237aa62b19724171784089a6a81a7aa6077ea10aaae223a5b9a2eaf6799f8a8e9cd981df90c81da9ea2ac47158d601e5a9a6630a729d6792eb09a2de90c88d462a11d1942c5d8aacd4308494e506202020204e8b6d40f96d9d998e20697a1d1a9a9fe9481986e286721bb9799da79ee637247240a8409440405db88ccb80721cb92e04377305c34ffd8ec68d6ba43c3f7043532f3d43f4133d5365a41c3df5134f521c537035cf714a0a1d1dbb80b203bd4d25d1901c491214d34d01250886e0998edec8811f388adc19bb48b972e6d96d204792201aa226aa48d999e9fa6d643a76a219825c2804c60869e3c2d8444a931b411524494f245173e35264b47eddf2b1092851904bd71335d1955341724587442e35f27244a4821cc985352c2265178a5be775aa899a21787ee496330bea25558dc4ad8b3da4e4427244c5f30cd5735b512fedc22b6b48f9ad9f3a9ee7d68923d7795f382472e46784d4c55b0494dcb6aadd279ee8c9851db76d43221744d19623d22885904389c01c7d03d8424af53357d213c1ee43cd7135c9ad8d3d40997aa6daa5620aa6deba75dcd791425c77c51252722ac7a964ca7d2777ae28970e5282a7f7a124ba8ee24776ebc92119993280498b54f03c41ca50553bce54c973f4d2f51357d18c3540496ea9a872a4279ae7789a62d875819427da815c387a6998aaaa88729e074839a6e446aedcf68123c88da729c4795c57c7fe51865e78aee6487e648a86646a825b15eb47a9aae989a229078aaaa776e2e7797d94a0d7adeab785dd0992e066aa1bd7c1b17c949e17aae2b975a0da7ddec7895a8c8a824052a9ec519e9bf8a1a9a87aa8f9a9a037020179a410e7e9519a20986e28da759c987aa7099279942bf88163aa86a637aaa4fa711e29c48dde970e8dc5a3e4c6ee0bbd5115c36f0b436fef284f73fcbc1444bd730ca95c114ad78e521dcf10f43c92dbb8d3344fae0a10101050c70971a8fa85e348046c1d25f7791d6a8a22a876e2b87ade4647397a20b79ae96982e2d9792b3a4739a664caa521b992eab8aaa72747d985e866725b38a6e83676eb070484a41e1d99601ce5ea91eb796ee4f98128d9812b29c4c1517ede9876de0a8a9e29a69b78be518a5f67a6a2ba71a8798ee7086ed9624211e2ca58374a4fddbe7433d1334439d0dc46928d6da3e4c26d45bf1304b76e4341d463a3f43ad2ebd0703dc995fbb8cd5ba33cc92de4c25045d7144453f153a34cbdb13b49931b539114cd8e4ba3ecd6f1ec4050fcb64e0d3d9544f2d205c94b0c54ae08112d1aa5b7ad22b8919b79aaab9a926017fab067945b7a8edb7672e096a66a87a643220704d479a319e5aaaa63687e6beaadaaba859f0c5b46d9ade61a9a1db9795c9aa6a039247232d0f0f323031816a0e4d475dd4e313d37d0ec48743b0202028d90ba905186619776a49aaaaae9ad9b778e519e274a72ebba85600aa6a3391aa95a8a8c4e4e8c5224d35524cd314451500c576e5569800244c586517e9d8772a6c7811b18aedb8a0a913f56a470421cd8bdb16094e8e6a5e8989a609a72a9f9a140401c27c48d80ec1725f87d21f79da3ba9a1eb9ae1d16eb45999aab1aa2e038762308a6dbe77d9e18db45c989dd4a8e21b879e1b77aa267c572517a2a9776a62992a6479eebaa72294291bcc4f07d01020a4921eefbd27164ec16a57a9222fa711e79725f7a9a27101010d01129c4914760ac16a5378e1cd8752b89a29d8aaae428c46d526c16e5e771e429aade3882617a92e890c8211165192a5a81808080b88d13e24635168b92433774144f8f24c58de47eac482152bf94e338c45e517eea896ee0c78d5e67726467aa5eb69836a85eb6985fc02cd68ad2533bee235714edb6ad53d3cde3b64e684b172e593c0ee4a85c11cab8d82acacd0b439224d1943bd78d0cd5b408252a431055d3225448080c521623b3cbc851b92274c252517ee1988aa93882e419a69ca762f152222f23093b45b99adea99260989aa399a2e24a51a2a3277e9b29769bba9e26d80d891cf96345cacf1423a328a8daa50a10101010aa7e21a95c11da26368a12ec506ffc3a6e53bb535c557448e47eac4881f2d379508cd421a4188c6250b998558080d0a32c5c54d4dcd24588288b509722a3b4fb4823a42e1ecd230b45c97de61a9e21e99de490c8a5aa9a7220e8659f28b971fd387135416fe34653f586444e28e4364e1cc3105d274ad0db44131dbbf324d734fdc21f2b527ea4d8f003e5c7851f2b2ffc0001010189cbc536518a67179aa4a8a25b88862b990e891c18558b5017930bd76d5c17938b6754448a1e47e58a90d03251a65c687a9ea9a25ca8ae66fa0d891c16a22c4842a6d0e9e548a80889288bd8a5538cd825ca3424b730e5b8711bc9ce14c1219113222ae2ba1f2b527e904e7505a42ca96a04a54b91d10a4431a800e5274522f25284a2298021c292a50a10d08f15293f36000121a16a9723a22940403200010101fd5879618aba65b5f2c24f0d443100117911222a0202e2920508e88848d552844483ca250b9291171580808080448ea37245c8a69155a244d374f34e151cbdd503d333ccb24994e01a82ddfaaddf18aae6a77a2a2c1265a8aee8c69da0489eebe77de258854b9151142020202023a4a3908b17a22c042837ee1b4995f442af23c72e25855059305283287aa1ebe188b203b773333b74f4b8504dcfee0b4f234a325d415134d7d413c7edf4d221910b4225f28cd421d4c854ab182155e13822231509c9ec221a211d15516edf976e22ca715eeaa9297772112252b7889c10982e4528f893825e7eacfc9ca0971fb25389284772544f312453541cb90e553d1686283b30fd3e3545bd93fb3a8f0ccda128604a72db4412f4ce0d34cd6f535721d2293f565ef851b11099340001a958884c2320a0a32e459c48e58a502a44298ee2a7aee6169ea2e9a9ea2844724648476010e5376eebaa9aa779a6ab79a229488a5f4af43c5194dccc753445f32457ce08e9c8ebbc94eb36a2a6298ea6f86deada09a10a85dc8f15293f46e6507794160d21a95c44237308fdaa2095eb26ae67ca71aa6772646a8a484747a56aa2e4ba9de2ca81a4ba72df96a2a2f3a372458805a3721dbf0d54c3d124d35414577148e4843a21754b172f484258bc9c6a0a762510a528929d979ea7688a1dc98de290c81521116511fa422f42ea9191d9e5e3be2e2549aaa2488a67a79aa3f791e2d8715cbc10794245a5c98d2a08925b2a82a908221d657981242a41744c3d54ddb63025c931dcd42d5dbc58d948e0527e9d48aa9ea86e1b8882640822a16a1721d42da5f78527d9912adaa960f7719e96522449905bc7f41b3b70e440ef871214538e23bf2f14cf73fcbc354b9672153ff25cd1d34b3b8e53475145ea62a629a4a60c40406348e58a1096a17215b951354593fb4e6f43c17148e4c02a1c7764a48e485cd612b094a1b9792989a69fd875a7b9824322c7a5c868258f904623a423cf5b4b7423d56d33bbf353371335cd219113dabc14d492fbb6d11341f45443b1f3d66f48e484c0a45db7658b8a7e645a7a64f771a94a86ebd6ad247822798121a472456807b4443b74e54eaf23c971db40530502dab24545b790ca15a11cce120c5530543d7555c30e44cf6fdbc1f43aa0524c371235bb2dec3af2ec56d2f4a11c5571f55675e3528f13cd30fcb122e5e7c7ca0b3f4546ea15a2222a40404546ea1044454440405cbc108922952b423f089521498a20f98129b76eebd89120952b4238f4508e9c487a1d7a865eca85dbd77d1f0fa56aa6dd99a6e8489e63a76e5c141a7ea4ec50a2220a7a1c97821d8aaea6fa9124e8506ee6c891278aa2a9189aa2f80d895c6a76f180ae04940ca8f4edef94402009410cc60000035074ce981999b301c31340502020140f88a43289a4473b3e148009327ea28626362389c210419003310c82200681200618438c31c628a418546600d0351d347048af7265740980022dbe7b53505a6db898b2c90867ac693de346640f80ce160c16bdf46cce72f4ab026223a5694ffb5587c29bd926eb1975632e2eac9c29b00020799cd5a3daca41604b6330011fca0150cc003ce58707a2a6b88a6eedf238039323fc8856c310d8a2397c22a53681fbc5527861289217c094a7afb223485748bc1781fe8569475bae391a43fa1deaa0c6c82adc5ae8f442dac8b11b4c0516c8217be50a5c1b483b224fc91cdde77bd0092a591ecdcf704bb26a77a594ea404f421c87bed74f1e1c54a12a46ac5da733b2a74a2c8204639b5f49c9d57bd181e6f0147f540ac442b5e07a9403f495d551c251313c05d90de741f853af41671f3be868d94ad9d015b5213226efc6f95e2c1269531674c0a5b5bac0616d7a670bf610c3a771be5587de7e1e4870b932edf53fc35c54c06e6abeeecefe57fe41dc118c9d12759cdc92c605812405f7c8495c5cdd8ce56080f2928975055e30d0857c0d279c50c2adc29850f11b70b256467b3ddee394720cc7682f4f0fe4644714aacc680fc8d354d00785e17df1bc8bc8f9d2bda088e9ef3a3940a243c28b02480858324756c61eb243176a269cb676af206575e2e4a3290ceefde8fe77930ff81f347e27721dbeb4e62b9260ecd9c941cc327ac0107833f8137f039c085741729edc00d8189d8f57360cc00000fc6d805ff17c1c1487f0dd02532018ea0d5195f02cb141dff614f6b1d111a0084a2b2bb4660dc4c0aa8f54331f24976f98722bab5856711b48e8612c5c254e121b8616f5fd1bb4ae6869a29c685dd6ffb804e02e6ae4c39a90472990bb573b23ac564b340bf5970459249d9513deb4e4e21a2231bc1b696045c7cdf2ce842060d23574a275b0d0456707f41515679021f7c74e9db38b148f16107ee73c19aad19d57430461fa676229bb2eae645dac0fd99674daf8eb6d8f85b5984eb718e0eb0e8967b7f104b54b8b1adebaf49596d4e01050228570449ceeeb21d4d76933c48943bf3b9c06426e356074a575d2920ee0e382a22c221d5e93cee57819099e12520717815e30577068f8533a1769c859e0b3fa0cba8e8292fad1eae7972577e8f74275992f820e2b37f14b0cfbc0a35e62f36b43d037a60d9249c9d114e182b052574aed9dce905278335593b32b3c7101a84dde773d66b870333fc18cc22303f975063ff228dc3bf76bdd9e08d0440419cfca73962bcb52b509ee5c1acdce1ea31dde2ec7b47346716cd14b9928038c849031f47e609cc5bb4234daefc55afd603d9227a456c4523096968425e808a6f8ac50b6279823a2fd5b3e70a5e102f9c8cb573ec3e203bde5de33ac5b0784afb01eefdb02f71c7d8a96b17d1bda61d99caf1bbd2e68c62ef333ca15995b6ea9b29c576ea58c651bf22f86e0551c68fca43b16c6cd8e215e34c3c3eba9856a0e07bf8d290012ed78c36f1a10a6f1c65cc00be31008e3b6aad145071397d4a017f3475b312c647131dc1b39090ee756fec06a903991b945930a9d69c3ed228597d0ef6d6acf981cdcfb33fbaae321a6d06fc3e2d53632f4c4156a026876a6ff10d849318cdc90edac3d5c906b01e994dd6190fa8ac257a2d69d13e33b40ec698cf6bc98322e3fc0b9ed4225eb701ceed684a5a2adca01370bca5649c40ec98de0fe1db6548a3c3632e7f103a96200301b0b05f80e37d524bdae5b88978d2827fe29469a01262eca2401ffa0443607383e30f04e2739d0bfd0ee574cee8f6ac9df3b13431153fd176459069cb4565fd6f2f339aa52ed68b2c1a08378abba594a486bace575b006de299c209547d690ca4200ed68da626b1c071a1d1bb9f0341f3dc24edadbfb04befc0d1fca1aaeca951a25caa205ed7cde8209907d74a8e4354a52de50c3bac15c519d4e4eface24a9661d44ec1931da103eab47ea5423c4471286aa050bf13bcce49b41b2a616f4c5f5b10582249c2ad0a51e50e0e18c150969faaf8f1c59e61b7d3e96a1839efd3e73bf00494f2bba304b28a12420bbdca3ee222b747d99f0d5bbc95f275b5d63895bd2d7b8777045c2c80075b994d35aea3f6d92292df5ceb6ba055381017e298c10f384105e595ee8a93a1e4c374b05e9e8a247efd2b45a3ee3c5aebcb4524715c851c5af83c7bc32a696e758484a030d315c1fdc775fb797bb26dd0e8c21231597c0cb7e8098943cd8e2ec231b4c74404acbe43abb3fa5b6ee94703195e7d994247ef9403ec96f088a510523a27a002547bfbdd8e5b8f21194699c322476c8ff250d4ceda120413f2cdaccb14237d386a493e9f4faa933b609e08e968cac8a36932cf0c55951beb5c8b80345e9c49c39a07d48b24791e17ec5f5c776e6a66d3d90c91029af158fae1694b7217286439abc0dc375971ff80cc1df399635403f73ce82233a74e472354093ee13f387872207116eac1b4662d67220a67016a219805092524be8251fac0dde99f5d637f5501da2ed2ba59571a007a7b3e3b9c0c6490a92c4a40cdaf97b226f60952e0988d641d0dde5e9f2d42ee06328173394eadf6f7afa845875fddc8f495373acc3e57f9a5c46f1ac549b70f031721882d4b846a03a0a519e0dddceb3f5f16f507c1ea5a75a7f3fbaa944c7ff19660d346e1dffb2a7d0524e02ab7dceeb8f25168ebb54a5800564a0b0ab82be2cf9f0c65110d04c61d30c628d6b421f809d5bcbcd1e9424b5cc21ab30e489dd885b07119f2fa0f40defe155eb2b002068b48ced2c40b5b7793eba6596c84990c756511e283784597325e332cd1cd91a48495c67805ea01baddff6a240ad4966e62e79c5d8eb166c00351962898a73c325c9c69e9fbe8080a670429fcba250a67c9c085a770194f024bcd92154092db295772c6b59988b37268bfd49624793002e442c47cc04509933d49985a382183e3e8ea098eb407a647b3d8a2a2792aae192b11c29c57032ce0a8d4a28041b007decc0e08db19989851b06fc0b8b804946042955a9088ef85be5c523ccb20eb8da9c76b0d7c93b0cd5f927e3cdc2cb1d1e4c5d7e100770a466303dc9e743940bd3fee46b8ac02b518e269ad55754b91d0d8781e45fd6a5b6cafc68d412858621b34e03d28f7e5ad7fd92fb72d53a45bfbb3ade309c6c442131e1825783d6a4100437a12308ea9724b2c4ec6c78f34175ddd438487da3db2b514d9910952e0b7d29c484b2e536eaf7583afc4e676b2a26ca85cdef6e7dc188a7fdd0661fa77110d2463799f2af0b9bce3961bf1187e3ec68336dd340e37f039d7f8acd4c67d7c49ece6045e979dc9e42214e2841b8a0efa4327d1d9e37c7033fcce2e472f52a793a1f87f842207d2148b0a67574f39e1c932e663386aa93ea56ef9f1a64affb6f4b0bf8c295c34cf3d3a5b5d555ef6dd0438319652c71ffda573304dfa5fefc8cc0ee1acb0c0e8ac5d55c061f4020b5f814d4f659217b20613629bf8bca79cb200e78228d3b5906647e92b36b6c36648811230858df148449a6c9693a4aaef18945980cea75e7148fd7fddcef1855616752a35ed56ad64b14d9c67063a7c94077fbc304bddfb2020439190266b698e29f4b24f281c7fba950aa730a079dc89f08dc9e5b80f690479b5872e6fdd11d3d832f18d11ef6c3030ca533dd92b1559da2a15b48a64900656be3572b0d96f087b45fc73ba3a3695d66609b28ea00b7d1b48f567841cb63090520c371ff000c5a4678fd27a9b083f7e0e1b39ce20a9fc7f2ac0a723e495484085d98d5f42e06033d41887738652bc8d846927c28cbf890051943e060fd8f357b23f9b7fbe484030e0a18ea53dba96a5ab83f0c4761b70fbb5147e1254bf61c804c7bc15fcfbe7c0c7abac274950c9217ded8d1f09e6c40e0910fd5d41ec8ccfb367effde77e45bd2b28b6918d4371840114c06bf33b302c8049123e953ea461537ed750eb9457ac14126a9d6814fa13c4427f2ff457273f38f72161fe3c860276ab84e58ea09ebb4b81451228660282dcc0cf97c3715cefa55e34160d35eda1651e2c5dee41df5e28312cdd1940f28bfc12bf1ea2d40b1479005a4c90f2630eec877391124e706f73d937ed855f355bd7fc3b1005564fa1cc3ad31bd13244a727d1b8d7e789e869dabbe9cba37f96f3b8bb01c86f7fd0a28d2df6cae65fee841c42cb0e3a63ffe6df8cddff84891b0af76c7e855c2f2a2320132a7340b6ec2580835c08b24cbc96f47ab44b10fffc020b811e82cd4b98d1f4583508f1162f68280d668188fad9399f44554a284b895b446d34046700fd7acc564932a4a2db2492af26a4b16037b6f12280f54811c94109c799564ccdb17a5d477a7528d22794bd31a10c21f45ca407ba00de8acb1b7b0a420c5a9da1c4b7f26bd4594dc421a4314fde56bf7a5d45f3d8719371a47f1b3f21a483650c567df12c2b78a90bcc263e3660ca58198fcc65326fb6a98d4f9f7f9974ddf89425d82db88f26d8427fc1fb86464b6b1fcd0f6246420cb152f774bcac9a9eae42ed12aa95e3bf68174048c743e35daf0895906f585cfa351df5924d6cd29282bf6f3fd5569b3a4b8a9d3e630888171f6ebbdc46e6d8590fc8e7e2b05024871ca24cbb335c20a4beda11a39628ef3965558c94d723597d22a0dd8e77d5278bb24f8a66f56ae8a834e8135bb8d31e4cb64a9594689fa6122be3aff6a56e83fb3bdca2e6c532b0c6963b129b336f1ed9d0c43f867f7c682409c970b33b3b1f9e8a160ecdb796b319eb32329c86f18827d7b4e3142d3c2504a4bc7e22e4512fb33a8613d39cd75d70326f155340957c059b3ee323d96e78901f02c874c6ff70b1ed9f92e439780e930c6323241de4cb092fc95d64b8d82484b53603dcc1d8a4e04a64d93c144ab7683341845024dcc83538996ef2af076d7c7fb15c661697d436694d7cfb4c40392a9622ba0efcf49afaa682f018f5afdc6818eed63b870ade3d3801587f319f1a7bc778aac4f08855de6e2eb4216b64b1338080d541c4e6b9493c121a40c3977b91f968d197501c3d4b897eb85ec998962ab02c009a6fa613154a8a3b9350673918c0fea1364e59767b20607d9e716e4f74fda84914a7e023481aa8f8cebfec2328f2cad198ddb8461262ce724a3d9851b61524d2d77a015facc1e66c5884af21480ba83828d3359692155165f9b1816d5aafb0ea5dd2e4923433a89eb39f07a9ff18e96ce36238453011709eac688791814e0a80938dc3af443ff8e5c8ab5cb73dbdc200781d7a069089235cd589fdedf839d6134e85268e523f718b8b842f387825ff5955457f0f8ff5aa7c50e88892dbe2e1ddef11bb79eea89d5af3d534dbd5242ac6877aa5d7b7e689b9a2226ae86c78792b4ed362de7a1e0674767734080f09f66edab87bb4a284a54ed7184acdd72875d0513df2ec5d607e751e4f7b4d6d0f951dc6beacd1ff28328ca33f38f5ca2fc07c0a77344a36c4a5ae9cece73f7f0c5d0912d40f27345c6dafb2c0f4ad062932a85422927db64b1c1af8604f95e42c1b8daad6a1dc66242aa195f978ef0e6e110caea9d107dbad89e4e75a76f9082d2bdd497f2d148d5898e6ac9dc7fb41cdd7df1ed1351dd3c8e05db4a3517b2d5ccd924db31a3317012493db9fa40866b29206a5e49d9ad99027a499bb1e58c287835c606e58e0922d59a7f8887e0a629286f391dc492968917a437cc45ce055f5dabd1e9a5bd9bf3bc01d08106b3195fb6a2827d292a114911b67390d375bb01bb8c9e9f8e0cc172777f3739f9fc28b0fe7054a02162a6bb00ce5992d79ea2c372e2d615a8061a37b5b3972390b071bd277dcd35f9756cc52847f72340fcc2c20be717d256e4f263607b5e471fce7233d99316f5ca5f21ed0f3a51517be4dd348b8fae5d22da8280d26a89dda9dd2fc44e1b16eb49a4e9dd0833a9d29c3f32368f856e7c2572b3760eb69b27481e74a01392804bef98821273ade2cdf370105268b82b68bb279889eb83367abb62edf7b7b2cb896970bef7b6131159c6a5b89b4a4e39d7f64113a9b71a84cf36010f0c5c46351acf7325015ac807098aa7f78086060f292cbb3ea31932b94238f23da00862847619f7533d42d53451d53cfa1d7a7c216bf0525b9423dce082235aa84eccd33df0b34b0f6a1326e0e4af1b5f69fd72d26b93b296d62adbcda39a103a63375b666ef20a5804247f1b0c0929c9bbe47f6da6e4fa3804125270e391a7f013c35d4e0ae964463c22585708c759ff7025953fdfa5252c855c4f627ac9c247894dba0e0b7c74ac1e15502d28ee5662c24bc3d4dd012a991be1a67ea471b805b44c8675a241628e8cc57880dc9ac30821f06458b873b526623fb5bfa965d6ecb6ae16192ff64495d577222aeeedb0a38c3290028c4f4f9cf91b49f1cbe140bb6d42a82e16f762daf0fa4becb7a00098e7a0de3d9673fadcca2bbf0eb2c009fd4334b97894abc63df91de9e3453e0f7f2ac69dd428e235e022d6dbf340e3533e94a40f507540e6e310edf01c91caad88098752eb15267ed468655dd6d34b6e26f30e56728938331f2a5ff2174e36cbcf4d449668dcbe4fd9d54fd2776f98df2b13fec9e11e255cb3866b223ed0e9e2be11d1f9cd5cd13c9d5f2680043e96e11801f0783838cb462374c88e2255815bccf9946d0028cdc5b4e3c52c17286ea230bbe894d089f1022bf7774926d22a208d110cf5adba993eb9afbe82170b25747db09acf738a963510e88c8b00f46af48f353838c66a362835d3457fa496c823060b4f6fe64f8d6c803c87f1e270be475c06d88e02af94491f6667d450a102f3aa4e1b047ab0893f2f52caaa711aaa8c91a98e82a9b531a1868cda627e9e0fca1724c73bc645efc23668fb2653036a151aa9f0371230a119fe0d9bbc6f61c9ccd0a3acc08d288e3eb1106a499b678dc3f4be7d60d286b37da86bf6d2af3f75fc2f2fd4fb3cd20154aba1300886144848a3ec3fec0f51d02fa1ff3e31750c59f07491125e1642bf318844f201ac22879a19b2ed538884fefca3f86491e16df958d3ef3f0fd00ed35fb0b7f75e22207bc7ed20bd0bb72a88b2424a2f8c5eb1dd2230ad2a5cef5b6a69a44c280b4c347b8f92811d20fe0a42ccc2e5a92ee00b0b5f10bbc49b0ba9f280dc5bdadd863c72e7ff85d887e0dab346f2dc4035f0b1b971289bcca5833cca5506680226216a6d63019cc4dd14c33cf8bf37b02115a6e620469dddfda2c119ee44329cb06641efbc14482cebdf22e5043391465b933b6118680a1a9784dd25e5033fce98fffa42129320eb5eceb75d62b483152736009b46902f1cb2533e69c260447900052b66dd102c1c397ca70402c7263c0e1c4597eeec864af4ad614c1f41b37436d83986e026a748ccf122e88c7353e76ec805061ca8624b781ee97fe7a0c40f1454c84ff893bb2bf363d0baf6d083bfcb30a007a65f19aad06a9b30751af8243173cd53dc6f9c2cc058ed29c33906e0cfa468496dba9be87dbfa56619ae24f48abaa3088a399d7d60a9bfcea6f7862c8f5a9d6d02c03f58a4b52f5355e34bb35a83c57657d9ca504a501ec036f393df41d68d9a33ad7acf2f40df285cd0094abf907e7ec6ae2b23cfaca5cb44322cfdff77d49c74fe75056fc164dcf01c1cb0b8dd97e0565f03e209c8639120dea542225ad43aba4b18dd9f929038af91c02a78b89b1000826987e837ec1ba6256619deade8b5fbb1da2baa78eb8fdcb43414e03f6e6e191f00b6172fce9bb64815f3c65115b5faec49dd49e07e960fdce6b9448fd7b294bd4e7eb1cdb841c82e493e8a81f57fc3e592b30b2fa8b0df3030f9cc7348f594c2de094764f52972a4ad672ba8e9ffef398ebf6917591cb020a62971e368c04490c894b78e7f24ec71fd43f4042386bf1f34266d8bac678923948937d947ad111f10bd5f009fe6055d7c16ebf50bfed207e8837c9d114eb0c50e09699cd4779d24f5564e92d2fe254db625191266b052affde1ab9404eef817d345e25277ec830753c720425fb54ff45eb56ddb3bcbb6635d0c128d4b3c8690c917f41dc1ddc6f21a862bcf186b7ccf543ab5a92d2978506d8c40909a6a2cccf2ac65508df4f86e3a3340007fa1217193e26f7b84b3cb14dce83c13b53c707df793560176242069cbbe01743dcbcf9001c4a67bd6b50e2f14a703b910857159e1689ed0d4dedb73efcc7c47aa01f710a1e5c4772239ff9bd255c5fd1c1a15d05bc8c935f2f0facad29acc00c3ef961aebc9e06a2a657b4fdd436bfffb26b0783bf5c1948f678de06df0bab717e9361e562b092ef2d5431f6e88f983c89b0e0e836fe1a3ba1ed8b8b497632bd49b5cbe26b4a3b9a9d429d379cc8a31add1d24d901fecbc0b4e77afcdd0d344aeaeff0384356af7e84c91f0f0d70b43d38d8059de88f3e5feac81f854b0f25d469c11a7df71e26071b464771494114c6ac2f1d82054a45b3386847fc0da9bb8d81235b22d3ec1203efac68d76326b91fc45bdf589fa4bf4eb88e828060ef9dd40883bbf7e5b6e634854db2a7498a55bd81dd381a1669a95b6b96ffa2977396e7aa12624cc142927e1001a7b98478528071b5cf362aac608cd6ce7155cd0d5ab48775c1eb7bcbd9d7749332edb3c72214d2cce20ab4225bd2c9f72b383fa91d3f2af394b5e7f10b89ce327f66e005ba3d1d0576e288122a14f0eb59b524afaf39738afaec936b93fedd863c338fc3d0a5aa31bf0933c87972b701111383ab16f95b9550a38bdd998952b0b00a64cc0518d6030b7a94c572b50e22085f9d4c998e3270f2d4d31d1474186272cef3167e8dde81e34f70423f6dc0d1d1e5d9b81af972f01e1fc4d42a3791b297409975e05351a9a897c61bfbcc26654176df2785bfd8ab79f58c64e4ee1c4978f82acc228ec4c14252994b35035dfd93eee97480aaf1ef9f8e66772a6f4eed5af2a92232b6da04b040a93e90ba8d1486d76ff43500b3fa2a6f67cdae7fb0228b978f84c3d1d6380cf7d840cb8cde0e7af9b417780efa63d00496fe837f4920a30355e62f52434520636034a4ff081ee6aa538081f2c1860a611d43d0c06349cf08aaedf2d81a43c3945ecbf003468343a91b40bd4a6c44fca95a66ea253c5e0ae2bcf187c2ac5f5d650f9a8c64bbd0c77c2e915b4f1dc4711afb9c69bc63757bd1e3958196099cc355887178d10fbda9ec7f02be66c5434f01e5f6bbd1cd786767ceb5053d83c6861bcae6274f3ca1d68a4c2af466193d97a5d5f3c85ebcc12125ff041b699a856c715adb512fff7e06b79433282a623aafa9325c9093490f8d5287882311df180a50194e30b6f2031f7153eb3065346e1db19cb70345021e415cb60a48bf44cc68d00d6afd765987395d83571ae4bbb88f701bce2380515ca181c5f8b166145067a6a30957a3c6fe063928f13ef6e2b2c485bd910fad1bb915bea8be7227d69d28d57298618d98680c5225d6fcb5360f8ec376f67688e79eb9ebb2fe1d413ee7270a3408fde6b472f04302d68703c26f7e15f42693e2684733d3af957e94f71cf91eeac90a9ed44f7dde2eb16249b5e0f6fe0087c9aadbbf627e835f5b87d4aeafa72e68851749a7feca03bc1d6dbfe1e7eec1b1b13deaceb1f643348cca099326e7d919625fa4892a01ce270599247b1398c7cdf2669ea3516fe8cefe9545f1a33ae833cc3f4ec256db50c45a45f50660d9725d195529e21a55a6f061f49a9eb2e35740099a1df75757a312100fd69bffe3a0082af1fa271e157ae822dad3a51983632369d63ffd7472d754edd74f5c2f4c5062e8c992183450334baf131cd928f3a247e70c6399eeb823a3813d424faf67523af29a764f60a59ae1bc522a34447ca71d1392522a6cf36ffbdb90f56a3b4051cf1d5c7f3992cbdd2bdbea7a8661954e72a8ffb6648d482a18b5824dbdb359decb58df5c579e5a0d03f73e4166365c7821d721d12b27f6f80929140d8c3fabccb0fd77de5d9d6b4cfffc000df19e76b168c184765abb48847464076dc8c4d52a19063830a0b27e27cf2d630c0ee194e7c17e6c0f0d555aa9cbe49096be8f141e239b706debd0033a265e9d8710e456538948d09956e6dc1a472bedb8dca15ab61e66335118250cec5796ad93ea5c7b32ab3ca403d67d25f49d366469ed328879296e9fe1a411207b2d160ef47218ff81ad554dcf03ca900e049ebd297c1479264484461931c0c8fef1e2cb9674bcbd2593a6b5c646a84f43f897286e250139550d839b4367d31ef1e7332dc1813070290d7544f5341852e0a6a87141746fe8730859f373d64143993a1d33a79ecdfaa7c1a94610fa5a968cde73758298154697333eec86a1cec9e626fcc14ebfcfe8a2457737d623ce4b89389a21d6dde26537e3285791fba518ab54ab45d5376500b32d6cb7f089846ae23719c59f89b7f92c747671ddf2d57215c9055de4cb4eef0811290be08a439e17c51b4059fda18d626256c39cce2e2d65677fb3fe33cfc229b44a39617d2e992c3098e65074f24458c2cc73f4ca4a45609033dafdea263f84cd323b6fc988b0418ab3ccefa4cc9d7f2c7bc117935e4585d34365c45afd247084372c28598f0a3df8437bab18a88dba94a7c5198791a9f040b2fdd2dd5558ee31a45c2373033683a0e2a27b53dcef8958f089a1c40794b423bff96b0d7a68001ba412d2d0681a744509e0bd851f7777a0fc59aedb6890b84ec02f2e3db040cbcb108ee7dfb74b0fbbf6831c436a8385b051b5e5bb6f0b27fbc85a0b56b610f8f23a23331236126f280080e3c9fcd5979708d31c5b47d0e0e9efd1abc2681a718c0fadd765252485b4773748078251014dcc2565cfbd70e1dd8261b9b70eb51b1a0d283b0d6153243573b974044c97d7172f4cd7b3f982ac560795e5a77262e39d937132e7241fc7e12ff4dc9f789cf746d6ff22dd0808cb6bfddadd63f78f0f667b9006129cd185d0cb7682cc6277ce51c29d3f8775eb0d77e36d5f002edd352ef0a031772ea4d12d8c6a1b6afe29fd177f4dea51c72fb2636effe37625cf2e5283cebfc0320efef9ff038735bc2beb5cf9a6a684ba557c3b7e23bd07735f32a6a25082fb4dc81cc1b13987e904ec397bdfd86e625c1824ac655c8f4ed6b83f58c26ad006e99bc48e92754fef5f682e40d107774ecd36e2ded20f307e4a7e7731608c0c0ee0f2ac412b673af6512a19bdbace86545f297ac37e813847ce64ddb5207b5517cf0714bda73f9ccdf6089935c811b779e29547786e6d7a0c0f036f90d9c6add9b77ea2fd72df72dbb9aeeb0731c1feed348f0e17122e8e69b3d7e0b9bc9f2aeda3b4684f4c49f5a5eefd60a98d3ee0976fa669ba27e711c3b1aaa08331f8bcded1d9fb6c51b3b3ccfe5b666dbf1d70f5ee94ded4e227e2ab3ad333e63e557f757dabd589e8b0c6d25de2e11bb669a992a47066303fb2ed5ddf8c7162458087633fb20dde4ee9702770398e95e6f080a1b485794c83cdabb2e84f873e4adb69a6eab334c318c46a6fd3352f4d416c569aada566907c73a8b847ab50a45d2117ce28c82b923c429105585ab22961c6dea61dbc22af5c40fb1f0d64b159d79c95b7ea477b2e07bb65636d3efb1cf4b3771df7be3b8521273650663dd602760d400c87ed3cbfbde5f5f76f2f908103790b8fd14207d4701250dca78dfab091a74623735b70b95e3f696f5d9c7563b015c6de19c29df80744a8fb3d00aa80e5b039fb4daca60b75e446fe78381876231cb720af3b5d07c5379dc5deb790b1899bb3a98b5cee51e1a98ba38ca58eaf814b85742d8bdce0b6c72cb9aa5c7d6fabf30641b8b201ca7433f0758cfd170cf826edb4c616b96efdaf9cc9725a4732e020f3cba07b5b42ecf02333f9c5fe9ff50b612cf17b69985fb25056f8536bfa227f2a2b49df355b2661b874e954f32c669275fd74c98dee866c73f29c87b5f3c2aa2b7794b9c9b3ff3e93f30dae707d95d7f4089ee26b8a3e589bcd1d3848f70130c4cf0ca7e2672c2fc84e5bb35c1d9bc3e91393deb6385f78297edf3faf7cded8f397fc2dfe91b4ffeb19cc8bcbfe74b8c36adf6b3c2a85ad256fb7e3f30eab74ab2ade91d81b3c22df08149fc5e5cd96983e63c7e687102907ad4601aaf7982a7e7a8f5d738bf4c5da71b7c12e31f0e575d6cfcea10bb87a19d8018da61a7830541b7f616b4e7bef2e1c81b26a6fc0b7a1054eb1abd2bfd0672457190ff740ff9ea4406fd6738fe259a3cecb4cd2fdc818167d2ebf196d2c0347a7dd8b2cdd0ac7335b35e0cda4859c0424a7ac687992ed387df14f9d4c0ba9f281db5d3b422b6c613645e76f97b974d2fb9a284d4be6c94fa63d0c8dde717d5041964bb7c0f268b17403a4ef206027bf6f2c7d009c76be27c7a0fb7eda3c05d8bc4eb42567a31c17f40586bedc90b8b71d0bcec7d0af90a3b1e19355bb42da50ce9d0a9cb25bee8d36cbae95f4ecce5810cf35177631e33f3d99335f739e8ddb30c272bf01e4e3de0e93ed0287b0976e6c49913cf9c3761fac4a973a7ce9dba55a67622aa57ac59b7703c1df119229eb69ff13dff179f48d3d013df82fa88ff981f44f27aa2d38d8e272547c6c8c853be48cf2573fb78d7e3d0c9fdb5c2fd9fbccccf985bf3ca447d116fc44cd99dfc7231cfb6cddafe6ce0773696cd212477ff0cd9abd494c7739574bb9db15a1e45ffe8c9f6335e8befc7f60607f812e109c4fe722f2f05d2446c7f50e9f53b32bdc79f05fd671fd6fbea3a9b733fd56451f8673f3826cf6b7687458bdee3276e8f8c557d6e8f983db5ceb161c5775b54f95b2ff1bf3ca1b35d0df7f7905f5e60ee624a7d17f5aa6da4defd56ff2dd9b5729a269678ce88993555b382f6401c76f46c3bb2fe36e073f29615291c9b3490e8ab18e7255c6c60e66a48407018193f81f18f8bbd49789a1e7b1eedcdf8f54f0a56bf2bc7fc4e0fb65eeced8bf2b0b29c5fe2549b415a1feb342950a847cffec9dd70a28007065f0f9f22ba189174c5d712b4c3cfa35760f81296453e70cb12ab75af7daddbbee0f82ce130f6b290ff9561aace7fedc013f854ae55bd3b92ff2ab44d993c54f5945833130bd39fde2af9364da7e5483f77f4449ae05f705ce3bd8397fa0614f78e777865d4cc065e071431d30cedc7e4450a0a5f0e6fb62c1e1c15e16b6ad789e7e34cbeea0972657cd23b1b117d17eb5e09a823d7021ea3d0df335b1f557e517dbcb083a712d5d91a231faa42ea287d194f451565fe322bb880d738fe2a35c18439ca1ccff5b2a5c2df7cf5006aec8685553326dd035c15bc3188dd519aff783e8fc88c50c42f79be4f91c22f219594e01a4bdb33d9f59ab5cb1c3edce8dca388fb67a4f541d67ab688acf7324cd01ed7dfb883db5890f26ce4796ae246ec8f9b32e0d03926fa9871b5b4ee1e5f9a1669b3b551732324a323726106750c8f5b6f137ccaf654afe906f246d3733989e7f76f3f38bb5db8e0ada73edbdcd2102fab8c6af3fd662ae08940df371d1e41db039cdd0dfab7f178bf7c813b7785f7c25fad353808756eb25ba89dd2f22aff74d62d2e76b6363303988f098dd99bc1e2c164262c5671b4a5e9a2ce0644fab1612f49dae6b7ffe5b43cae7e703d8cb667452cd379551c6d69081d3ac5c07f3cf361c2e6df9bd63775da2fbdc2449f6b27d7b535d3e4635cbb22cde263cebb0ea3b56fd146f96adecb3ef0da241ca735e4086fd0203c909eee011d3993ed9af56af49f6eb24b4a6cd970cb462dda376cdab069dbb4fee1b740298e17fb4e8cd457c66879030cd159398669cd8f83b895d21cfb9ce1d95f9b1fd5073085d10e6106da7f29fad93dc92874e3e7eebac4ac8595ae4a38b8b62cddfcf3250f7b22f4adb6a6ae6ce625f0fdbfabf970054e8370d7c4213bf1fdbd77cfd4015d49f3d08bfaed3bcbedcfe335c59e45b75fc7cd507fd0cb4ffe0db10ada45b7feecb3239cb9b2bdb927cf5cd06ae4e3a898f4efa7f458e949f3f242b887ec1a92cbc45b2dc6b677f23d6cdbe929dc350c8ee33ef4536139b8e1c1f228fc5bebb6db819d1e2f0e1120fa87eb7601ed4fef715e7a86b65ec60b09ec593cfbcc363ff73791fa8b5fc569e21eb9487d63313b8579f27a23e20e4a42f7c33e4f3c0c50770f47b2dad13916f03a5f1be51cc0ed8d185d1462ac759fd6d28679678487fa172ce58d8ef28d794473ee246ff0d04e5d999ca52d04770a8f3f8fb33f80e64cf9547d9c04b4779a6038a607d9cb10e978a269c652c160ed356d0b86711e36375669f3ea82b468fbeedf9db7c38c7b5f296edd947343c73fa138fef4ef48e227f0d6e55909bf3b1d16d9a00f679de161e8a16fe4cdd47433bfcd7ea31326776e2cf09f31bbd8017a66dcc271b729e55657f7f08f3e491ba8738157f6bd864f0577c60684bbe6cd3f1dfc2bfb543ae8e308f2c992e3189d1be275516a3746f646f47f7ccac3cb673707530fed1991e0688b4bcbb806cf1357babe8e131ff6812ce439f699a87909be67156b3065609248f644415b98fd2ab07532a7c02ec23fec9a57fa297e88b8b310d439188f9c7f82f5953cc7c1ee874196e80eab5c4b3da3fe1b1268e1d6b87c64cbdd7544d7eb7682fd3ba4b3e6c2930610fcc28fa59ee9ddf51e709c8a0d0e376da4f3d74f864bead734cd9093de3343838e622bc3b95a1c589eb1ef2c3b8c6f2863c731059e2fe56999c8509e73c0c2968df5110566e29ff08d1dba8be9037137c8769e0d7fb685da8e12ab8f314b41ee0da682155df356703bc682716f41f7bb20a174129dff3188efbd6c77dd21123fa3a4ef7068cfc9ee9e7724cf922061c6983907874e970d60fa42871bd7c78b51f78ee2829f0ec484234fcd75ab65c9295f40f20654f20704d66745f78054ed07755f3cfec690ff1c5d1eed349ebfbf249c7d0ce4dc354de8f5c1dd92fb282a7d9c2d0e7564187997d04de89f6f47f19e06ba0aff15ee2cfc5bd0bb90fe021c067d0c7919f633280fd6e6b0972793395bf903af4ec31269f968c885d5717a5cbf3ac141ecb655eef377f2348fbbfb40f8ea0b520dd223b4e38cb87594621eadcda8a63b74af2888354a8bd08e33e2b6a394f2603da31aefd839f635e46029778707e061655aa95dae9c4bb514345088f001860c1b3460d0b035389617e87526d679444cba4b318b466657d315ea942037799c5ab7a8ef46f6ac17a17de690c86a284f3de91d3760a339dc900e06fb359ad7338d5cacf84f4c6df061ca1ffdd466421f9c2df6540b180cdc7ad635539ccdca0ca0f22a9bd1b20d5994b6565f25f47b4f05d02b2a9bc518994d6765a12c348f2477132a79ae9f79ab1efa0096960d807d89122fae382c62687f8b0ea9c9826ebdd1ef9f569870a0a30f361a559ee0faf9ec112fe58d869ad19a5bce9b2f9291b59e28c2ad16634b9b80d8c8dccdd99c751ef7c8d8ccc549c6afaece59fd5edf56b2519f7cee41b14393568649ccfb53726f92e6aac1ea782de58563f77f46aefab062b3aa8b564f50791042ba88c47672f3ef2069d4166a89db7aa3a9a12938d556bea60132fb0f64fc0d9a7b1b8f770bc94902e3ad7efd713e57be24d9c7ffd7ec3ae94768a9efd8633c301fee14327aea10877d8f437de36625dada0db4c369cca3c7f4f772149f9eefd1d149ab1d23cca3ece25b4ed2880f02eb5b21069f320e3dff32ff0dc335e2fd6ad7ddbee0c41fe07da436632277c794f0c0f0d4d914bd6ed40d74e774ff080bfb73b8631f67081d9c53299bd3cdf475e0a6e738e6bda684d1a2f67c1787b1a3327233e3491e9d1ad513da067834cb6d4d01f96dc4601fa9f792cd0b37e7b9e60da4ba67fcbd798307407ece950e5ac262c734f60671b898a749bd068edefa26e8a984bfd4b770e82aad1c16bc3a125f3d86948c9ea236e832be5dbdaa70f4d3f914fef773b13fb2ff5d184795d591dd9e1c4e207af7a8add6544341fad4300a5f0d11e962c38fff61c36fe7b2e1dfd1e5dc9f750364f3e6486de178977313564e4f78e1eb45030d091dbb99b81fbd89de11c0f31ef02fe6067d9653566f7fe1ad97f16b7e17a72121f4532b8bcd5e9eedbb4fc6f31e34aee558c5b16fdd343f980bebda0096d20530fe36927ecd39d77436e8253a8d4199905b32876f2b5a929b9ba0d7732b74d2c791271eb47e8bded1d5f83617fe61f95ff8a0ee36176462614c6776c20f2fa4f2d8749c5d4586b076fc2680d6e4c1af3d19253349e36ce356685217eab41a16de49cd0152e305a45d696d77f1cc61a368da0de2728dd156ad6d6ad9f1a7705b9b4287c749a241c9c44cbe775694f3ccd2ae3a29ff75ed0dd67c966d94b96d28a3dba356f47c03e9a8973eb1996f64643858e96c5ac2bd363baccd480602487ab5c9a57bef8c9cd9cc1d79d2954726685237ed9175df532c22e37be86d3dd2d3dee14f6799fd4a7227b236079a33f1652293b88df19cd9eb9690bef170378ce69ae97232d46e3bb2744b0c2c522f5825b7a0eb3f560a19adf7f1a7d33655ca9a3b7a20962bccf9a245e6442fb7d74c7a75c6e5b06cdb4af2b1f7618c99e8576d86eeb3af8cd36cd1129a1d887aa0235bcab2a3c67067794e405a87925065b8172cf5a56c99a9b98f4ef4b487b22807cd8b423e9b0adcba3971b51206abc927ae490ddc2ceded86f3ff4bcab34f143096d02d2a024fb72fdefcb76811c7afd45d58fe89f3223737a5732f023fdc9dde5a2a75706465465813da78736dca7a68ed17d3d00ece005d92ce0139c28c42e7ce0e053dba4821ee6d93e3d8be2adbe71c99c5ab7d9955473d3ba8d9818557c66caaf51db1fef99a75993d73a054596bc414ee762a19bbc69aa64aefc309d6bab9a33326ec24c9d21d9d84991ff0b89524579a8c5eba4589bb5e5f6a9c6afc93d277527642192acdbc888942d3254e6929039350ac2f318d1f5aab8d0dc524317823b3c8c68bc848ba6f7a96893bd91a3e779496887b5c40f2defa7031e5169874597bb91cfa8df21f046443fecc2b43ae6d60288a19cbb868a9cc19aad6aa8536c57d8bab54f73f94a5d90145b1063b3f97fec19d65a969465cf6aa5d14c73a9f16ab672916e1cb8b98a2d4a61fe14cbbedc740ff5ddd182e799f448bcdab964e447ba7c04d52dcba368d2232c2d2225c56f5ade410333cf2511bcf95c6f39c98f6709752df7831df5c0518ee336257fa638cc94a7261916da3463734cda09491462a7ad7f69d312d370ae8ff072e876875105c296606855bc6261f7a430680ac7739409ae75d937f457f40bc09d9b053133aa3b7474f6d2645e8e5c546fc83feb91faa8fefef920b0c820f5df018ea6d92d25271a563bcdd81d000ee9b024459f64d853536c09906e7fc76a3e5ad4c4fe9ecad20fb7461643ae95a03c284662dd53544ca9abd9b7aaf9f58348d8994e23186c94d6c4433694b2511a2512a57d2168b2b5d8b067f77fa4c14f1ab83757dc523a198cc99f0c4fd816b2d1ef78762fc92882dcd1bc76f253fcfa07a61a612c9d07b9da12c3f159e99ccc2a1a7d7db24091d4dd829390140fac86ed9345314e6ec789a64df91de8bae3b92e0eb094ceb69bd6cb1f3ba3b06b27922838d62fc4b2c4cc9a155eb27c417511b05c9aaaf91919f91d8893162b3373e4c62c122f14261af6d4a325d86fae1c9fe05e53c32d1f9cecec798a37e28988a53175b831a81d41761b60cbc96deda0e509d45777c3ff3070083cf9f667739b8a2763abaa59120f8487f4ee54e2e397e3a057801d7ce87489bcbbee3c64f8fe6999e56b665629dcdfa4fb211fb4ecc745e6b454b5173fbdae5e86e652f1edeac05ea3c92537f568624f1d0feae19ccc60200e04b20d9a80ccd9a9d4c4f30d52fe0dc43ee500cf3b09c765d4b78de4fcb4f0c47f9d2f740e449e0d558e75c46b4593cee428835c0651ce4518df24193d4f4e3d485e8079a5a5b95421fb0fdc2ea9cb689cccb7674978ddccd83135dba9c71e610355d2b701e5392b2ee67929fbe90e71787258bbaea596f2e32d23933b756fc101e4aecd7137af1d7e6243d406ebd5324462d40129a9b80becac4d9c375f130aa665d8e4808be7a5f6e8b55a3ee8cfad26b7696b6685d1a64d82d9f44a500ff4b8b2326badd96e104c8b0fc4de6b6f6a72b94310e1dbc5d77f98fa44784ec2250253e17bf5b5d559b0ca9a06dc31f50969764ae97825880d595677858bfcffd7387bf62eef8ef843e3ccfdbfb8f0d93f19085873805b481f3751ccc755c19d14a29f793977562a1fef4f3fb6434926c2390038cb62b30ba6a2324122366519297bbe07b75ec0f99ba2473dc8e7b1cc83f4d2de58154f84fa5bbbb102671e631f214b605e4f3020ea2841c0c3c60b46439330f0f0f0f0f0f0f0f8b036e24596413d22ad94d2629c970b335dcb65369c4cd24534a29a5247607efc2eee01dbc8377f08a209becbdefdf3f0c010cb60be711a9b5e517cc276a8557ece46b2af582c94b25a17290bf0b26155f2da71551a74c870b86f124f673be249e62be05d397db87935366976d2d18b353ee967f993a979805e3df2bad7527cba2f6c28259bade8688ac6d71cb154c5ba6da673f0aa52e6405a33ce5eafce4cc6e3c5530b8db79fd0a7d2ec3a48261a4860cf95abf821a794cc13052a687efa465a689a5600a42d75b5cae3826f62898eef5ddf694502533858229a9ced676e923f27a8229d72b573baf35373f24d709070f279873d4499a8e5814192f748247134cb6b2d24b9cc9d1156582f9821491a69349957fd2259852963a05f76e5309c6ef509dcc95a9602ef48147128ca22d6d65f6f9040f249872abf152622c2821a48e60d2f2df565dfda5eda487118c755a54d645a55bbaf62882f9eccc638ae7a0a7c41e44307bf42ca4922144baa80cc1a473d433a48a3aebd196f4c88896427808c1bc6e41568a7d10ccf69ea2b9f6179add7900c1d85a43b9ce9f4c9abb3c7e60707d957715ea936c95870fccd2e3a2493bad1ff7cd2069a4e1d103b3ecbd0aada3b00b5aeec10363b87c9bb455ff5099c70eccbde5a273a7ef26521d982c9b86659da32d3bca7ee09103f3ad7cdc92cafb7b4f0f1c986b47aea7289f1b984c69a83d39ad83ce52363086d6ce0731dfd2c3af81290a5de16d4777060f1a982b990e25c5f4eb67cf0ccc6c2957e8fd2a038349a54e4356aa60f93682470cccd9d392fcba510b6a1190e8ddb871705cc0051e30300a0ff15e734267d9b2c70b8cf1ab525d07153b6b210f1798b55b141e46b9e58f751b3c5a60564aa8399523f36081495b4646a494f244655678acc01c3652e4c34e54334e8a023c5460d6c29490a127f79d5ba930aed4496d954e273ac90c5418572b5151bed6a62c770af39bd653f3d11095f31da630fab7281b3b3331657794c2a42aaaa82a29f59c528e490e1b0fb8d1410aa3c8b938327a334e748cc2a0dce5e870d2a59fced0091da230bac9a6ae450b32f2224304c78c93c59f9ce4f89313244547284cfa4ea950556bde31280c42691342a8a936f1c925747c42157755181b4f7bc2f41adfe1f9b459ae64278cf28354faae79b1834a72e3eea48313c6f9915df53f6fc26ce1c7a2de11b929571d9a3028f3cba7464de851c14c9893f2f8bc26674ecd0913e6189dcf7d74f6b88a5fc2a895847aa79dbc00c1af0244b6b02201d96109e36db7d8d5eae91ae238376a98a4fe3483060512a095302511963f29113a77669430875a95bbaecd8267a592be1b3552e2c0822761f63c26f7a5676562250983a5cfe1225554f2cf48464678d01109f3c9ae15fa3aeafc9acb043a2061924a3fef7fd63fc2785e59dc9b7ca94d2839c21ce54917d3d9a216b5f9a0a31166d3726496ade843e860847163565b16d7734a7e42e3497098f45ec7228c264c46c7e71ea9d456d287c384a46f0b8ecf3143657428c2ac832c792df5b2bf1a25a218df4823c2e4bd2ac44b57897d9fc3c649c702056f9c1a7908a316a9f315d745e52a49eed8c081c5dff8620b1198e0c061a20006c01f3a0c61baecfaad72cfbf78a18e4218bf452953ad6f51c52684316ca4ef892d0d25bc931a690483306ca7a8defff00e211484499dd69ff1f23a9be94bfa484c72d8161d8130a85ce679d9363fa80c011d80306f99ab7e7edd4bb3fa83f1d5cc09adecdca316ea03bc28a14003d80f667bb114b4ea85116d25c064d978c0c9c971d4c951eeeeeeeebc34e7151a2011c0133afa6056ae2a6b28bbf041cbea021d7c309d8a5f3321db3d68a93d98d2a9c8987d4efa8457d27b397098dc18c15bd1a107d30aad742e579deff5dc9107834e42fade9f18cfa6462634b820490874e0c1f062b24bbcf8ec204a9206dca0e30ee6bdecb2294abe5bf0edaed00089093aec60b4a483ecfef78debc9840676914cace822999c9c2b344022828e3a986db5ec78a76f5abea783e994e5952b4c2cab94e21c8c5abaaf95b93861af2f699683b9557bfa96afe48e854746ec74d0110763be4aebdd1efa1d5ab8703005d7a77ee282d29655de6058addec2f6c2f977b2041d6e30ebcb5c2547deff5e8ac6c1d106a3f9e894f561e4cd8d3ad860d4f2514635557d3ee91acc3116f684ca42865a4e49a7a0430dc6f8cae29ecafb3ddd92bec542471a8c2955d4aea1ab957794e344afd08106c39d4b1d74dfdbc9703bce608ab7162b6f9810a2c20e3318dffe94cdeb747172ec59a0a30c26214fff84caf25955470683ca1f5dc839e9adf753d2e7c50949768cc12c6d45c79099154dec2c3ac4609413a65cc58c23c78d8e30982bbeac57d915a46fc5111d60300b8ba2e3ad48cfb24dbbe8f882d995e7d7494a0bea73f010f534d0e105a3adcb8aba4a27253afb27dad105f3bcc751e9937cade22824e8e08251e924e3c2e8b0132e43945e58a1630b263fa13b89d616796154d297a7a9430be6babca7bfea727736b01819b16142858e2c98c467c92d3d3ba3eac182d9e5a8242f7dd2422b47848e2bfcffa71694593aac605a17aef55392de328d53e3e060c095a1a30ae6d75b97a488f3a49c417272f2890a4619ddd51ba232d49d5a818e2918f566bcb2ae731d6d96f4dd69d1210593cebf341d73c9fea54047144cda83b61c75aca8df5fff6b64646484e4040aa6242efabeccc495e9559f60d61815d54a27e9abb5994e30d9aca99696f2d2e86047134c51448dfa14cc2c7f8e09e6e4aef5ecbefa25b1e61a3a9660d2d17a5b285579bd6549e3e5f9073a94601639f55245f582c60d1c5e9c90281b5eacc7a187818e2418d34ee46a21df2c6bdf8b363a90605233e579a23fbdcbb4a42f470d1c58d8781c270b921a69042c878e23183f78a715a52bec5436be140d0132a0c308069b137add2b9636d1318e1c373a8a600ef3fc38beca6cee238239fe270f1d2a423cc5390493dc76153d28132ade47219855c79cb29c933a8260542a677f6e3be5df4240307efeef574aa9e41efa8e1f98e3c8553e42def8bc4e870f0cff42c8d0c2c7b2574a491f89892a1ce8e881f1f5e5fd53572bc5b73a78604c9d841ef1a6337d2ed4736163c6e20e0c17b4cfeaa45fefc3a84307c64fb18465a166eacdcc81f147997af8aa16db5f070ecc292a3b15efd3a23abba42f045d90acf33448d4021158e7ad902192c30b098c8ce0f82e709c1aaf4cae1819796562030370878e1b18c4c9d54137742a2d8f0d2e669cbca0c30626e51d3e58d0a59e4d1c19b91f74d4c0d81d35fffe835ef5621ae7739cec896cd10013195cd898e1f8cac4867e070d8ca92dba8a0be3eaccb166949c90d83099b1d8310353961d2be6ebdc5298b4430626574fe929aae5d022150373bbae53d2a2d659346d134374c0c028365cb4d5475d2e53257dbb828e17185f48372d3fdc2913a792be1c336acca08123c7898d198999261d2e305f586d32948ab72afa46e868815985fef55013cba1f5383e87175a88c810b1e204c78760c602161638be24c70c563174b0c0f8e53bbf6a313b95540e111922225b582172c549023095aa44c70a0c7bda4e8b1a1dc5aa53870a4ca67d173b4ad395334a853956ee0edad5b66b6f5161caa12f5cd651a99c423f85b94ca71e8f59a7c46e728324c749394c6c1c8a6cd1801be71d202243e449308302278bc65385290c32f4c5128fd11b6f6415a530ad694a0ff33522457232c34ffc115590c25cca2c5afcacb592da3061f55cd838255850c0640b131c38ce152323364c6ad430492427558cc2fc36db79447b96228bc2a89397a9cbd794e25aa130ad4eb6b1ad464b295450184ea80b4284cadf513d9f30ad6a099de2a97e3eea09a38ed3d2c45ece4a7fb0130629434c77e4a28a25e68441dd2bcf22f46751dd69d038242529c74123238b882a36615057adba29b7f28585015568c29cbfe4cb9ad4f19c5a15993059965a895613b19863c2ecf16a51e5be945ef5258c2a5c5eddde9676334eec401596308cdeee278fa55045250cd2f35dccf7153aa7b2a48f29612e694adde794c47727d915aa9884410a55391129a45a9957210983107316a5eaa49256ea8c8ce865000e5145248c1f376a94ea5c22643a038da00a48984ceb84e5a4c509b5aa47986545876ad5b0a42f4fb95c04aa7084599e88accfa95f95fed89871723c0e0c54d108d3cab4972e9f3d33b42a1861562adea212f922ef4b3aa86211a6d515ad4eca9129d58585c8165688e43031d182ab5084f1cc72f672215ba2748930aba8173d4733f5d3458459bc4ceda9cf93adfd210c2e773bc84e51c5c7cc10060f2fc654f4b4f37faa2884517baa453152a5dfb6364c48ec8a9b2a08617629d2bb4c8eb66fe920ccfa65ff8bfd8230767239df279e5dda2510e638a95e0abd1555b7632309d9a24d5c0208128c97f3aa5acf954f8714821cc1205bfbe3d3a5e58e006204d305e1b99229b33c17562b82e9c755846fafa9930b110cfab12f4ae17ef3b9dd00640866114a0965da3d6b6d219834ee4babce7ecac91204c32af962f6a7ae6d424030c90fb92585769ef9b4a80f407e6050dbb1a9ffab773bb301880f0c5a48bdfbdab29bec9c1f80f4c0302b5369cfba5ce854391540786054f7b1f2c688d888d20e8ca6dce58d7f166762b702880ecc1d5f6f34b6c402480e8cd23f5816cab3bf833e89008203b34ada2cdb7f52c13be70626a5ecf2e90c7d424f2d02880dcc2fc2f76d4bdf493162442780d4c020e7857e76ad742eca6fd438394ebec68c1302080d8cab4e866efa7e874e6e184066603e55ed526ebd568c2e03b3d0e231ab4ce81d211303480c4cf1546ae1af4d69955fd601080c8cf7972b9afe3b31957f81d1e26d78abd1a182bb031017985f74d393129e73579ee45000d202734575714e25a53f68e1165688a8018405060f72ff4e5bd83addda14405660d67697da4548e9f15d0e405460ce7fe1cd538ea142fb51f84885f1b427fd9539131b5a5498d3fe64ea4a42ab522aa730870f5acc6a791435a9620ad398bc655d27c5c505abf0510a53ac20669530e525949814664b5be31e4a2bc52d0b9bc2c7284ce2e7f5996b2b230b1fa2308e4e69a5785928cc4a077f25fed427f320509894c90dd5d174c647a54f98c7435caf5e4eab6247307c78c2e029ea0f235a76c228b255ca9f7a5db81c4e98ec5f978e8fd838d1da84e9d5a8b113f23aac14e262c609c995e143136633d1d2d6c2ca5eb965c2b842ad788eeae5abef31618c17426b88370f17fa91914b18352c278dd3df59d29368098392972a972c6172ff8a6c6185c8c8c847250cb633aa2c9c76e17d213995c107254cd9b5d84ea6bf43786912066f537bba535239179284297b6b559e75d87ea913912182e367e4b5e12312c66db1551ef2734ba5b938392860c364860f3e2061aed75a1d649b8591e9238c3fa693d7df99504d86880c768449591297cf65e6a78a8d3087cae9adaa839d698f11a6d723c3b28d6d29911de16311e6539ab11ddd83cc17528451a97eb1f2737b6aa824c23c7e2695d0d0f89223224cde991ff651a5ac750ec9f51b3e0e61b27cab4b5c12d3163d8630c95cd0f64994be284334283032b226344e8a0b3e0a61d0f9fa44c4a71c634c84309ee9aefd55ab024464882440240b2b44d88b8f4118c34e29aff53c32845210e617723da6c77931e705c29c1d2a8abd6535ad5c4018e33feb2ba9a62fe9fc1fcc5b42bd4aabdf4c29ad1f0cdae6e5315bab145cd507e378d65f91f18be1ca0f7cf0c130a273a450fb56775a275a8c8ce4b0f135be3837481885f0b107c3760b2d173deb984ad683d9b3f8b2adbb245cb43c18e577941ec5d3af765ccc0b1f7828fdf76aa91e5e2323232324c71f7730cae9e7eced0f774a680b2b98f06107b3d21d3379ba7f3ef53a98d743659db6ecd257490793744b2fb58abdbd2edaa1163ee660d4902b7264d8ebf8580d3ee460d09f3aa5d54f4abac9e2600aab3d69279d72b3da427243f88083514fc8fcdcda7469e939f97883299e56fbfde2d9ad633798a50e2df7171d2d556983b9e79508e11b3ae5206a13c3071bccd95c2b1d4346885be51a8c5a3fa9b111cbed42c8858d19a806a3e64a37f16fa7b3b34c4c70649177e3a47bf09106c3eaeaa496579bf2180d0615d256a7de5b2197543ece60105a84eabb089d524a3a2c7c98c1583953eb8c8f7557e93298544af39ef5aa71532a32186ed545c898b07c523606b369e1492f2bcba245c52706e30b153d69c9abb82b2609835146a5eb5367ea659a9ae44e52f0010673a96826d685d6aaa54771f0f105d3d6c512d3975fb6de0573f0e105b390b1cf2a7d94ea32ee824946db5c97947d379b123eb860986df7fca4eb53f5fb838f2d98b7e2afb29f719ddcfb935392457ff1a10593947bbe666b1ef54a838f2c98858ad253bad2a53b4ab1606e15ed5b3945215b9eae605c7d21435a5a0f622392e3c601840f2b18f4d89790aa171b4f8324473a74c047154cc964fc4ba524e5dea682416deb20d5d86ccd8b171d3032d22477b2370583b0a8bfeea2540a26cf32be3cf5e5aadb3ea260f04ba3df95942beb23281847cdf5a3eb14af35a4616282f6090615f5a9ff1cbd57697ba1dbc587134caf665dcd2dabac433dc04713cc725b9e3ce92ad4954b722df8608279df73485d1b61e6dd271b265714e16309c659ad822cd793cf2b2518b5541461ea3283e4ae9360d62e94f417efa8a3ce21c1a8f696d3a4fa50f07104f3ab27e92d75f4c30826bf74faee430b731116c128574ba9f7729a970811c1a4a4d0290b95a4e8c7ec63086691afdb5a79a6a310af5ea10112087c08c1e84aabe5a0bbfd08023726abb4dfe21f4030896727a9e3c892f2b99293e3001922bc6822822d14b0802f7098d478098c8c2045c247878f1f183b6cb8f4d341c707edc307c6cc5897ea6941ed453f7a60d47a5fc91261a3f46debb538792e66a8919134636444051f3c30abd737dd74d579fab7437cecc0e8655a29214d968df498244017f1a10353f0d1d7325fcbf3515bf1910353ced1c5c377fdc58692101f3830aeb2202ec2b3cfed5cd2473263ad1b351ef085024e4e4e162348f8b881d93f8a7019bd1cbf943e6c60ee15fafbf5a7f9f0aec6127cd4c03c5f41ece6ddb3ad94b3e0830606dfdbd529a5fd9455b632b0109121626ae030f998813947e68954f2fe908129b69273a56a84e8942ae9d7c3470c8c2a4735bd4ab7e2770e068693a3cb82109e17185329aff75851cc48f9870bccf257bb70293d2e97ca470b8c3afdbc9f162b3df98b64c6c8887b7bb0e18305a67c5acba898253fc7b102390e0d068c8cd8f81c390e8d1d7cacc020647da796ce426d941f2a30ff8e25a1b6b3948f5cd267e359e0910a935211dff69febc40aa1c21cf425532e75927aaf750ab3c7502a87a6d232e71a1921394a7898c2a8b5da78ef97972fa74adaa314c611d57efdabcab556521845bbacc925757e73de62e1310a63ddda25d3979d063c44613859166564abf55f7128cc6fba5afe8828754f1fc0031426d5b9214d9e0e91d57e17e9e0d882027a24572e121e9f30a9a0b3abec31fc5f7a3c3c612c0fb22fc5aba04eea3c3a6178199d54482ff5b41c4e9853258fb66a7e1677b50993ebafbee0db31b4d7257d232324ca861788f0d084f992cb3b3127f75d2a65c2a0f92b5669a595fe620f4c182eae16f25a659955a24b18bbf3767ccd197d5797b4031c20224344013f03c7162323232324374ed0c3120655fe76d95c9f47ad37342cf1a88449ea381bfaf4a7d8e77b50c264e2d674daccb7acf54998a57d52ba656344ebffb9f09084e1540a9d852a5f21b416071e9130f6de5b7e991647cd08ef7ae00109737493de292be6694b7984d1e3abcfe79fede97384719452714bacd2a31106576aab837b8b9552278706498e19346e9c9807234c5f72e567517b51289592be2c5ec81011c9428b2cac10c9c20af4588459f7e3be54e95f85be4bfae49f012323fd858722ccc1dd92aad0a743b94a224cc2a4dab1db4ccbed418451f3a33275358f52bccae310e6942f225bc507f90f7918c2f0299a5c71ba97f4d90d8f4298a54bbbe0adde2d9837bec851238d209110a61fd93a27a4b8961a478e1b09028f4198e45edaeed43d8bc261b248f24e70fc4a18e85b1e82306ec58bf6e172d495f21a0883d0babe9576a975769580308a16d3dfafdd6486897f30e7f3dbd2ea65f68cce0fc67f65aa63399dfc677d30aaca5572da423e60de8851df7b7a954287e86ead073cf660d2b93f846cef90f1427a306d9897ec5341cedf9807e388be5cdf2bda73943cf06094e24bf6e7adf8207bdd80c71dcc4157b09896e58793d30e46213b5d5b98bf9d7a3dea60f0ac0f52cea3c5fccc830ee6ac6d5cd5761e35cae5310793a5d5624ecfaba724f49083516967f175f75a6e8d10e360ee8f6d1d5d76555c25b782071c8c95ff44d484d4d1dace7c835169fdf1be3e280fadda81871b0c2774f62ef9524ad8db06b3ccc7bf2773d5e7a2071b0c3f6a9536d5dd1acc1b27ed3fbf6bd3545583415f165ddac4cacaea330d06d392c2830c1d2ee720a2c1f4af72cd5cca239ec194c543bd565e4bfa4f33983ea70c5b7996cc3218b4baa0636aa15daad749287890c11cafe39fb23c42ceca3cc660123aca0a35e7c2550e4a31185e2eadabec327514157a84c174a6765d7e4e3afb3c3018e795526b7f0f04b3a7b99cb3c5b99cf2873808f981f9e3c717a7553ad7a286f8c0f897d54a9553a82c3fbf4908e98151cbf90a9e82369db287f0c0a0f58be5d7e97d85663b30aad6a7f45e0cdfae5507a67071a4d459e3b63f290706255bac749bd55fb775108203a3d2e9166577d4ab7e1c197142c80dcc314d8b6765522d688f0d4ce97b5c43fc43aa14cc42480d8c6a2efedeef6ca7b66960deb5b9fd9c55ce123a928b4208fb233a4a05219581795f56d0b1b5ce93150b8981f983d2d5ce7a4b40b9101898578a49bd2a8dbafdfe05667d79b2b30e2fc405e62cef3bc999fd18ff3f03720c212d309ab05c7aa72b8bc53b8405867b5f5d5be797cf43ab82901518e4e8f7d22dff54ad8e21401784a8c07ca73a3bef05fbff12622a8cd973fa39f6a70e1e23a2c258c9e5854ab942bb3e84710a831c7df6a6434634854108715fd3a73653b9104b61d29442a9a82d4597f2472485b995a9adf4ec4cf55288a330e76fad9a13fb21ef228ac2205c5be564f2e93c55324361922d2b4b37e34d970a0ae3f927252e9e2af313a613ebef2e5c56506a677ac22452a9ba2c6d2952290d46274cf9dde6e49eeb2d29c54b80c10963ca11e293c678be2894b909f388baebaa88ec97a90993e9fccb5dd99309a37add5acb351313a67cc1d3aaeebc07b9332f6132efb4f7d7490919b719018625ccabb7b3ceb27e0acaac84f1e3c9aab49f7fc5a59430e8603ac4985e17df0e8c4918dbf6be820c4f12463d42f7ca95a274bc5f482e6300231246d941b83c9bcd0b1f42c2686feeae83d4d2c95c188f309d54faf757afd6c25f3b80e108a39c1cef7c426bf92ba4018c461874bb106a5df4ae593808301861f64b7acfef5f949a6d0a3016613e5991b352c592685714617aa9e5e715fa55894b521dc04884416a9d54e547bde65a8908c3fbe9acc22e7608a378e5902a2e27af1c1ac2a0a221ebb37811132f8c42986407178f1fd9a36d3f31c962083008619026ef54562fdfddc741987c5cc7bffdaee897250883cab22a6e6a199ffb0b8461e75356aab796980c0883ce799f9e53e54e1efc8351c7be9a52e2b1b458fc605e59dd9d3f7f4b47b70fc6cba2c2ecabcd078334fbdf96f3a0d43e2c7b30c891afbc724e798f3ab92b020c3d18bb449c54e74908755501461e0ca63bfc2b71eed2ddc3835929ed9e5a47f9cbf512d9c20a919191be8361947855ed29b594b7aadac1b45eaed56bdd69448f7530a7eb8eb25de185ee9f0ea6a8559e0bb99694f69c19261620b91a30e6607cb1dd49e87b78adf4723066fe6ccb8b511c0cb2e655e925b51360c0c1245a7df4549f5d37a59393ffa5fc06b3c717964ecfb4fca66e30ae4e1685d6fd12d776ca00461bcc9e26abc26c7a865eb1c1982a9b96eb165b5c34d7e0e928746d4b3d3a393464b81a0cb32aa7ee94f5bf52370d66d73ae72f0feae453478349b4b57a871cf57ef12fc03883c9b52a9da2458751752b0c3398e2ddae5629e7c7fdac91913218a5ce0e52996c0b9d3e0787191939387e060c32187c476eb375e954d78fc1e0269e4b6b695ab25262309ea7beea581febb90a83694d25fdac2bcc77b06dcc60000c30184f56d5e9cba58b9323812f98f5be7608792a28254a2f985f55eb182677950bad2e986694875ea5dff2cd30b8603c9d7d1d4febb76014d53a532fa75b27d482d956b8d6593ca67e27b360363de1b9b2ea28bb2a160ceb6a4dbfd62623a3362bc0b88249e774f8a720758eebf20630ac60ec3265fe2a3aa78eb90d6054c1f8ba2afb45dd496fe8a860d4db7549a720afb2ce4dc19cc2afc8932fbd4cbc5600430ae694cf50da72a85739c28882c9cca4fc9eaf7200030a26a99459cad1c5b98efe04738c0771f12443c8879c60ce6642869637a69df335665891216817c06882e1657e14f75e519b1033c1a89d53ceed17ca63664b30e9d54af16f85cea24a2598c5c98baaee65b6cf97048358a1562eea57cb42878104f3a538ba8248cf15601cc1ac22e4b854a2b2c5ba8900c3082695e3af8f16b1537917c1ec61e9c3eb58ad469e3088607c9dffef598588db118c2198b2e957e6e6d25bb7c92780210473e76c2a5e25e923564130a59cea45be36d1d22b4030eb2864caaf6c7a36ff81f1454dd58893a9c557bc07307c606cd7f1f4a07e295a12460fccae55c5b73479e27306830706f9325e0bd7f19e6f547800630766f768e63f1f8490c2d381c1a59b669bfa4afa74726076e9d147b6694a171f83810373f44a6f7add1b18f354ee18a32cb8c86a03937d6a399e524b79188100a3060695b44c9aac37d38b5dd12880410353f28fd195f3c79ace6660da6f7fd5909d465c5c06e6b4523ea91effd0f363600c5dff1ec48de89f0b03f3aab5ac6a332a086d798139769454a55caf52cf61b8c09465b7c98a2aec6d5a60b4c058b759ee3682c1027396ed7b196fd970b905305660ccdd55b1b35dc548a517c0508129cb9f692584bbd24f2a8cbaa56a687aca7edda1c2ac3b7876b0a0aebe544f0039853969ac6ef7b1afa8575398c2a59caad38589709506298559b7ad0c53e976552c496196a69d5df9c54527f928cc1aabc25fd4332e36cb201185512993bb22ea518b564361dad650cb1784129643a0308e9faabca8f76e4dfc84f1855d0e2b75678b174c27336a289b01c413465953b9d2e788c56d75c2f8daa4f6fdcab94fce6000e1842945ade5cfb57e13c655d17527f9c1b50e441326132e367664f9092dcc84c175645dae17312da39111c584598a91f2773b35ffd427209730a8926fe73f1f966e75be2401260d304b98544e97f47818a41226a1b5efe6a952fff62b40095394277b85c8f10f3a57d237e36446b9131a33727861009049184e8e89d0a3b142255b12e61caadea59c7ea1a456244cf63aeba4b744e9a8363d8040c2a45b5a8757f37ea9553f803cc29c7f5ba8a87f940ea742b2208e305db6a042b7b61f0d0da411a6b09f5ee75c76ea9fc208c3c8ce650ff92c4e4b8b30e91db542ee53c6a84b11c6ce42c8f9a7b6b93c11c612d5a9c27938d14c3da001223244182022630122321061cefac5784ad333214e12003984b9bd4d5ed0e5f57bfe18400c61104fda5ae5f953736521cca7626448317ec9728b10a6d36622af55b4f0d4066196a93f859252aab03a250893bea84c9584091dfc0b84f1b3aae55c52a5ede90061f6ffac85560a96e4bc3f98d53dec6a997d722de607e3e99c42b547f59684b40fa6bc9f222f7d98acacf1c1a45d0857a63f8ec7f7604e26bfa55679e356957a305ffa512f375689d3511ecc5a7f4daa79794aa5762d00c183d1a4799a9e8c94d52e0b40ee6014b79bd5a25cbc5451103b18b41ea15ac7558c6d790820753009bdb735a3b4b58bd7710e2074306aa14a9db8e7e660ce62b792c9552a272a39183df5f96e6ca75a90c5c11c5bf4d7c85afff0351c4ca7fae9c3ee9998ee241c5e9850c086c90cc337184eabcbb9ae4bc8f4789901881b0c272bbc7f4365b7ae510d2fae10d9c28a9191126e83c95b7918b7642f95b8b0c1a8d32d8be52cf399a90c40d660164a8adc5739aa4a8e2323a8066329bdd71f9e3c223264546064e46d78d105903498b4eb741eddb29c555204206830eb931d5b9aee0ce6109784d29a9faf98c300c40c66af13294d7798015ad400298341aa75cf5aabbb5fdfea3c5a3218b5bb5a19a1c3920c7d016330777df65ef9293cee4705206230d6e9d0b39fa6a5ce522527c701242527a7c4710b206130ea97acead322b7e5f50210309883899eeea05c57f63b32c205902f18cc645db858adad375e00cf00e20553ca275674f46cb2716400e98259cb8c6f7fce3152c912f822c78c850308170c2b44a4eeab72a9e91ab923c92d18fb447e2ed339aa6a492e07cb01440b266dd7162f5de8fcce96c3034816ccfe297cb4c57ce99a8e8c8c8c608e11bc1d2040b0600ea354fa3eade4c8996e805cc1b0a9d753745f67874ff300c40ae6205e666a294dc8716d154cf2b27f5015945411a6d203102a98fbc49f9b149d53af3205534c91f7e9750755539282797dcfc395f8abb03b0ac6cf1432332dc907b50001040a061d5b32e3745510293ec1e0795de7ef2046944e2d08204e30e764e9dc54bbae985b138cabca558eb987972b3e49d77fda47cb7509e691e1db2fd5c90fbea9124094609affacdac4c66b11a92498774d8c6903a6ef91adf3898a290c675aa7cb95b37deeac1466194a4ab916f6a4de9b1466219f628aae68ea2e3e0ab396a1725041ec8577164314e6ae57eef5d93332427272f238b87c8183a4ecc949c60885593baf12db0fa7ea6d415c400c509865a6eb2844aad295fa8459084f530c4f18b58f4b51ddafa5aaac923e04c4e88429b6f6959582fcea396196625aa6d0eb2bd6d26881189b30bad23d672b65e99c865881189a306b28e129aad9eab88fe3dc505e9930b70a42a60b25575b0e3191e7f52c43bb72cc389961812c46464646f61246d54baf5b89bf9c3c9af7312c61acbda8b7b2547fa54e2e6c98d0a884e172f257a6f5f44ec5c6db30b931825709c4a084f1b3a2ab28a47b8d5763467a3509754e6e41ee7d3b99118225400c49183cdfb93a0f6d254a2b6244c2fc398970e9e1e4c9f89434c977716ad841c230fa75b4fd9b58bc0d2cb688f108a3d459ef97146b731e3bc2b04aa7d6dfb62fe676238c3ad52acbe9e1b4728d11c6bea0e3ebc6a7d21f0f044f012f3116614eaacdc7b5872f912f4598728aabee723717549f08832751975551c5e7558830cad2427374e80b0d621cc2dcae4677ec8a14751f4318dcd2ac56754dd5d94bfa30bd20d9ab21c2002d440a611ca95f9ee3630ec42084b1bea3691b51a782d0c51884e9f27d49cff22cd35479366208c23c2a459d525ed1098e93059e026204c2b0264ce57e6c907c61e330163100618e23c4a64b1dd45bc35331fe60cec136f62cb79079a75212c30f06a9e7c48486e79c1bc7e883d9c249ef4ce152bc8a49463063e11920061fccba3db68f16dddb2977b207f3beebbfc96cf9528a9563e8c1ac5a2b7ec9cd867069492b1bdf26461e0c9f941457c2531463ae0d1198d8e8c2861778308bf3a4bed2e7d82afa9319357263dcc11c7ba4be8eebe1839876409ba997d7c1ac93aaf421d65be67e3a18c4b52e2dd47f756e770e8695d7bbe5af75eea096832925954fc98f7437558983e995a893254a9569d5c1c11cc464afe9da7ebcd11b8c7af5827491aa7ec56e3077cc4551f14c5dba701b4cba2973eaf1b45cd53d0546465e8d8cd430c961e394642182186c30eaeaefbc2993aaa3bea4cf86090d93449223fd07904999408c35984cd8fe9cae4c1d5d4ab2b7aff0158eef186a306813722a09fdaefe212dce6b11230d069d717fee417dd536669074f17fa34f8b051cbec22c62a0c11c4bf57c6489331d544aface02389e467a454272e3c4066083186730a9edb9be0d97198cad4628552d62b49062198ca393f878d2f5b9f28c0c46bd23bc93163d331b8dc1ac9ff2ef8f7b5b90153198ef557c7fd912f623f2186130dd69dc2a79630c3098455d2bf1202a2a71717ec1a43e4ad379a35eea98c5f08259c7ca5adee2f3e8aa5958215284185d30fb6c9716619fc5bcd3410c2e182ceaad894fafbcfc82b805e3b9694fd9a42a86160c9f3d748ae87dabcbc6c8826153f9b6f8e70ea9322c1845ba8e23e4eac751638c2b18f4a557d97eef54523931ac6054fe1d5c7f9815ff2f46158cbdca54eb67cbd3af23210615cc222ad72a2d55ce4b360583489551520791da394ac1f4f1e24b78ca146af32818b5b691974c09db783d148c16b45b94bbe88ea12798dd2ebfd6d0dbabe939c1a83fc996a946f683184d30293dfafa9307e5e1d54c309a6e9552ae7d4f615e4b30eb16e6e23eccec694809265f75af5fa726c1a0b208a1a5c2994aab4782593fe91cbba4795d501ec1f44aba54a12f7cea798c60ca2a478dba56276f4a45309ac7cae1292d99782911ccc9bccea279967c960fc1e8263ee508b50bc1bca9c5e5a0948260f4d3af4a674556a610104c9762e914199d6abefe8139deee05315a5aac6cf18149a7e8175cadc89642db0393892ad9bb4a29b16df1c024b588cc16aab22c69d9815943e98856e562f95b33c4d08171c57e102e2b64cb28330746dda9b5ad367d9e298c8103a30a2a2b8ba346be974e1062dcc06ce7794cb63fe9be7c0c1b986d3c3f7a9b0a39425d0363c9c98a1f549eda3bc5a081417676f1a0d57495dc316660d61ed9aa964cd5183230fd0b2174cb5019428c1818b5eedaa7fb199dc620060c0c62fea553bc2fc60b4c4a3e5c709553af6e15c305062d65cd78d0e1b512718c16982ca71cadb6d2d87b198305a6eda85d6becfcbfd4791c2ab24503727871c3c60362acc0acfa1f6adcb538fe699ca484182a300a296b59aa5bb8b0da905418f363de69db892dcf4bfa2850e33c8e1c2730084185d94b86142f4eff684fe1290c96f6b5d66676318549f58dd0ec8b2a7fc410520af3df5c927a59a4307650325596b9c09b42c828cc1dd492dd472d947ece03ae509fe3e46d905c0f42446134f7381ea5e9a130fde9c5baa872a030cbd05aeb4ba5d38a7395b4046478d1783872dcc81026847cc2a8abbdfda59dab7515e209e3aa5d34af755f8f5a9d306e05cf346d1546ab16274cf25475e45912b7a80e81498dd7c206162232b208d984b15e87beac73be8f3fc78cbba7811c0c219a30ddb7d4df2fed0a0d90e008c98479851625daa1840973b9d41d37dc4fa9472f61d459bbcaa6936b09d3adcb89527aefa2d055c234ff4abdca15cb598bb1c710420973695579849f9330c910f16059376ef39584c1c67b4367dd4ae7c48891307b5442b7cac13fc8974afa4a727061e3699018244cca3cefd2b9cab93597f424421e611615bfb63c6fc70e2aa685880c119b80880c11cb4244868861212243c4ae1091216212109121625688c810b10888c8103108886461051f00d810e20883ab893d1db58434c29cb22e7795dd97a29a6684800c218c308c9a7b94adf128b72f6708598429b8fcf4d86fe2553cdd851045985d0ad51f5e2a8aff9d0883d6c9575c68931f2d8620c2acd5652d8492b6aa6ef5001b58e0151a2009001b420e61d8eea83584106af4d3104318d4aa88d314cb494a6121cccaa3d0caf93cae894a08210cabf2a37379d8a9bfb40021833077a7b4e2946957216e411853ed3c4b4b580aaf12085374f1dcaab2722faa94f48560860a418e1938cec8c809204cae83e9d6a3ae3a0993c8160d1039e9c2c4c40101b043c81f0c2a6829bf531017343f242439d2c19163047fc880103fa064e48ff872bdfb60b8ff913afb699dee89c98d344708e183399deaae94a3ba0c89e0e020493508d98361545512a66d2e5ce6257dbf67b842f460344bc1b43d9d8c5a561ecc9f6bebe26e970a3ae886175d989858d17508c183613b4f0a3959f9b55e4bfa38e40ee6b06e3185bdcdad4ced605e95a5c5d6f9d2b81092d830c907d81842ea60ce292915b466eddc761f08a18349bed0beee61e4c7d3903918a5d0977467a8f78b1f1b1f8227c9f1278f23440e66f16aa4e8eca1ed2d1d07637c5cda3a2594f8150a818349aaa9d44a6ca89cd3172e42de60d052cb4244abb87d4f021b587c21c40d06171b22fe46ebe7d329e9ebc2c4a484e469a81924364c7009216d3069d5df42fc7aa98aea0842d8605a79cba3ddd999f1cf682884acc11c5ef7aa54b04f255f6a30084d65c1e4aadb9c5cd2f7c5a1a16898a4130e44481a8c5a05f13ef2dee67f6486103498828aa1c3987e957bcf601e35e29229312a754e33189550612f69cb2d5d25f909216530bb12512fb4b6edf4a092beb481858c921c332e704283460244648888c810912162d4ee433023840c06d52ac991b32937db2de9fb1c2743c66052f39e2f7c580d9542257d8d23c78d1031185b95e9e0512b058b10120684885e2954564a216030abd96d8b9732c43d5fd2a71008f982b94fa8e67c655befd40b26bd164b4a796a53aad41787c63b6064e48b43e3c6c90c35822f09e98241caf6e82fdba1e3bb42b860d26f17722b887525a442b660181df5f2767d0e959e164cefae5268f5a0b4cc3a0b86afb4fade3b7736b162c1a063bb05afb41dbb735730eea93bf55539a75db782395e72e9a1a192da13f11707078e5323a40a267dd2555ca998ba2e8b0aa6cfd2ff54964cb14a680a06d7aa739f4e2905f30bad614998ce4fb58444c16cc9bc944bedf9ce567c8506483610020573f0dc7379fe4bcf74257d24666676225b582122e30e214f3029954fbc47bfb09e3c1ce204c3aaaa47755aab09267d419f85d349374b0f618251b43069675b6157052dc1782673e92feab6f59560f6b01f47cbe8778ae28c1c244014d9a2017b214930b9d2ddbf42dfcbd3291908418261b4a4b66e952b8745f036ee44b668c0851cc1fcda766fc6822146309fa97cd27d84214530efc5df12abb7f7511e4204a3eed52928312ae7772de9533466788156648b068c8c9c74616252e283902198d4658bdb172e7752a6100cdaf326737e413bb49020982c6abb6cf2d3c58ffda874b285b248200c05a21886610080e3e71100631200000010168e8503129950b0afdb0714800443362e463432262e221a168f48238150180c85430141201408c241100561188d7434466b001708dd705e364364e817cf1b98360d18d6d8c42d0c1f897e559809560eabf1b27fc43b8d85e27e90d44a21157ddbe961f0d245ebb7e344860a99a9484b4e094d13290449689f8ea8129adc251e0215ea148ab7c0504691152a37188e984a940ace33d5ec356ec460c3bb5e6354e02a2d02101ec63541eafd7f3b20ac62be7c58c3266a32fb93ccbfc63e57b4a5b435b99ec27bf21b63a0b94242677465dcf7dd82c00e0d04041d9fc0cc402726b98de0ef8e27b49f37370f31d4b36e3831612060e4138f11cbe07aeea821dd52c5ae978ed07a8ee595494b001272874ca5339ddce801ba569a2a4bce59ba4154bebe193cc89950d7572883833a2d55262d78aa37134250970ab216a431ec47b9dd7bf2484dc9c82b2152d748941fc2ada9a7c6a251056430e78a5fd154a53f362ab8d29b36e35172c187c0852b0ae8367881547c943e602b6b6d5dd71ef4f377c77e7d49a80515101a88ad9805b6b30696a5247c0de170d86dded7ffe2de6038af671fc7dda7b878704b9fa042508c78873d5bd305755fc3e8d87c029463d3f83c5c4436bb442e92e034052a96e02f64ac6e5ea1f2e3296eb86bd8aaff016a7092eed23d2b8734fa8a6b37e1d09b08ac6b4452b35a26a004fbb1a477ca79f11e20b4a963b1bb18bac546e3607ba91eb4f07a0513a9b934512d3eced3c57722604b56988dcae73c0cb4fbb8561d3c05be8f8303f6e7e6169eab555ca1e72cb034517fff5dd5534b58ae90a82057c1a784a91054b9ad8b7b264194d263a13e0ea6fc3e1c3b5add3f5b716a4d2aec14243351344fdc6496b28c189fda22ae9de4730a3cbb5c9ee1874b97f54255af08e544cb95e6a3cea3a94bd555d7d2bb0df7f6933343384827bea24e55fbcc0468c773014a5003b26108907158d725c7ae29d9894a250758d86a44c39b52b81e40aecf0bbc1d9793d0a2cbf04ebca5538458038f2815235289a86c206478a347ece271101611bf35efda3a4e0d67e50fd54899da50aa279322ffe08df84647c29d8a77c206003a00d600a4320018de33d489317e2bc6505f838e006aa79625faa7624f8e31820e56ab686ef16b7fa69e7afc2b747cf9110c4186b1ad3e8ae4c5c1f8660988d9f0e97b8a2a3007d035003a2472c9d78de8913959f19c6e8161433c394fa9ff9375b3d53bf959c68e8d7446fd1abf018b6173ae708fdd5d5b8d35df630c922e4fda4885fdfb206bb3830584be4220732d448e245221774517d5a6d361eb185c46d1a98b9b2937ff9f9dce934fa76ceff3eaea88945f3a422684a252f3d946bd1d5887f838181b2e8c2cf63a14031072e0fe395be6d014a06645dfb0836b057d6fcd6af07544df6b241fc2bf27cf10dbc1f4a8b255685a1157ab21a3eb15d7635989b2e216ff7002fbf135cb3c05eb987c4116db09c1d7ec87f4a1b715625a212d57438195e90c40d2aaf1ad40b3158e56416bc575565fd2ac86b87386f76235942a96eadb0896bdac7667659d56e965d59dd5d663356c4f4e315219ae419c2c06e5a30b83b5c6c403a19bffa11f40ef7f27738d80583645176f4632523b737af06d1c516da2cff2d7315301a61780506042154882140550a5b638cc46bd27e1ed0da7410a7f84050ec54b7f8864dda0fd8a0b4e8a986a100eb9e26321eb65d485b6c3f2fc0a697fb199513ce57389da81981301862a46d4e984252619f2f117291fc0abc265b01e9cb95bb388116516044caee5ffe9b4728151c575de087dc106cac201f772d0350cae16ee1de053d8b7c8abbac924765025136d3644e7e18127ec40440169f075e21f46b5cd22dd195309a9139de37805fe9aa3028fd17cb0fafba4a82212819a9f9d37e0c293c24e3958eea42eff4a808a257a732e5a71a5ede01b292f14548da85fc7000ed5b4c42678223e468f2226122a1615da40d8c8a91116f5e8a646497c692a502554df4ef21aa2304291f0ad7cce9d175b43d85b35dbccecda2e789486d3c675ba87822841c03cd16a86012397e43ed638a38376c90618931a2cf3ce3ec3246cf6481f30c5206a5fe243310bc44d80964b37aa0be2128a363da505fe56af7fb92e70404f2929f3549dd64539c7cf298a8c7f0c334e66e4a0b80e544570f4ae9e574c358a39e3b63b2ce0b522b74518afed81ecb471e8448c3a9645612d749b8b1b514bc19791eb062bd7f6e5ac6d2a7fbe73c898ebb5ffb521d2616ef6d23201622d55cea92bb878e786122c7863aead1e879df3940c6d0587c536dc3ba7250c6135ff09ea540546f25d62aa2fd84cb4f0a005f645c3f364d8279147d18ee7a9a031049d3fc400b646818fd653aa63010beba1a2db30a5daf7849c246538fed01a0d79a15057aba6e6829530ce45841f9387391243564ecb142a8c1b88886ad73bb38d122852cc9951688063f646c1a310c2f3fcca5970cf11fcbf2946722b1938c955461379516f29eb61f073d8d6d2e53b7839c326e268a28c0ee962dba7652da518cda1c25f807977243a19525bef860684b50b5b56ae29e4851c4a058fbb57d8e1161b9be1642cd7850a6240bb9a60f0ed34fea17f8ddbd0716578ad80ba7782c73e9119f22f718c7dd47a9d6ccbe848016ffcbcb774e56162f9f6cd02711474965a3b61003a28fd5b3fe3f2fedb52abf85f2aff799c1efea5fe21f92361000e7b06538fcce6da9bcb589216fae989fb230f4def43d11c9e14eaa46fb8928ad695b5bac3854f1088fc67b2220a10e8249368e4dc24453939cab50077198397fa1715c5b8b049ccab92966bbc8b3ee7a60790114e3f6efc6342f4fb88350db1a46e914f71869020ffc65d602e3b8272aacd286a9195f3539da92c962d483a87d830b80132441991305bdbdb4c68c05620628cc06ab22900c662a4bbc1826e96d7089a4ede615474662f8778b4d31cf9863bf9879b4e50203a2530b5cc4b6d35ddcb17b7eb73bd76ed81dd928980bf194ce56e8b378e9b38365a985367d85f88f5f91e79e835f173be1bbbe89a548cdafc117d504019f7e2df9c3b4a8453f9b1f90fd6dc52f9f0498d3432c2fd4626f26c941c721ce20c17c893faf9032623d92d60dd6f6bd070838e3bf55d5817cea118e6de0b1c17f43f57291187517bbd504c92b824bf388528e31b55cf66828f50e45118430b88d1d1bbdd4f9314b437b66912d8d4320bbbe945b43e38619733fa73724a10517645c8295799ff9ed55c69690269d141fcb86e5a92dad3e49f47448c3f4f747c2b252669081ccc037789fdb6a1698fbeebcecdacdd79ac30f458d8eab1581ac6da766a1b09ecb65c79aa5c033b266f2de7ecb2c49320513efd055997a1a3c62a40c5fc4863da29d161a87169348ffac3b02d7ca802ed945d0e3b6b511e76dcbea93576db6642272809f926332cc283e67069b904affbde0d6583aa55f0db03c0c0b5b53d7b6494c436faa2bfae9fbccbec6b251c7e56f2162c0f59dcbdc07f4049f0a9510eb1cdea2de377501d6528577519e629792d4520eed5a31e9893064c229a3222c32321db098dc64bf2db848c19e15f5e1f4c09fed951d401df9dd5e7605365804fdcb57390ccc661f11cf505bafa54949451c32d83db16f15e98fa620a29014001e032a5496b1cdffc53ebc83db92ab0dc6bff2570fec3bd04312c2c1e5ad65c2b37c51bbae059a7b55c4dab412771137cf50c22a16d09a7a6649a31d0f9143fbb5a1956cb2adc4a160080470232dfafeca1aa951b6d109129584ae672a9214e63c9ba2e773bfb40395b5604c948574b63344cdb1db6ff4732318dc34403a4de54301aefc3501dc850a387790045fd64f02a44130ccea03c5483c5282201b4333b50224d7f75e5af7df9d99cfe081bedcc7a895d39831e305ecdc85075d8707fae215e89298a51486b7992aa3b6af37bc02c766df2930ae9818b63a18309e393963197f676bbbdeb7724bc3a3f6f96eb5820464a806247f727061454df3164d83e0dae9bc28df47dd0b10493e21d39341b85c8e493a0858a1907c0ea68651ab2de2d005c97426b5ead014b88e49ccfe0b808d939c2abeed4fb6ae4bd0c628ed2d645c560e8f9d838de692ec90b2d9bcd6b8b9842dafba6fc9374b60078b45217f72c2d52981e719059a8b362146d1da96da12714b76794217590cf664291ed33502f91e6807a58d03ba4a3e9acfbad25302784dd045fac07df2e8a80386b8e8cc1aa529c5ed210be58013426198f058ccfa333efc629af2d40c96d292f5017f2ee2664fed26d7ce4eb76d7a595f897c5fcac51130e3271deac743a155fde611cb875b769fc49ca93bb37a303eacc6421573eb8851927540bc76e935174890faad62b773d48337aa61f0b5146b00bae790a4a0792b7f63035e0edee3879cbf68e262d432a147af2600ffa031e14ce752f1a1c69dbc902ff80f208c71f746953a3b46002f33750ae05207b566691c162a106d5c04019376d24293a73c3ff7592961c6e73a41b4710b2e8c4ea25c5ff8b984f1efb180e0011486026993923a782c558f2bf8a02a82e4860342f8d173fa40446436fb0a3c4def1899e442d8492ccc9f5489d4a76418e6b1434722202526644c9b99f6458483c29e0f7d0f326ea0df5d648826b8f377b5410728eebb2ed59041b505ddb541fc1311d41f4f0574835386475080ab950b41dc9962e9c7ee18daa82dbb0f8205f7f6c898c7cf1c3c0be5ce0c118a450d290836eb303f4a549846076c9a7fe47cf6358400218c37eee8faabeaf2a2bd812f64c8d9550076d5a703d21d2bb00777ed23ce00efcdb2ebb8d70e8b5935e67bd26931ad3d8a7a17383ebd6f2e73706d0e155666378d556d190d9e56b32f3f2cbbc5787f1056699bba11b82fdb6f9387fccf84c699bb05f6aaa47ee006b407f4508a4e5c9dc954d8fa95202143afb184ebd607805ecb4f8c18adb5e86a921809470df84a85e4e09afc11c4903590546bd5846763b4f57b082695c5e074cf40005896fa3873cbdba22e00ac7dcbf317a1d56a139211d205100060180519d8d7e4da02aedc6026316beace70b8d07869d50158ef617f2701bd30c438141adbbdb99d05d15bea3f032e0b7784c48bcdf618b996cc9dd3bd80c8f9a7bcac355974aef0c7dc2912a0595d83a2046f86811a2c740a4d46f1be315919339ac89e15994295265d48c703cc5cafb239c0112d9f10ebff244427061184914ea754c8520f482c474b98e8cd4a4e00b20ded42b1afa38a25a9d4ec75613a0e064e402843e7f1024e35b0479388dd6c19fbbcb102fa42e659a6c2385ac60f605c88720386effbe7d31bef5235da12df9c04856a5d3affab80c1eeb0419c918f27484c64f35b7337938c68b9554a6e83998757364b6da65851cb2085033e948706cc0f4eb8fe50d7ebf1070593f0030954e86aa9393656c6899045d94b3cc9fc6ac4c2899e6eab52b9bf24287262dae1fd33ece6a80d02e9231f3d9364e32b49055401fd8ea253bbdc663acf392ed6ab5fb32f495f487d3e1e5d70778ab0795afe1363454c893cc3e4a430d89422982e88dae8ef5341ba7125fe13d02b81bef17c0b66a9584e9b60dc793e9044dcccd293065720181b7e85a3ca49967fe4a81f2470a4c54c65143446653dee20a589839e575b493c4ce4c445cd004497259066cc13c79e8c220ebeb8387bba02c071031441f3aebd336d0e74ca827dfe9d061fb681471aa5cd5bec0710234dd2d36eff703f7c7ce5a0975fb4571622840022a15d13514b64802298a4444d82eae2ecdf8bbb0234d0f5f483d84ca219de88fa2885bf244dbf4ffa7d458a1588f5a17616cacadf770b222a76250e47dcc620e4b676e2d2a580855cc76b26720efc82d7a730d750e7c262c10298e81968054ddb4b90a2f827728d3fe45843e7ad188882d263977975414aae3312166d43b025381cc6467f03220f1def61cf2efd2fc4ca13995e5601706312056e04b881d25865e1cb58f6aa4a83a69055c1fd9ae47109426089d2d779ba1e5792f822b922626a398202ad05a3493448eec6999a390a69672a04345f16d39f459eb925ca8db3a9d9dc8de9a85e0452ff470089caea0441965d44bf8fafe9006d35500dfeddc2a0efb842905145cceb8139303c8c83fce1cf48518e417e43cd8560041f1c9bf4f2ed7911cf52587ff9801dc74c451cddce9f9187888c70f06945047e4715aed13724c2920609a3bc8ce2f3a80b8a60ec4e13cca90cea919968791b68760f60d29467b75ad124543de9c6bd9ac50794e52885b841d59a2ba8a52a1fb40e87389492e236348bb28dec8bafa3c71bfea4089d64281879e7612de501866c128202e004091d5409c7d20faa0dda3443322760be81179f076b8e18584719be745da96899f03eb8e0d5f2d7e24c8cc4a587ed9d9027475793592613d902b7cf041aa72b1e585ea2425236ee4ea6848c2835ccde8751746de9814200e6dc58306c7512cd994497b2138ef89902df96bf91cb3759ab8a294bd008bf56d983103b519bdb557e9af9d504c7e9ca260e542a4bd00524fba984b3c4ec66ae4a412398f38ad48f23ef49131db76c6acea54ce022c596047c0a851e25c9bd8a5518c2134ea75241f8d7941a0418128984bc92ae9a37db7433b3ca4c3335ea0a46a88e5e619118530e439e134d8bca7864b70b6285fbaa08ff387ca86f69930baebbd5aac0d3109254b0aa617ceed3daa26032cc5f17ae5e0cef826a0994ae5648f7bb1f4119f12be98a28c7e6e7d4a5a96263746c1b10c53b120299e24361fb52f5e732c26c4dcd8432fa65f5a80ee85180891a72932a185fa580529a3e3dc4c4bb3c7ef1352e2dca47d4073c45f24359c213a45bb175b11768442afb57a08780b4741e144ee008d1271d7f41deefeb4ddb28785a3b52fd771d27a4b27287715aa618efce77730e51a058e33df2cc4170fd9ab5cf28de71da517331a444905efded3370367d801c6be150a0532eb72840b719ec36d5a85f8ced0181a906168a8188a071eff3d1cb47656b674c70aff9bec67ae7291cdcc769258e1d4b99bceb469e458fa89fa862576ab136639ad41d1ae012960504978701a40c8e8e6d1be9b1c3e0e554091568e35caf083387b6a19f7c75b489af15054a9820905dcabaf59b049811060dcf6bc06ae76515e81f060a3ac08eda4e83287c50e77f37e8070ef883069a5fd1c57da35dba119a70a59868d43ab0986c80e4eab43215a2f166f13f9279686bf6cfccc4d46b50286160ccd8c1414bac7e73a2e8affdafac79f28588a6241282bc94acabfc636cd727e406d7ad6404529315e961e39706f20592f087272b1ab81ddd4d4e3a51de72300790d1d3eda9c5a3cebd358ab4514654f5095f5b73576991c36a49fe647195b479b2b7a58a8efb5dc7ee24aa6cdebbc23fa4229092316dc1177d556f3b1d2748c23b1143946838138b66b57d63c00879b0fc9666cec650690c7b6a740d2924a6d8278f3f1f0fe272ded871afe17afbcc6faafdc96f4f9b1a6d22cf40e44a96bd9117cf83c10fa1d04452992e69bf7f601c26c1b99ca6c6c1645629c1ad944907852921e3495b9040b6d51e333608d19ec4e5d6e4531fbce66b7ace6e90244c75e064d20cacf73ccfd815129d272e64a3c3329363580eb1abaf1eede839e198b4a4818a5b712713ec0deb2d476e0a440da3c5a84f4879a824796785a7ce68edabc62432d0948844f9ee5f1400547a230f5e2f53cebe3cd830c18aec21845ed9f5de358be809cfdb32b5abb217b34969c57c75950f9f5dfb3b9bb979a1104f279a919407b56ce2e07aa300eb128a628d1863d255a9461ce7e0263b3e1193d6200b4faa02921e587d253261125b5884c8737a83428cf4255a7b47ffc05b2a757d91323f7a1e8d747ed65d960a89f9b8b9b51ca7b762a049c02bac18f418ca6f8c04b96e204f74852829973bf7b10fd912bc61e7344df6fca6aaecb97c68cd9500ab102b269cfadd5c2f3b845f9444532590da2158490e0d443f9d32d673d25c829043d9183705b15c247b2c45ded1a8375ed0fe1d88249920a8aad4c13bf5d21996e22441e19c89a59e04d209f2876460dcc8ad48a434bce781b3cd7ce2b68b73c656a8b8a8bb1fd57fc342f1576f553f122169638b0201a7a56a3550ad2d91ea0980dc2ec8f3624d189eae3273de62ded9303ff52628c302c48265f6114b6112c4103b0caf3f498efbb8febcf0400783619e451f6abe4256768352e538f8258881734903c03c47d4fca8c42bb448c8fb30044e5a440d4f6dc318ec2858ac11eb340a4f8575416f9b0e9b3c79690a86041867d48058fda26c9f8f8db13e0bedeba7e7c95c8d7c87f939a52b317246f5887d30c7e57e1079c09c3bd7f9c037ac6e1ab04a92c71c2106c315cd1b414e9148e8e01940cf814d5044ab6000d5ff88b3db2641d75dfa71ea6a9eb13f8db8f3986b1e9b533a4f7362c21e62de376d20d80f84c89902cace39f8dfdf27e40ef76c12c671f40037d302e096c667a6bab339187ffd8fb5420acedb156090d755957418af750c764a3d3b7eda50dccacd8849c1e8217735dbef4e8663a23fa67c833b0a4da5e5540da2a86e107973347fe6caece97e1422f304688c841435056bbb7f7e679908f443bd6edfb6f22834f043aaed9cc2712eabdb3c06ce830f274b52e264ce17ea97031c4e4a2443d239261cbf043ffc296cd1254bc1020a1203e2efb4f10d3a818de24c98619a8969a2cece9419096f8b1387490dac576b90b51a01e6004058b4fc0e09b2cd67e0e281172d2000d0898832505a61a5cebdc9a20c69b64ebbe0a58f50f604b5bf769e7b263d4cd200797ac90fff5e0b14e8285b39ae123e05eca18d38432356212f3fad7982ea23b90621b453474d523c23299b1685f0fb944a095eed65344ad84da8a71f7dac192b271b0f9399a2d0e2eb40935fc63127e11e373e46c5448ba82928f056db87d89bbdcbd34577e5855e1c695df5e55620ce27cacb0ffbcccb22b497aeeda3338051665058c854f4f7f5f52fade323e2bc13fd9d4b0bbc7a6aa9ab3abb277d1cb435f04ff3e651da617cf16b06752602a7ada0a1512ad596854bfd8955a411eb76014115f619415bb1fa8972286fbc59494be58ac156200825aa84eb07d31d8c8d8e7cde843d6166ecc308fdecdb1ed6118782f7dc90809c81734dc2ca78251007faadfa563747da68e06f5418db4377b43c579ee5a36761ca0ea95057ffbaa57cfe04e3c39ac7e86cff5367b0014bf1209053f2111361b007760819a7d01a581e86402696485f0788a50124a0ee6a3156526c6fa62d59f5e1528a5402c7bd7d5a48e19258763e927d80d4f16d9a2f3b61efdc53a2aaef65ec81bba39adf59f6eda1e754137ca328e78f4a2ebadc385a070b552fd1d05e401855e6bb9a376a958376d550e43e87a2273d9ee1b910ddfb21384f8788f4916a6f86b8e20e2fb9dce2dc5142f0dbc48902f361295192a382f43a4b65a7ad4cada67a821a291b2e8262f19c206bc58ba4cc328dd2cde76860aa8e4888f19fa4e1703685b3f890cdb5dfeb398a3d018f28f3b832de9e6a78d5d6349ca6b41e0ac965a23235bb3924a234a589acb4677bb5689d7c70a0bec12c0b775c7b45c13b2eac9cf3b2cc75bd25319612f23bbbd82614060085b93515751c414d824a20314220912bb59a1f803e6c05bd617a3c4360a6a37793ba6dcc13e2492836fa4f0a48f19ce35690a28e85d0038a806d3023a079952a8963bb25592d1421660e43132293498c19cd94fdd90b562584abc6c83164dc18d6ae00348fe8bd6b6a34eea9c9f0a23d12b3aa90e820000ebbb1893937288b977dcc5fdb60a02eb0154644a3fae27836ddde1f81222d137d01ea5027bf426e32c31e89d423d57c78280489da1232d44c0d571138299cfaf1ff086c378be100cc697044d1b7d5524da1ebaea4b202c544152fd0c1f46b125ee63be605d5bf8598f006fca8c0fdded79e2e82e31a922a89afbd3b0daab8ba9b8fbd979ddfd901cd5d73abbb7ae0dc0e32159cf077cf3055aa6e9810de4ca0c7414cfb4e22201e3b7383546394ce24ce72e6baf8e39a93e94ca400dea757f012327d012a95d640554a8b3903acf4728774bf94c20a8b90706a78adf2d7ef0a77a58d0342baf0cea8567739eabb0d0900194dcc9f809849e69030a5030d79c044dbc30f321d3ff6714612f506cb59383da4b818d4a5e67a144a5c2b27928230e49f8ef9bb81e195c8f548cae54b5aa1552f7cd02df00a8a0c3e1cca05b0690c710d329f34f16da68309db9900327852506412d81436cc698c25fef9b225646e1e5a48a9340da50316b9adb63d7194266a7a127fd5fcc13602d200e416a2633347fd169322dc1e3e183f5e06ba4074f9387241b4c2d6cd124dcc787bfbe50f6a8302257077aa5857d25f1fb245e14d7ed3d727c946affba13e46fdf05821347a2c4d69d2963946251c93f3493d7e7d991573305d88e1dee9009ea18eaca43a51a770d100ac3095193b33749e30bc4b828af8a14ade32ed02f34ec65e95029491389d89452d31c90e3f5cb053ab166336696c464a6cadaaff29b9485adb39521a0349c2a4be70ad9690bc640046534abccb653eca4496809bc3f70450f491f980610a500150eb01a4bb01add9713b15bd8531d533eec4ba66a76a06d6c24cb0cb010b29bacfed6c09b5dc18eb970e76c1c9261616a544758fa2b6abb7833d618741859bdd089fe1fcd76fbdea616e2338a686dd460216580379097640844613f71753963b9d7051f4f4208e84310a4db7e397ab1f501da2a9af7f784eaefbe1298754d2412326cbbce5c7af1d344ff796af2d1363f657882fa2721f6a23c93d56f9d47930d1a5665d85e6f5607167404205648ddf964087d95989da38571278d268a8c8864bc04134901a838b48121e22a74084f1327fd55da837b82fd3870e2be86f5fe03089885e359284dd08bc545b8a04d514e84adb7ac00d09d625a92b3f486c703b3c2c47466efa4c7a778098ea80f0bbdec5c6e99ceee9df2bd98a8d08cead5aa1cdf3e0ca47b363a6b61066ce3d037f7e9043023ad223540605f63d8706251418d415b68661e35f6b69d7f1ffc09440ddcad13aedfe158d502371e9227da9462802953569efc0e889f3d608fda3c40747b578388a3326fc8adc25cbb14a45034a10de348aef853391b36cb0178ec4263d253bbe51b014db24368a3ccc7e07294cd767680515f1e58a0264e60c18c0f07de58396a85ffaa15463094c687f85fb176c110f017d176528b31bc4ffb5eccc40378c25d66688b3d0ce89b2278b8b4f0802d00030f2eb6475c843ca4f54e01e88cc4250ba094a18098382e8484d4ff58d566f454b411f2c297d39cc774504e76cad97a3ed657671642edc9830c19c70b9f67d6803e697e45d567bf1d3cca5695abe26ca789603e9ecdc35c245e62d562aa32fcec3b9140a12345fdbefc70a9220be0e2cbefaf5c9e7dc9d038cfa7009def7973558ef8f8e8cb5bcda43415222a50c80111f0629d4e8158c8782707beffb593d21815dfd679aaf552813a95b406735d192d67f89a4010b7053a01c1a465ce2e39374e77c29a27406580903a4f5af34f0d5068155cd5baba9862a8a350fa81fe2e8689de9222a2de4c2ff484a817fa4a085aa949838b2cc26b961ef7d14fd038225868b8a42de498531fa4eb07bee0ef6439ea37a7a9064e275437447e3e45d5e7af7649ca104a9f9342dbc6c37b41ce68b28119c672e315e0a36b0471d2efc19848356a5ba6156a6f837ae7386273e4f12b285c001127c29b40c63d6108f24ca93ada9220c00d8f216f794512aa57e653bd594fd57e7de85d8c00a8ff7d3e1a155237b42b926ee07dd61078617fdccff9ce6b8dcc442676a10b55f520ecaf0bceaaff338bef3b4c05e9eb62b259dfc396bc648e000e307efbe8a5861d98ea04356782195d1219b858542a2c04cb8b60540682551c12e6e22221e206b2c7fd9308e78ebafc0b1598726d2a8d99e1dc034e9c069df0fc019ed8732494184a7ddee070648ec7d360de8045fa77458076036a9b274a6244acb2ed9d4b9e88815106d9f4d5f59c4c992d9bb543b38e3a3a856ba2d7f5d9db001fa9cf92b7c28ae6e5f1065590f511b736b58769e30bbe11cc48a9c721f84ad87527c89a3b0c31ba7ed8dbddb72ac017c21de01398606d529837ca7f71dcd879e9d5c5cc1a7d1c749c93093b44be532cc3ff4b33ace0c54986cceb9d69e04b36625a6a460e6448a6c10300303edada4a8ee76ff05364780d36588bcb6ece501090bf6e82c3c10a29d3841c0ef2853d7ca99d28c495f3d66d707620403e018574626813fd0f86f3c22ab546cfdbe8f9149e7b353edd54670fe8195fbb14239296288100a6856e796f73eff26bcb37e40e1b9017a2c4084a11f469d85103815873e8b3d3e264d68a6265519b87ed6009a10f102ce30584430883798511816fc9010b22b0de1f0bf7714934ee1876fa588da1cca650d39bfb222eec2f6bcc00e315c61f705f68049f3023a5a3334e603273eef03f8aa7ed522ff182c1d4d61f339110aa1899291052884d1248c30e72d4616d08191c467064079b2d91e7e99eb2146a92864238c2b421696f41009a31a2449261de4088989130ab9e2242a1dab22449aa45d08ab68bb2dc3fa000cb024ae7672db6b59f24179092e48271dc16b97f4068f039c8716edea5d9c24a160cc1b3543c340a6368db1cdb6c1634113be6e07663e55bdbc6252472a4359e1d258100d7a3a9e21c663f806206972936889064c9aec64490281c60dfb02db48aab46f23b421245380ee2e320321c05dcfc498558d6cbb18879ea586fff804ef2db12894fc96f1ad66cb6eae7d593c8580e7ac8ac68410b5a572772ad00adcd130ce64a99855d9f8e323404c30a5212c7b4b08cfa2b6de94e61cfa92bb8d9cde6a87f73d609703f73b5f1bba5b00e9cbb176760123ed2201592464614ade90182713440ef68765f3c5b1fc7f944e3cec50b1b41c1b27805a823b08826889e2871deea55e80593daf0d9b372e4da74682fdced606a8f3d94d3ceb38571cc7fea6a17b2998902bdc7b7c4e0bc9e278ab7daafe7dde6c7b4c73c8de01be8c8355d033ff1b37fdea07df5fc9ff1ef5eb9fa01ba8e7d5068729652bc86ef28dd9be7b893f614868c092ef32a727ba073bf9807e29ca58aaffa58883098adf98b9a08e7ae1493af1200b2903c43cbe251af85a1b0189408cc910012300778317d64f5a12d68dde58ab084afd6897f5892e4903a483f5c2d2117336528aec277649ecfd52afb3581512bf3ec11d2626c9cba5311b8a3064e2cd756326bb91324308c4baff979d47384bb24e50545ad6598fe44c62135f5a6297e64c5ca0bedfdba8e22f80af97d8861ca700a0541f602e3938cb530832f382800bab23b46b2c9d7995ffdc4f47bda097a735e14a20bc1d03bd0189e65fe17b1656f191b5f4272bf1fefc73c1656bf9d6321f9743fb1b7afa68d9593f8a1cb4ea05792cad0f7ef951a6f01358202a99a4ce6ab5a7c920ed7bd41579d2e1478ed75fcad3bdf952f0803beae06799c3e08ec5e40dbfdbdf5620e7d91810055884f7f58926814ac9d836293d6a1cca86003e389f36429d21c2ea90d956cae21cc1e8760a79b0cb23a5cc896c344e763866f9a6996037585567939811dc19fb317c28498c695312ae5677fd24d7014b58d2d9b6505bed7b679cba0559419e9ca5c68d52c7df75fd5808b697afae5672888f64e672d1b712c7e9c808824c93c632a91c0a1d4270b3af3f266037b33336995021895736896dc0c9fa5a3e90611fd58ef4b877313e0fdb521a7b41d633cd8b585984c5fed95aae84d390512cbd8f95be489388010c34822001f266ac0f2d385e7de823c3636ff57a08c558b6ebae4d402359c21bf7b2fa2d0a902813ea25ee966c0ea865b55e963c84cc882f1f47fe8244d181d9f09956325c8126927092a4aa7dcfb1f1170e5cd627206169786effe08fc2ebe6586327a1db26bf1760aab0fc80d4c4ce5fb52405c829df98bc7ebef4208f8eedefa064309c5b738b7597677193c4e96df63e2aecf3de2e6e32aeff2aa84b770fa3168c6901cb39b0da5c2ac4b8c018528534c78f5d26b468275866922356ac86c14286784cab5f13d1e4ce7afe76453be18bca97b3d2fde20c3692e606e4d64acc140eeb931fd32c7848b9b89c2924d8e883dc7c2b37e421b2b00aa4c8bb82125bde88356c89b36998edec167c290d094cc476495203dbd1fa6e08e0a11a074c822f5d4877aeb7916307175dcd815d7860a9f3db28c6e41879b7071ccb8a1992ac601d033d3bdb200514b84b6bd3ce247a5902118fd914e971ad7397823e35803ade9ca892647e331c0fe3a0be96d9e3bde23fd35518caa8ff078f829b35ea40e24357c3a15f488e595f67f6b1886248ec3e63d6f3f572f3f9bac4e69d6a00738baeefbc462de113a850c2dc744478d5f67e7e1ef22be0594a9fa961c4994a5ce83248276cb6ff6d27570543c53df17bda6187f16abd5a783d7898d08877f421111304476752cff8f08060532409df17a3ec103826d489861dfaa9666a6d585c009ef85eec08975b22ee6367e5bbb8c17164089fb932fc3ff43de8a82033150c60fcd1cccbb9c07cf0384071f815b0403d7147ab9eddb45456a626999195ae27c17eb2576ed31154abfd8d697ff2264fe06f42bc725ddecd371ab17f47ae481ef774083c6c63411b24f9b2854bb849eb32393721a041330d778fa91325ada96aee6ab5e874b7927fe087500477c58b9d2c45134963267c981284aa64809ebb2770f1c23c03cd66b5331da7d64a3d3dd3cdb181e2b25bc1e9ad50c5161a0cf16e1ca823a9af5566998b729fd55554d51e64b083829c37db1aa0e97c489cb69aba5d7d90d2d45399fefb12bca0825dcc64c921d66129a6f6812dcb011f769462b9f97b7958d8c1a89b6fa67bd3389e80d215e4e87ad747d391e01654f904da1a00bbad1100474442fd817db0a4e81bcbfcaf0b14b0081f6429fcb7d13742db44e299703f7a09673891585a63f43958cfbffacf02d8a8af7384937b3b53ad32d3f6d4509088bf86c5116e1349658d99013de41415704f78e19485a0b261f00fc5292b0f4a0abc0c1ccc4abab0f2cc2a074bd8c90f26727c2708e6d139a961a05d11c1b3411084501ef9c1a8f6d2176dcb1ea2124a25bf222f4ee91b01e3b242e5357097e233d98da6fd9f683f4a10ee652e24486806f1d0189af76ee34cd9d0073c4115802c81740e859463be596162d5273d4473559a1cd2e41750c0f20363b3db8e35a7022cd6327b7aadb1c33ba7d1428a90f1ada973e605b4ee0a0c242bae7271716a8ed375039c6922fe4ae2ea2fc7493911d0b178284f7bc45a4da283dd8c34d6fb62e0a00608c2bd244e5ce1ecb3d81150cbd32c331c750e78f5c50b3ae322b4c3c0adab6ecc3040b219110c0779c10f4211585d5cb4db7053bb8ba0234f2bce1b77c8697ac46816ccdc4b06d9de1437cce8a50af7dc2e997b7d83ae22dfb0ae0e8a24a8d18a574c8baf2a94a667cf9f4164fd28332b0874307fea846136f75dde0388bf2ec9f0cb513e41d98bd426e650b35c88f9b5ebe52371d808f07ad0b56ead6917d59eb311c2297e54110e41691cd874ac9432d10ee0d15c20d2f5571b3728d2bb90cd59abfb2781b4e833281c88a212f87e05f29da95dd132c65a37d95763d47aab2604c7fd3bf370ea5a2452b719da96a25b622d9a6f099ea4dfa20e81e88db4196bb666d4d129a6960c93988065c13f558a095e3853df7e2901ebce1f276d8cac4ab9e2e37e7f6104a2416048d40ca87d5e86634264df8257b0f291c351dc5a6e7abf140321240d709f46462454b9739da35e30678a76661c9dab336094fee25f1d06491414af1d8a4669509b600ee2270b02e393b38945aa33590ee8cd8b2f64ceccf43a1d6a856ff72b54133a70723e9a64692e7c6ad400591500d50b9b820f8700e2801815c08d2c85eb331fc38a287cb77b6e14520c9f93ed1469985b8586aab016293cd42db48fb11404cc7d21a3a5feb5a0f534cc7d06b55f1fe9000245eee94034222bfc63769e11f5b2e2e9a84b9abe67f914e63264fb62b73b037c105348b94458fa52c3940a801ffd8b00a2dfc0450e19ae382ab114fed956244a38332a102a579f65aca379d82fb93f8e88163b09b5d81e1c05f72b7e3cfa5d18dcc8624c4dfbab5bc3b64f0a23fc5835599ddd1ce0f2d946cdfc872e93498d2d7b56010e500a11274c9d17baaedec77d557e7e4c0cccb815097efb2b0694816cc9d02b1c2737b2089f4360e1127d01b6c9ec6245c0a063f6d95b1a8eb89dfabe2dd19056604775f4693af0efceab3a8334864497cbe391932c4419028d8ab2f42bdd5e51d3a59764dfb7db5cb7ea693ddd487a3a449bdbd76ef43aedeb1c7f49f64031449b060aa0c76c60f8bcd0f3a77781af7a8c2703b769aa53f523aa5f9dde1614043e041356b023acb4ba33730ee34c3013cc3333cc3333cc3a39f35fd68948491b6a76e68a72425b5ee2042782cfebfa04899644a296552f830f8c70363d444a4d90c670af509250a09195deae8b78eb95bee1f8d48fe8978fa0c5a2ad7b3dce5a50f4624ef36e81bb1a32ab5b8e5d3f8939533707c2c22d9ab73bcbed8a3cba2292f616973263845a446cad8a721babad165d7f291887431dbbc8cedf832f60fc2072252faffb662558dbe7fd8f07188c4277bed71dee5c310e920a38edaa3720fedab7346f828445a85569ae5df3b6245844876761d17b29bb5bde201e1631069f5e89ecfc588f0f031e1f3942e4194861f8248e7286c47cb2ff6894a3e0291cc2e8c902d84383f5d99071f804899f9bebabfce6d2f223e737f482a5f0de7c53af58ca367b18490f0e18764cc39afb5eac1bb68bff721351ad4eb728b2b97d9251f52afb634ba2877a8125c516931e34b72b08ca025078984a425076f858f3da4737c763bd79f0b3a3d8b870f3da4bfb4e331bfa77eac3b0f49d1aa5e3ca4cbe56a5c8b6e2db43eee902e7c39f6e665f47d59b3433a5e9ab96ad830fa551364d2f8a843528e54d97bdb2b85cb7c4b7b0e17d8071dd251888bb697d934cad1c71cd252eaacb27e273a7ffe0d1f7248ff6bf6dd56975f2d0ec1471cd2e55aa9214f8467213f296cb000e5030e6915b3599d4ec3174765c4c71b9279b370d9e7baaafd32613254f0451b3edc907ead736c1522eb348a484832cdf0d186848aebb2c9668edf2265435aa7155ae8f17231c8bc3ed6902e765973701d4a737d1b06b3243ed4901022f5766aaf15ff5226ec5b0609c95a39988674cadc2e17b597644c33d040e917e41a3ed090d0c9efe4e54e63fe2d573ece90f8c2f81784ab7df6c6b0000a1f6648db6f8b32f3cdca65aa0cc91c5d8ee905b51e83504a56544ad8e8953d147c9021e9254ffdd3f8c584cfee543ec690b4bd4f5b5d19575c837af021867416d2ebbe98eb57cb921595963e76828f3024be2cc49dcb67dd7a4f140c0911bb5a55adf2da91052658f9cb97e1282726619064e1e30b69d99a5ab37393eb1ce385b4d4b229c3b5e85c5ecedda18f2e24fc3ea4d0cf258d72f70717d2653f59b53e5e7ef1390c1f5b48eab2de24a72237a5a98594ae06d1992d6417eeffc84232f9ea9c936f9acdfac3426a43bdb4ad66cc55f457488ff0317b6126baadfbb04272d5777f8c0daa32721512dae9cb31a78c15b5171552da5ad5a8f5e419b3d0123ea690d621428967dfeef2d852488a95eb31b8f072b98e4521e1e59139632e6b792fcb0f28245dc57941ca67d6d7e113d2390b259ae1457e9688081f4e488bcfd9542ad7f4fe054de1a30929d92cd5bbf8b7da5d9609c98d26373322aaa6dfc712923a6eac780bd5234323840f25a475b8fa3c176abba4f6471292a3eaa2551774b8923f9090b06da9ba2037986f673b3e8e90def7f4f4e5c2767851e7c308c9d1a52f8fb4d9fc5ada0e3e8a90f47c1b26f205edd8d13a7c1021395eec4f23e3e5626a8976858f2124446818a9c163264cf226abc320217993d52b2d01c4f02184b417de4cc54b9d66f4ca8485052424791f41487c177c34a667ed3c0d0889d13269d857cdc528633e7e90982fbca767faca1cb309dfee404961c1ebe0c48ccc6ce3a38db48b4ba54285b6cbcc32e1eb45427b47291ec6e745facb1db3b6cf9b715c8938f0d84532f6baaacedf5317097fd1b1496fe367b07391d6df19b5ce49ed460617e972fced0c1aedcb3983b748ee76bc3dcd71b31e94133550fec0c316e9a065e8cc2cec3dc88d3af0a8455a672ee766c8ffcb74596891f41aedb859bee4d19d454297bb9b7751288b64cc6776f8f8a75ab209dfedf088454265b356f76ad4db8d4cf872071eb048bd5a1311fa0b3b5abe22a14abb3fcb8e9af01daa78b822a9a516fd85771fad37d38a8418cd85f7ce31ba5f2eb278b022e571fb2373cbb0f99955a4c7b5f4a232d11d140f55a0ef99eef9a742383c5291d423a3c8973d74d6bca7818ab4b78ef2dfabf383dee61409339379254abcbd34b3a898b01a1ea64886dff45954de08d598097f71860afe64a5e5c4a314092feb976c46113ab64d8aa4ba6b7e47a9338ae4eafcd8967ab251b71ea24869525de5b2be1dafc66289472892e541ed37a4e8d2a7c7840fc7b79c94b0d12b79667880223922d3557fffa9ce3f8b9ec0e31376177597563db654c04697310212fcc9ca19068f2712afc3e81ca3b92cb3beb034cafd0bc62021d1f56ba04751ea24c5dcc0a31369991d63438516196d9bf0957c8aca211828288dc30c3e1478702299a93674daccf3a3719594db44ba45694ba1553497556d8230be1023055f84f18509d6f0d0443a779459bfd8c16647c72313e92ca42eb8dabc136dbd4a4b0926921b1a1a5c7674f99d9af0a15b316847038f4ba473876dfb1cb3521a858c7795310e8f8146497a5822f19bbc33e4767f41d7267c298d9243b909de55c62069c3a312698d652722e651177438c5831209cf519fc67531df8e1283c72412ef055d1fd733ca7df590443a43577a517fcead0b72203c2291fa2fcad81c36aeca9723820724d25247ddb7215cc62e968f48e8face60eb72af636ec27a7a1e8e48ff68ecb2d3cd05295f133e944e59f9164696151d613cc1a311201e8c60791c9d5212018f45a4367deeec6ab3dfcfd26260e0a188b488d741c897b9b96761c9b1c71e89788007224a20e0718892931c60a89c81010f4370c0a31038560e18e04188b4074d5fce1521f6b72983c7204a1ae0210896ff4bc9053c0241010f40fc5f4a32e0f187f40b1f9151b5acf8a229e90a1e7ee880471fcc830f2a97941314940e630f7ad880471e1242cb155eb28d61930ae121031e7748c6d0fe9cdafb41f7ab24e5528283a5d928f9965e6961c9b443dae3cbf2d587d0aafbacc30e0b78d061021e73a880871c92f26ba5f4911bc4bd748ebc34fa0c334abe65c708c2202161f9961daaa42445e524141e7160d9f16a5cc0030e17f078435297b763fadc233586f4534e140e958567dc90461a7d06073cdac08674a87fa185a84dab6ab386b4accb1632a3c20ba622c1430d16f04843043cd0905c399b53b50be7b24b40789ca144021e668080471992217331a8d5fb9863220949143cc8902e7aafffd7c71e6348f7ca54ed2f6bd15fda3cc490962e9bf57e86cfcc5d3cc290d0450d593e1e939735ed0186a4fb06b939fbdde56290093cbe90d6b2cf7c33857ee9bdbc1d2b87041e5e486a217755cbd8baf75e4cf872241947f0e8424ae37fb8d1b26ff35f0d1e5c48ae3c97e5fd9e77f74507e1b185b4a99c7799353e67565a727868a125e091856446f9a59f17195db4c7030ba915a1b5b6f1e2a6aaf9ea718564ec7241aee61832fea3de0bf4d6202139c14989de1ab972529283453dac30e25185b4dc6aadd098f5476a4df850ba84b1e504c7094a89494b8ef71c785081011e5348eaa031b3e872d1c5ecc1430a094d113a6574d6d4dbd0c1230a490d1ab450d5b71d3ca070c3e3091e1e4e484a4fcf29ef3f695adba3092519f060c2023c96507292038c5ed10106033c94f09f6bddd5d47e12d22fa46b0d19d367f5e2945e2c1e48488af996f9ff357cd0f01192b5ba2fd4abcc08e9723173f98b5aba3c8ac07924cb8308268fa43d8690bcdde41dbaacfc3dac2b2d3954e02104cd23496ccd6695c2566af68f1aa31e4148ec8c10bdd26e20a48b5964e3da98c6d61e8f1f244b4fa44b5d2e2651ee8de1d146625ec5baeebc52ece714d08bb4dc93dbf19cf20b3a2f526fb7c2de6c5c68195e514949e3c4d450c02e029828d55ca972a78e1028243ceaf9763957ea7dc19027a4343de6e26df038afb18438212d5a978554cd6d32e2e0110d214d48e617451763bd85862e6765a59cb01afd30600f6142ea379dabeb4f5fd4e179aba09815842c21994767f11a4f934347891a2a2a28a7055f84812e4409c9f0e263ee15ab0bab83429290ceb221527717d3e7592121e95e546523b39f5f9c51821d9f821c214748ae8fc75721f44648cd77615537486d2fcb22a4f5458a5a9551a98c22212142ba658c511fd35baf78703cb3113284c40bd5d632964acf7934438810529af368cd9e7572a93c214148d7e7ee5ff1b91c8400215dde34ef961bca3765945e791c6f922391906c5ec80fd29963eacfeee66f1a9690369222737e8d6a73685a9d1e905e24cf73e1cfcd44b41095099ff21296b66301630424f05f9dc38c1d9f821e80f022251e5af75b9727f3281306d9454a363d261b4d57542201a28bb49757e3288fef9f63169d2440729174ffe2bc8bd610951dc335b848173db47e979f3fe7f0dc22ad579cca38dd6e19ae09df4a1f455ba40b9a5d2abcbc4bc506a9453a7f31e868ab356669ce195301428b74b963ab79912fd1dd9a453acd973dc8e4eb22527964916e1973a7cdd423bda88f455a56e47a86ce79d5e8608109fe64e58c4980c02271df597ec8117f5e5e9178dd7621753975b88c72455a17766372ffb91dbd1569fbf7283d7b78c72ec88a7469a4bb8ac48bf792cdd947fd7953457aa3f61eb1d9db73414e45ead55fc6f5242374494345f2562a352fca185d5ecf299265b2195e6ece6d9a31455a5c7ee9be3eb8ff079522dd6aa397938d67f93d7b5fcc59bcb347661a06192af8428c32be3848808c22a94cbd5e6bd4a248eb2f672134eb3c14495d9bb5cb79c3cbc5121449ff7266316db539be984fa48ba923eba39427d2e24b22a56be77422599ad2bda0398a13094fd5d62c5bc7e0a93d01d944eaeda59049b3b68dfd494848485872a86822f951ec878c314af5aace4452b9508f32c7161df2020413e92dd7c1c70bfa9578614412805c2229bdfd8ba145688eba5c4cf8cc8d01c41209b9bd1ed353e7c000a944b2c3df9c467ded42eb12c69fac9c61890238020825929a8b494667eaa2bf90555af2f4804c2299bc47d5bf3b3f879624d229a3d6907a7572cd191a209148ab902f235e7a741f2d4824659cf528dee2f58f8e3d22b5a24197bba57cacd025850d9532401c918e9a21d3632ad1cdaf1d208d4887980d52a46726f50da6008411e92f67d617f3df219045a4e45f888989ac789da0b094a034a622d251e698cb31b9f8a266cc9d014944ba18e5a6d90f52991755267c3b9c1191b04df1a1836654afcb99f0d913400e91ceba858c9757a6fc3fd504c4106953f5a2b85eed1bc3cbc1c9a510e9926a1764d43f6eb39010c9d278bebbefbb397c8348c87751031144baa0e1e55e4aa12dfa719040a4b53c17765ea7a770b7e4534e50524e140410498f3777e2255921ef1f1d28207f48e8d7d99e63d681f8216177bb427dbfc6d773258501d287649637cd79dd81f021a12a6e8458a9cb391bfb00c81ed2e5513bcfcd79efcbaa00440fe911ddb19f4147f64be621d9b265ee52fc36d637615b03081ed2eb51befbbe18fd6d84a252c277486ed435c28be1a94b6f8b0e1c6980d821f9a373b54819fb12e100a9436a73355daa2ebb765968c257e2bfa2d24287a4963bde2db5175bd68e6280cc212dbe98396ae3aa7a1153d850d994435ac839aff172c1dbd5207148e6ff0c2b64bcf66584a35756c0202111a38c2f4848c000028784bfb7a62ebc4c7d57544e4a8e05206f48a8dc9e2f17c417438f2e1b06881b52de653942decab421fda1d7cb217a237331c586b4a676175d66b457a9ae2179b251081dd253435ab98d7419524d433a368950ea2af647656848a84d75a37551b72e7ee80c09315f0c21a51cf97214198819d2aea3f36dcb9851d5d9819421f5ba3aaa1ad12985a9eb2a6318261032a4349dbd47818c21f5427ace2cbfe0e582aa023d54021031a43e6ccc0ebada74850a240c292fea0d2e758ffa18bc280e9e04eb5f70f83a70f05eb27e0d4406081892e145cd5efca03c5564be9090ba20a4ee97b3bcc0abb3b70bc9ed7e9dcb9b428d1017840b4953a5bf2fc2c6c45bdb42526cc3beebfabc49935a488f4e1b625ed65c8c51b3902ea7eca2de8e25e2b6cb1440b0903e171ac3ebe8288376b94242675d521d590f55cdfa9395335a74ac00c40ac9d7f7fafb43cbac0bb22a24936ab9e63a95a6f201a1425a85102a3eaf692e6b272b2dd60190292464165f7acf2ea5503e4a216d779b75504ffa65bd45219d3526ffd779224d44298040212da5ccc5f2f845d551b50a8b4134061a252800794242eb28c3cca366962303e284b44eb3f3d2ec8dee5a419a90ece84591c16f0761423a79d45c5ed60f640949b55f325a69f8975e5e09c94c35edbdd956a91e4812d232c377e8a8ff43e612040909d9ef627ed8155ece721b801c211d346d85eb583defc584006284948b76fb38bab5f6d245487e0c5ece30ba1cbfacc54448d87679a41754de8710418690565d8e52a4fd98cb1f0d014408e982fccf396f87792d070942ea3bab501fda9af0edad800021f1a75ef6458db9ecb7a4741a8b4d00f94172533eecdf0a17dfdfda00692329467a280d1a33b588a517494f9e5d0b2147fecb2c2728df7262c28b74b144f5c85ceff2bea8925da4fda3f45c162fd5a1a28b74868d2fbfdc3ddb32978bc4d67fd97483fe393bd358f1435ca4d55b8c90d1753179613e61436f91d2dc25d14595c9b57a69363ee52f6d91103ac4c8f3919f8bafae453a8f4efa625e128d5999f019a44532c71c4397a5a7cbcba282b2d8101fb348ea502ff3e6d1ab367e8c133e64912eaa175ab37ee7c99d86b148fe6a3dad8fd2a58b2eb0486f7ce1652ea67a3df67a455aab6cc67c31724542e7626f487b7735d7c5846fb1091fad48bf561993bc0adb98ae0f56246d447ff96354afeca70746ae22699e52fe0b8f33db5e5491f2f41fd765d6b119ea1615959583858f54a48bb69db9ac2936cb175d34061a25f8818a94ca5c3a95724543954bca09c2f83845c263ce72b2da645f7b3145da57c78cb553add378ff518a74d14ebdf05e50f3f3ba902219e3ecea5ffb7241378a74bcf6cf17225b275f14097f179e212f64bcd55024352653715f72d1ab3550246c3ca716f5aed7518be65a72f089c6c72712eb9e66fe83ebae2f060f173e3c919ab5ff8cb9758d5009de191f9d4877eed477ae7f3fbadc072752f75af966e86c4ea5b0d139586e13c934b2b9689f4bfea2a09ca8a189849ad958f667b29937387c6422a13366d54328d36c1b444c2444abf32f7aae6b501913be96c661887789a4961e67e49f89ed082f5d9277ec04f16189a49ef762561532eb9c3b6af8a844f2a396f1da45d7a8596ec2b7e3d5309448fb7959e7165dd4249259d8dec8caa87551755412a9d77f997378c7a8b5432bf8884432bf2c642ee77afb0d66422229b38ad8d1ed72f218f30d3e1e91f6f2ea77dd348d1d37a7e0c31101f9c60c9145a43cb8b9af58d34ffd2822d92f1ba3d457edfa2c11470f493a173634c82e690e11e9cf1aeac5789d5fccba09df1e2221b2a30e9b44aa6bd672f49b94a8b420ee483939c3dcfa3576a49cbc40c4102971add3c6130f8c535288f4975dcf9cd6a74aa55a121142243744d7a88d26db51fa252b2a2732888498c7525d98dfd579edc5f2253978931511419c9a23496b10cdb2c57f4ca3771a880422a951bce87ad447e8ba80488988ce5d561dd37c528521f28784dea02aef959cfdeb45fc90980d3ae35e182d83585d90a2c28202913e243ecbe6361736113e24cfd3566b6f9c88d6b3ee21a95f7bfef098753ff7480fe92cd5863e0d238508ad2088e421195c7997c53bd7e39b69a49cac941394153c24b57fd62d47ebe8c7fd84848465c7ab4142c27748967a1c2ddf76bc1ac80e6921a5ccfcea97851052133ebc6561f9951c78db262c06db8445c740a3c444ea90eca84b5e7be1bf28e5299d23076f767c0a5a7e65bd192729272b48457935584c840e69fde58e22328a51109943ca5ea68856d3a8af5139247588522927fe3ab42f1287c46f58d1592b028784149bef57e897b9a328f286942bf92f978b35372bef12714352c6d85ece1ea396a3a1494b93414262d2d24242d2226d4806adbc5cf49c5b3e57cd86f4a990325367ed89c81a123247a9215478ec8c9d22a286a4e6dc17579dd385ae59584e5a521ac7d9d1d2f22b398c8e1d2b99868494b1ddf1bf30761a113424de7398f5cfbaac538a24242424bc46e241e40c691d467adab82d4df5ce30435abf94a9a9cbe5cb9074ff20bfa42a3d5dc8c9908e327cb9a4413f654ced189241bc7d14bf311131247454cb149dde95952ed9914365a5c58840240c69ef2feb6c2ea40aade3d53958bee5c404062d272839cc58696111014362a5ad8caad37e3e6d95a49cdc0a0e8a7c219d3b7e08d5ba3a973724e285640a1d3436bdcc9d8ba54817d2b99cfe994196db173c5c487baf4b3dff5cd4a9e35b486ee662f66b86f2d7e5a8a4886821a5e62adf695df5b44d912c24a4be66fa2f684a95970816525ffcb2dcd1b712b942323eedeaf8beafae61112b243f7f84178597937f525521191ff5bc487351fdb9645221a136a45c4f198a4c211d85ae797c350f196e1129a4456711db9ca5abbfe844240a89f3f6a83faafeb2969a3644a090d6713d6ebafd2de42ff28474c96572d95efc449c905ce9abebb6bc77d41dafc6af28a6915206a221d28474c154ebbdf8b22ee6dca791f228286db2b604224c48ae88ed88ba5b2f882a86c812d29e3546dbea91d7d6b9961c272beb3c20a2848412d3cf3c22912424b5a89997761fd587f21c3c33d02b95951616937282b2f238cc1868941c112424f36f75da302e639499b9cbaad16bbd1a660c344abe103942f2f5fd4ff4b5e7b296891821d926e2ba8b42434139b9d88591464a1924249ed22b65ac939426838444a408c97f2fa9d96afb1bad24426a3d3d754b3d0f14448670e9fb5d6aadbf8b8810d2a1735d3feb9769ef8b894810d2d9a6e549a3ca7c41de4b1001423a6f482dbd2cfb5ecc4de3449578ff4a5e0c447e90109a1e84f8b2a88b47c940a48d7497b310cf902eeabc642fd23217663f0adb9ced258e15153170aca85c105e00c410c28b74ac8d51dfcb23f3fb4bc82e929e32175bec26ed1b5d882ed29f5dae97d3349f852717c9d94d5ffc20de105c24d366b7ce5f7cbdb07bc82dd2a3e365d6fe5597e6242139e3597a0424240b89105ba46bf3bfa03947f5af5d6f05a920a416a91321b585cb58cf32bfc649cbfa1ba8ac90b13508a1454af63ca64dbbf18296777e71460912893b187801b845c82c52a37d23d5f676bd8e267c25ebc45354c220213951218305e54fc6f89493cb050c1212961259a4e53f83ab528d695d63c297031555a2c66a14851ea545e9603163a051a2819058a4eee37d3153d67631d8267ce60c43038b948667fcce51aa7770a1e8580109090909a60e12922fc428e30b4a84bc2299c3eb6bd4bc691d3860c15321bdaf96beeec5d5403654726c2b9231ba166a9f2e9e568aa5cfa2c1e4287f228661453ad9fbbff8ca2fae2777ac3c0e95345a525470e8d1206415c991b9eed5b6d6980be3091b2525ae82a3c49411a28a8446bd17b9217388950e964468c65969945e2c0653471a2e21232415297df9be6fa931e1632931782d2c2a2625252ca84508414552455f872e7b9659329f222de47f598cfefd0e5aa770083145d2e64447a9bd5d6ff44a915ef1a729857aae0e27299219a39651480f93b37314a95f5d2cfff3551449fdb73aa2ec5eca8d4391de243a6a7e512a8c3d848022a11ae3175eb81c9b1d8710f289c4ceea4fa9737646d72ddd628627923af996dcc62e280fe66a21a413c98f5ddccebe2527125e2ee7dcf925dff4f10bd944329497ce46bdcf96290949882692f1c6bbb45f7e9bee15928974ebfcdf7ee9adfdd6104c2456c8967175ebdcd63817422e916e196f3b5f109a193d2196486706e1abf7e556339e4a2474d8f672c7f0aad91542896450fea3ad5a7c41563689b46ed032b6345357e69144d2cb1f447b31566aadad0993900ce016219148e6e7da8c2f87171b732190488b6cd17a44c295666f905e0c5e7035c41189f5949717f7d5bc5d421a915a2dee6bb5fdbad04f0823d22722eabdb1ad31be218b48c748d5aba961b4978542149110bd69c42aef820cdb0a494452875c2fc820b3104310910c323d7739b5f010c9a8bab4efb5dc511b443444da75ec7ed9f562e4e7100b91fc6db1a34b6a239ab510ad11291f421c44625e6cdaf788d1c52e280591d0baa875d4ad19c317f7244348209241a98c994e948868901a42009196dde275306f1f8de2cc3fe4aee3bec67c34d30f682eae6fb972bf0f09edbaa94b7ebbbad47f42081f12ae32dd3e9ee66cf443f6908e9a2f745e77e51442f4902e1773946daa06a14727240fe998ba0b1bbe98cb6a552178488a8d7a856a0d2621e40e8917badc3b625e8f3acf0e29cfafbdb52175486c8afa2c65bcb8104287b406a9ba2c5f4c1a57ca750ee9f83ae6a507a194437a4606252bf7d3bb4ea21442e290b8152d7da4e77f2d35040ec9105f76e1b93cbacf3e216f48a917b3bbdcd5fa4799103724c58bedc5609aa395d88684da1c84b87fcda8b5101b92daf2bce0ab74559626640d4917eabcb5967b713d9a19a28684665d3e2f898c42d2906ea952cc8f2ed10e42d090982f08d3f9a2173b44e60c89b37bd9efa1f52d8de3a485e5757062c6454565850cd50b31435267d18c59f59817b5aa0ce9b28e1f3554ebf7a2afc890bef18ce65aba798f38640cc9d4f8059526f7c5b6590ca9794d4de227ee850f8621e99a22e5f686c6f0620e18d2df5a3bf48da99eac4c0a42be909e4f9e8b219ef9bef0c1102fa4858b67078da9bdd8cac141481752ee1f5dd7a58a0bc95c14772334a5561ee2902d24cc36ea188de92084682121f75517b5ce385fab9d21240b497b0d97ab352ca4d3e8a8fc337acc1a5f340e2157489c6673b1efa51b4fe6bc102b24ecbe6caa3f72b3507d4815525f969fb3ed6473fa62ee10428594a6d414ff70324e21f1a20b3a66539952dc2e8584260feabdba5223240a4939031be0eb405a07140d2d809d7ceedf1ab080913093dc04560b8b8e9502046087ea80470058567ee97095951c090001cb498ef608c0220010000074800421620080854591104000ddd24e528000744bbbca0ac901003000072460e54bd458ddc2525212028000dc38400e1c5e48bb4e5ffe1c5da22fbd2ea45b37b83c195555de73e10037b6902c7d995d64f0a8cd5c381e079b1b5ad89163652135775e7cddac5fc63b267c3b722c1c8fc3e0781c7b030b79d2e22a38ae90272d6b9cb88e929204dcb042ea9599b78a321fe19d091f3c1d78387ea5648d132ff1d7c109ba969493644939694139b931d02869c18d2a644909026e50214f5a564c58504a4a1270630a79d28262c2d2252509b821859667c1f1380c70230a295dd71a5c5746ede5dc8042b285101a450ad1395e18b1e45029b10fdc784232668f591daffda9bd091f4aaff17a82432565078a0a19ff621c6411e3861312baf01be2fb4519e489379a90149ef653d6db75baf20613d26557aebd9cebd273883796909cfbd474217478d4e586124ccd9ddc484242751745fb679161e47b030989134fa3fee3c3a8180d1292d7c189cacaeb608f90d62e4d85762f8f16fb1a272befc606378ca06a8ec46f14212133eeb9bc1579199a5625262c3a72a019dc2042d2cbb3aacd65a93bec299fa3a465458505594890f82c2a39709ca4e8b821a4f3bfbc5a97b74f4f3eccc19ba0bb2184f4eace972ff37b4117342d9fc68d20a465ebd605f582d452688e09df49491a8a253780902eebe720377dca848f6f6ffc20217ca4d6de05fd9f5e75a38de478d611b2b79a7b847a91bad7f6e22e3d5ea473597e8a0c62dcc3c7ec22e1a15ef66598b6df175d24458c947a5b8e6afb6c2e92e6b177e631ea9c75595ca47f3edf4517e75ba4cb2a55af8bfce671d716495d2ee9bc7f6294f1c5da2961a4162917f62dbd3d3fa50bd1221923fe445606cd2299aecb625575d57c7ab248c817bdd15506152144b148bba8d995d9bfa35d18162977df2f07ffe2af4898978cbff4f9607ebac2d0b42299d57b369bfdc28a846d9622f37db9ac22a942c6943ad6a88ab497a62eab2f69a9486697299a61350acf172a52db296643d49775a8f3146999f5f6666ee5ce698a745986fc18bb52b7e7bd1449fdb2665d969fedf66d52f0992349d7a77657192a472b7914e92f687ddb0e2251a4cb3c881023b32614c9d59abc752e9fc7389109dfabd16780221d743185bfd2d8d0a34f24eb8b71bfa85aabae1fcf13c9175dd40597596812239d48cab96bd0397eabf81127d2f5bae51edb85fda27a13c9245ff6fbe05f4eed42554d2447c6367bb14bbeebba8e6422253be26c376d3091ceccad3e6bf660afcc4b24b48b9af6346a8994b7ca2b9142ddbdc72b917c155d1be5d39bfc4989c48ecbf55c72fbccba9a444a366a8f0e9d4b22f1dab3d6050d2f1ddd4622a18b9ef37a39ea0289a446e905bf790f7ba327794452caee76a8e6173555e888a44e2bd3977e45eb34ae298c3422b5f1737bc6b05297a3cc0823d21e55674b6bb47fbfa230b288f4e772d17c356dabaa5c1189cd1874798ad3a04bee482292ed27ea11442474337aab50de9b79cb3c445a3ba667cbd1ababb30c91fcdacf6ce75a662152f69bd1c31774c18b5e2e998448b62e677deda5979b32287310c97dd14565ffaa1f4ecf1444da3bf768591b7ff3db2381488fda7b69fe2eae943120d27b9f0b769bc5976f75997f48c917c5338adcb12d35d30f697df7a34ed40b1446fa90d0a0566ea310fd71657c48beac5c2ddd34ea51e6c81ed2326a7ca99a64ce3e5fc2881ed2514b376a6e4eaeb385ba309287f4dbafcaf6ed205de677040f09d52f436a6f1d2e5dd9c6c81d92597ff2d2174584ba50238ed8213532eaf60ff5de59785618a94352dc68be33d39ceef7113a2446870bfd596f640ee92ebc4a5fa9c5237248bfcc85cdf590fa60240e692923f734eae208841138a48ba1bbd5caf4b4d25d78a430f286840c23e63e796183c879c40de95ca92d5ff2d01db65d307aa40d29adcf3faf19852ea7ce081b9873640d099dd94b1f7517a31733460de972996e99ce2a57bb8da42131f29acbaaa3949b2f5a328206e43c921b8c9c21b923b45dbc8ea6bc098b8e1c3cc6881992d9bbf05e2e8698594d4f1992befbeddd05d749bcc5644877ceea9d0baf3463487ec98b59cacc45570cc9a80be24499775851ae8621a1b6f93acf7e6976996048baae97acf8557e2139ca735ca1cb256d6def8817d2f98579a19bf754ea74a40ba9955f2e66a86c2ebcfa5c487ab9b4de397ef8a21e31b7a0984792235a486c77fee6eaebe7e792b390ce526bbedb8ca19b0bc2c4423a68e8b27b695eb367e40ae99dd7f159d363db218c58211d3e978bfa27cabc2896933d2363a40ac9a0b9a0d573d466993c292354489c867818d3d9cfb08d4c21e9e6c530df1de4d9e846a4902e7f505e982f67610d46a290fed845d9e5783f8e402169ba3a65d59e234f486fca74ad95bc469c90f820e3c4eecfd595965a1869425a173f3c0a9d5228bdc4a8008c11b0400525303bc2848497bbbcc2cbe56cdfd11b5942323e97cb52974b7f9f5b3ba284d4e7cdb9e0ba58b3eb45d950098384840d959595208c2421dd2a5e504f5d16e23f348284a4f2fcb934e6922347486b3122e3cbc58c374ace91438711d29e56367e3a11e5e2929567430505478a90f6ce723575dc4688904c59eb256af3c810d2416d0be9517bd176be1121a4f463bb5495b6229e1b09426a4f93eafe8c468090f86287f28dab8b524a7be407492fcbcd6a44cd888e37d246e263da7b596755b789bd48b95cfff89c7c115ea4633cba1ee15f522fc7a0b4c82e923a3533eb3ce28596bb2e925f924f259ffa8bb5ca453a7a327b35e6598468ed40041769d1ef2f46ce97bda37b8bb4ed8a17bcac79923688d8229dedb58f0e792a7366535e0d95334a4e0722b5488b7da9fe69ff3146a6456236c68e41b9ac1fadcb2c125ae8f2db9a4861ef6a13d680882cd259f686a6a9169dc31f8bb42e8677697a864dd350418145529736b393b7d095d2cb0a445e9196eff219e517cf658cebb9221dc4fb5cf4ab15c9122984d87bdd2ed5b2b022a17e2b5abe77c76c6a1509d5418e70a52b3d774a15c9ef726cfdec92ae5a5d2a52e6bac37ed6741f2b2a52b25206d7ca8550a144a748979febf49a2542eaa81a889822e5f14df46ebaac7aedb014a9d3e8c5ecd275d3ebe53b438414692f76aaf8cef15f16916514c96c2da5a24817748ed0bf6f9e316a30100945ba85909d3bacd4ebf9074532bdbad4ca750eba5ffc13e994327afecceb0944bdf8bbc9e74b26914e24367e647edf3861bcfc7256651a3db789a48e394347f5a258f34f13091d56b5aa0873b16215c9446acc0ba3346c2c10c14432e468abefd776b32f7b0956446289946c70ddc5cc5a5e37c6042295489fef8e4e7122f24415a14432c3efcb858777d82f229348a8176d5f545e6c2fb6940b229248a62e66975fcca5f3820e4522251ae74774419eabfc9fe506229048eb147aa4d6e834f2b5931517f023125ff40fcfb0a523ccf2f062c9f6d7083d87641991545efe64aee3dd2b8c5ea8c4302613691c1206845110c4300008cea32000c311082020301a0ec703029960aeab761400035b4c2c4c422e282c208984c2a148241608038140300462180863208a81400c2881d875635647b87ccdbe230f18701ff75e04a8ac4ea45dda0b950564eb7d84cc3f5a0ff2fd64201c5940d937664e1fe2102e0a24c1b8996d8880027a6a33ff9ba473f87fb4888679caee2d399e1ac9b3a3bcdd6e904a6e790bdbefcd298b2fa5dc169d150815c0d50a9a20f2cfed3bc8dfa766116314043773fc366f63c7437e49c3a446133a9976886dfdf6223831855fd0f003efea50866771fbc70c4ea82b73fc74a6ef5d29d23ef1496066efe3fc1c74079951c1e697818e08252041a58cc4e009975d33bff9cda9e2e5ef810f94950497bb3240054e066bdfa042373fe590971e5938f0ca2368f3592432c91b53aafb0d23306aa4e316c5464a70fa1978301d257d1b635224b21a790f7c26a036774ba7daf2931aa7abdf858358fbacbb2525127cfa653f569a87aadf0f785834e031d2f987edba9e8e6040c80ee698d6fa49f8d07785b609b287eaf907eb6694ae884c0bc08f2be8e919d5ada9cd2e176abc236dd6e0d5d8ea4b68148f9ca59f9b4c2062ddaebba436bcad6b7919debafa23edb0b6b23685acf651ec29da3010b5367dc6dc02785e0fc7311cae8d9aa357e922ebbf6be9e93368ff7d8658ad950f666cc66773994e551bc944d8421a7995eafc4b4a61e28858913e65ae30dd672e597adcc02c3eeb0f6c4b1e3e71af8502df63ca717e094b421077abf650cf6330af1f8cd4edb0e90b11790cd64be9a787bd4aae93538f0f24bcfac315b9f71cdf79d38f92e2425945c0ce8155005530c286600c9f6d5b30c90f224209cbaeaa8388ffd2bd07344307ae8804be702b2b9fb8d62d44b3602e34208588209f31b1cbf2a6811b77b128dd1cdf5fdec27059d1218a130e4dee4c03805489132f6de6c015911f944e8d15700d14989726be13f572b872373fed2fa8446e6fb2b02ad9a28fe2dc0ef9a350d55f6008986bdf39bec818c43ff1aa07d3617cc4835515cdc83fdcfbaafd72fa2c680f15112146ec23ce4b99911f82871b067d1641724b44af116d63476a44b7c98c76fd7cc2737ed103c58f63e28bb6022a01d756cdb4903a808518833d0086ca88c15675da0d1628f3c98c1069c10aec9e20320fad47881e4bcc94dba511969a63f664793f73cc4f56841722c296a6cd4fa05737bcefc91d01da6cff0c2a38fa83715bcaf411e75cd71e856759845d5b26758bce73116c56b2e1ddfded07fa5cd1fa3a78729f83ff0afae2946478a08af8d5629bb7c4fd001b0354963657e62be908c0d1021cb51be8803af215c13bf035158a4c117d0157e1a36c40a2e719d89e6aad433585518bf4a06c24c1ede01e807ac5452f28f184305132e187108098bedf2bd1dda989812a6a1bee2027db1c278b834a7787327525af28e66d1675cd1b6cd9b914d0bed84a601a265234dd3014611050f39f635320156fbc501f745321ebf6d3cbb0ed1fc5b29558f7a5898879ddac0a707c65bc30839f21539176c8bc3096241e0c696da6a07af0708559682e63c340fdf6a6ee1548822ee22279c67bfe536160bbf0994e80ab9bcde2b336d4092fc650b79369530bfe6f00c0a53e83d780980791240444dcac1b0a07ebaca7abf25fecd066f5625a6fbceb1918512e6a3477024a7c0e0c31e06cd5f36993c66d59c21b68e72de1e43f65678c3f45602a264845612ea9255fd27d82a1b08bc940cdfca3bcaa1a4e1b9c9e28af4505c0fa495be20df71bb7e90690ec8686b9906a2ffeb69932177482a10f31719bae8163d961d49e3dd83bad6ec121d4dc1e6df3ad20b4596ff4e459b11c328943dd18be8575cec50dea4056b7e3d7370d0bc80f939ed86ebb61333fb58ce2e73719526e26c8cad2e743b42caf27d241682a09a96a9828fb6446c82845c58c12658673ff89d5163b1ab01a65981a03a7410e726ffb8a32581469b0ca342713406d0de2468d54cddc3b431933e070007088d37fcb9f1d96892f6dae5ea7f93093038721e22bd982c4744d058c5379a7db688038b3f14c5a1fc75a9d290d9319b102e292df3fc6813e652cc2f48e29a0a3049917b423df93fef9698b2c469a090ffae68e8c6a7e9daecc1d62766acba3bbc2a38c12337cfc04bb52da8a57db046ddf49a9297096775bb6be05e1fbe78f023763e6c02bb8a13643351508283a95ac2bfbefa7eebb709104cc9baeba66e411e1cf51b76d9814b626bcc4fee007417e4e0022d7053ec6aa630446ec028a091509b64074f00d883138023281385b5cd81cc46f87c2b73880f62e886348b70e815a5cd8e9b9e2487641695be0aa92e6237f0b6569e962fb0e346799a13f2a9c785db815731c351818551bf1c056927002b7dc3b16992d606491b462d9bb03ad55335df11839d0d14c49c6d04cb72c0338005bab7be98dfbadda4d756666f7dc5d54d18d1137bab2ef04e48cf9a7c5acd2829b80b757f9afda71eb086ab8309aa63bb3bca675b609ef133f13210ab11b0d54f3317b49d3885414ad2a4a48ae50c3c491d45acb10ba60f30017ce16419a01d5cecaa922202ce75172a3f5cc6a1d59fc1dc1f0105d8ee72b2af027c77de94f676d5cfcce0b41d61dc28e97fc22920b0cfdfbd6ce2c958de5b2730587d800747cfcae0c311c960e3a6f29cc7383de497be0aa975dbafa84d698611bca406151e9c62f7c1ac857e9eaffbb301d5625d6a6050a0a0f00f59d7d06591115c2f54ca6f400d308dfe7156326d1bf2321af805b112c70b98378024c93bc2cd005fa77e8067967bc4dd5f6036a71a954e299bb1196023b5404c437c7333a1fc07bf276d1f5e823aa87aa22f06a3bfdeaea8411092e395d92c2520a72251b4a281bbedbc6428590cb04ed9218266551f5c6b7df09113938b440bd60f57c03527387ee8c024cb58d7d0326ac6509ec710bea7780d732091a5c465228c629185c6be7769b6b1be64f3482f269b6d4592f001012b49d6cd9383272c47a01f561a5ec7784e0c936a35604a2f794f60943fe3f32ddd66b1077879910f5153a8502aa665219953b7bbb3c38d43778b2ec7e04244f56d068e18594c423d215969d778d33ca9215d01c28678a4744984fd1127d4fa1b5580c35077d0055600229cc4d4d74150ad47447ca2178ccd07b428593caf2a3eb432b43223c41b3260e9eaff5d60d000aedc91ba944f9560c96f232c60e98a2888f39177a7db09013006a2ad016418c30c91cebfb502c7cf474c4a89d311371500b5e28c7608c8a4e9ea24c37ca7f12e8e8d1a0d54c83a49df514d781532767945238a0766a4a13c69ebadac6ffcd9c50420d904d4d2982d0159bf214b4ecf75989be8348a9d98153d10a113170c45f39e74ec6c64c6945556640b06630883db229e1f246d664eee97414999b446fedd17e5649511cc0cd99b510a2004e93f809934fbcf44fcc8ef2b405a5f02400fbe6ffcecfafd607744cc42bb3d3601c2768070551d74354e530f1d2c951d06db5718c8e74a9997f10133fad21a0a3962786f5db88af0d39181847f6d3973160744416baaf23b8a8840c84a612183a165dc5923d4493943610bfced0e0af5aa72fdd00ee435aee27312bc23165832b62eb6515838822a6d9012e850568a7938df0810cc8e8d74443476bdaaac38db0132ddcdff1d6f2c3895d3a34478d3a3a128024fe7a5b019093a7857f5e0c265ef7bda4065af1fd764c605a492039b262157fa160ddd4419ce819b7d70474c25e56aa97a679ca0d0a7d786d25cf3dab7fb76954f01aeb9cfcfcb31c849b120324e5f47a939b337c10bf4fb253d4762dcae01461ab8df66661022977924916c7bc9b6a55a4782fdd3e51eaf2ec835b1e91db1fbab52e9d54990658cfa0c5a66ab8587878157a39585bce70af906d6b3cca382975d3fc5bc6dae39e301edc59c62561501101df094a4ff853dce575d473f6d0ca7eedf721627509d1319faf9c32c49f6b28d5bb3f25f3061b09f7e340497fe17fdb2b748c2629176db3506b831876c6622128ee9616b6beb2552cbf8801947ad629b7cec7498052fcfbac9112246c5d43225d6cc907e17b07e827a28c7efa8c0821d118a39b93de134344141eec6b0942a0e4ca8373d14d4de5a06a5011ec56aca91dc33a4653948860b0e7bd455376affa517d2c9e83e60d548329505f2d8cf33f0d626bb01320ab31dd7ec00778efd756174303ece6706a9c86c937bee7a18bc9bba753c7736d9c47defed902ae71090cf0cedc4be255ee253a1f8149047a1a6bcf7f70e83c94941c32dc23e4048bba96388b5612ae32eb674096da615b18a57e021ecf0be630bce0b675a78ea759172cd11efe72e8017daad6912855f3874863fa8341e43a2d6d99a6e2ef712a40ce9de1c8a77141731a7bc31495fad18beaf2dfff1cb07f0434e804ca08eeb68534590b5022c6046e9108261a9a07416108e8c18851f317339ee91655b985b7816a72cf0a3973e4a9a3d1e2d8b92b526e09107767d7f6cd121f63b29c890444e11bbe32b32b1fe259fa7f1c2b2c98bb4e50aa08dfce23c01c3b719a26129cde71e5606ee9652403f5fe0c1e407fc3db0a5170aafc1641e6dba42838f8c6fcb63044ff59230238a6cad1642233e8de1674cf533505d520e636c713b492acbe4960b231e713183493322f5f83b9008332e087ac8b40bc726e55ff649ef645d3c045e895567c75099d36075b65c63c34ef4807ed0e23a57a8405110ab3e683176afb22bac7ea4382d80e35ff1e031b94caa7f4da11fb165ceacc01b7db53409ea17ae3e0341ff580a443bfa4b8e21ce3f079385286cb925b559944ccf2aca1e8dcd643fc4418939b9c954733c617c7a9ba5d89a4005c98d4d52a5491ad2b61470323c65778d920ed4dd7c7782f4e4732893d929a8ec75ffd6d0fc0d18eff447204c80c141856e04b2d02db4c49ddad31da43a8eb25d3ecb73861e2129734a2c4ee3ba3acd52525395c491d8bd0b3fe0c5abff44dab1574809e10d1e46a581009d586051aa1df97fefc247c0bc16b4464f8d5db09ad064901f7456eb39b0d5de69216df06e535cedff77559b52424bda36a509e8bcab5b34b42ae4614dc7a9f5cea8fd99c79de7f1253c6a631a1d98a6c516eea23173884113b819044c435f618237be93bb0c82e6995e814d2782ebdf5f5c0a138002b0afff91ade2277e828dcdcbc9c383919a3a9ceefb82f57ba8913e6bc2550eb6b1e4693d7310814074eb6208496986cd277396a884955494d9419acf1c77542e99972dc4b8e46a9095185b315f83a0f5e3b11924702c6914f2770166cce8c97830d2d3372208d4b8b57da3208ac8cd3c2ad56d3abb78aba02de82ed58d579e8825301663babdd7a66d0de1b3a427b6e911063957690aeadd47193ce03739d717300f0918492989b7074f1835663551a6c7331849452027d1dd8a2499f8e20c18581813e67f4093afcc34d6a4fd0c4c2123941e3e2c06a925fdabb8a7033d0934bf57d60725ed9dc40b97902ffe301d9fcf9c3448d88432749871f5cfe6fc549d1b75b4a76c6b69401ae21a71a5e791440a922053fe832e04bb7cd59eb08f29d8347593f28838ea5aedadf3bfb42865a7d5a6e01e68b1fa12b7a3bdbcef4fe8cf199d8225caf1987815bfb9c2ae19d0c3af7524018d95e20737961cef3f08ab7275ff9b7a61e0c99e21845ba43c5554ba39840a547027014532444fd46258bd8ab74053f6f3819ca8c2bf58ba28cbfff5088457e867900f77eb17b09652323595ddd976e31d28fe8c6ae6cb94648ba16f235d05a536ba82a4a7f6c29efc0aca00fc4c74198f26e0d37521f28f7161cba255b63b4b97458c44adb9fdbc51bae7e80267851d19b512d3d140a1b7be6001435975c75f293865f66db5e3b984d483d9300db2fadcc23b41fc19b97a4e513907c3f3d8c838bf015e5a3f384e17d4c88d53761cfec5323cd2ff5fd2eed6cbdf36488d12883466b6e232dafc88a4e6bd0ad6caf72cc1fd406138f36f461efce4f3bd9e6d23cb3d47fc4035202fa6e08189cc4daa82cc5efe856324dd069c5491be71714974312a5ab564d4b32216555633c749d5aca1f45a70a561a347777a81b026bc9d5166c1e75b6f3b34081df7dad44e25ac4632cd21303ac74ad4c6e6af94a1029a0bec6188a98b1db61d1a8526e56853dfa40055fd86e4e1d185285c1c56baaf8d03972d473ca8878756d849c6857b17cbd3e3276bf4e2e1c1ceecc5c8fc6e5e0a47745c3fd51f07a3a9c8878a781a3555ec16a0b503cbb0bb7ab73d057212f771783a6c8585ac2c5620c8a54b382c9c6d92b4515662ab5f64368a7dd696f77c4887f26508ea4c82f750caa47041291c2c1e08258af9b4e383c1f84f278d994dfc375e5ed91a8b3b03849312433e9c46c59b00acc63a23b4f049d7eb2cdde918d5a0008358952d47c4f29ae469d7eca9ba64485eed639238deee16a7d709a254a905613e9a5c4b5f4411bd0c795db11977cf8d82d8c8e4cce6920f32f93133fc9b75f20677ae9e052f90dd60ac761cae70eeb113d226eaf176311c7e8731571f8859b78cd86039b0d659d38b03ccee3968af48a8d87f3f6b5b611cd6073e41a069bce5d74a10cf909bd8e45676430e49f6ca16973dfc8d02ee60893028229045bfd8d41b1bd122096ff410bd826c2e408d9269b6960820b244822195ff74c0e2374f7c73145383ba9e4a2a66118c5943232047007e649afd67513214881996af349b81be70c0ea6d4c326b00fe05192340c2e848a43146b10cbe25c416a38d80b11edf3afd71c182694715aa89ab202756f60a2a174cea98fa6daa073ad216a26e6c40d1a1aba3ffa535ca8158ca0424b9f189056151703ecf1acbec8f068133f0a7e5259c64e3701ebbeb04c9cc4a9b0f010ae73a452ceba74c539cbd86f62562d6a3398ca06e61bb31d9bcb548a89da2d3c3cf7f7adea9926a3f1870f2da4e606a7c6564e579f83ec43fb983f0a82815027a2f4974841bca206ff32154556b5547381775595c38271aa5b8a8a0f341cde584015289ea569f515c37bd8a8cc5c5f70c2a84590e14e07974b2bd18d69d3d0b772b48416d456052f98df9c96c9324345aa4a03c55ffe4929820407e8d69eb248dedfc4753b867f7764b383ffc78d48377c7c49bcb8b67f41d55375290a0ab18a440df77d465e8cbda4d33c653a534a91064424090a7e5342819961bfe7dff5d0db3ddeaec85b1c3ace0925702ce88238be3854369f68a28513a986e0e4169b94cb852066385cb3e3e8508726e39b0285d17648f5f7ae68e61052fa2f1354ef85f832c64595d4791033cd76297799f6fc5c150be8d14154a98d5f0e558e0c70f69ef63364b9fdee2bd160356485f596abb96b1302a61a5c9398821fe3932c5aa6a91355ec0ba96960131e5a6d8acb2b3ea8c654d5c7271b06367d9193500d162957b2f1bb3eacfb048e817b985f9c8a3aed9ddadbf2cda1718658f4a25bd69c05218617b325787fd153b170a8874da3031a74330dfdf5beae7e0b09670ec48a2e0237734db4a313fec3c75b93df4f39787a09cf96e4eba2081044bbe7834da18681ba45d8f302304904c561b4f6b65851f1d6d6d318431c95a2e600844ad3ee24cfa0eba94ae93c408da23aff9090a51eb91ab91e12083b18377aa1665eaf30eb91ee8398ebc1c74e0506115d0e76b064e6b7ef186d87f388bdcbae8f365a7d2ae327f3f90c7c39d613b3445be4315e184906035ac1c22a634f8cf9ce8aea7cf8d6b1d3dba83fa018976742ff6f4372d26f3a6c023e744074ef78aeba84e38291202d254551e4726c3c5185a33292341e4222b56e629de813203c839b4e012445849b22edd3135942f4d6e473f7e2dfe752787cacd14a5bc9de32b8d83aa12eac91d39965c8843a6390ff96601bfeb9da58ab831da867693e716269a1d66c381145501196a8b13beddeb5e030a3b595f89b3c87b6b26cb310f0d36f7f0c302ab087704f5c4694e09203c993bd0ebbba399f14f5ce603f41e112466b7ebabb84d724438f8cb97f9ff1f37492a2bfa751b27c4bb5155667264b24b6bb018baf0742f1c3a74f7e5131f54f24b8ef2837fba23844c53fc52841eacf091cfc9a9ca8c93f9675ac515c717386745c681aa40a08315bc9ad8c79706948d8f21b324dea6fbf739ca120edd41be39980fe3d54fdc754ec9aff7978fbd59d63958a69e6918a7885fc3e7eb240c54afe7a0b1abef12c89122738eee1cda555730fe1188a9be0ac10a2fc88aea20ae681d619f9650edbd961d2a85cef414491fc24a9806d087901a26247fae1b572410db416f5e9268b2431b4962668a6b7143e2f59e516d02cdc29daf2ffc74a071c98bb69c445c8844a48d5c9d3bfc4fe3f2f500a937032c25d5049fb0d1742419dca3884289a7c0dd91c55a9bb23d7f7f698e236515eb52002d38399a8298eca195c22228a1d5e5f96f7121e4371f06367f1e8abfbb7aedb08d6e82cacd126dc1fd8ff7767a153a1605e880e55bd742724f99c4ebca80356403c04f6b1bba995d2d665d4562ec1299c04bd94da3a12e4bff4c02531d685ce5bb7ae88be012d79fc83779409f1aa2d71039710191f4f410d2db218c3c1ad8fb53f48bc37ae2adf8f73a7bd57b7005b4f65019d8cad44eb56e11ce9a345ee8c43d3d89d3f8ab7f68c26121bf0b5ebe68d161c10847a8cd0aa67a839def2091ae4f3f4bec7ac62967119bfa915a2a350964ce93a8e42a9780e2044a7a3d14d8cddfafeaabb74cd4f9cf8a8c89c327e74aa8606f97f49751e47687b6e1b38c8713f3868733a038287d4b0817ba56f8fc0a75c8793e9c8f13a2f4b88bfc3fa1bab0d1768c2b99313844a1bb793f5f408d745d7ed5370c48df4f1c7a18d1189516b7967645a67bed2ba23c0509aba4e73c4be9d22858f415157317b444506f4b26da3f58865ca245308229bec930a0b964058c2e78a51afc4920075479314e0b5da1dcbafdfa284add0e6dab0289b3452d8f339d806b17b7d0bc4ba8520c070c7e497b70bcc8b8997e04cd10cda89e3d72369155b1fa1f32372203947303c2b9f9ccdb0608450d7621aed73e22be22c618ebdcfafddce27728581b99f80a0563c55fa9b6a2b2ae756bd90676f2a60779d9ec773370cc14a5b1132108dced1c34b0848d922f1dda44fda6fac31a35d8a854c2d3e5eab0fdc6509ae842a2c2a4b9cfd9e41e0e226090791693bd253389d80ecbc7482867d5bd5e1e5bd6d0a96bc3762c9da38fbb0a37552f261243cd815388ec8ef00cc40c6092f574d3ca64f24de4496f6eccb0a2fd7d099b49b646225bc98d5fa305bd51994d124e677619085672893bd52d8e7b6905b155c65e5fc5aeca02d8629e19953447c8143a80359111b36fa9c4ce73eb6ac982e18f22e0ebe5ead6db1789e9b29552e0f78efb9f9ce634b478154ca8cca2a92425a48dc59a88e9499355244ac1ddb4dc0f65abaf9fbf5e00baa809766a5921f86f22f1fbd35749d34f5374ec77f268f9b3082b1be312886a85b980567165bf420fc4a12464c1b96ad37f6900101a69a00e8e3d141be16b016babaa8d7f3353a1f80c16aa5d0f955fb02c3e4fcd80d653afc8a32bdb74cce935024b42f2ad89134e5cbe6375e1907a2aab0627d56581130832a68de4225f21a95b094013e947958623b1fae7581034d3986ef6c9b9cf14c8b5d5fa4e1599da83ae79f53cab1e712f13f53a7cb7c0db3f9818dea93a936d8cfde62d6ff94ec61580b27a59757fe2eabc3947eef1b7631ba3fa07d95bdf03685604da227b03027cc94215b96cf6c7bd0ca5a84d6183e33b58360dbaf54fdf37fe16002cf0eef09eee375c385127efdb1a21d7d802319b405d3bd4b0acdcf708c4990857159c60b753c409da32886854211e6ea384bc67e0d0187f06aa703b63524ef604a63a5ae1b558a1ba892f40da8a7e869e52296586b55f8c4b0db87af1f62f53ea41d381107a7a2308e639809fdc03fdaa465643fdf95b5da99db0fa03333440cd8d918946f50f35a13b8f03f560e3d22b8cb52952146c4762da860c0f204b4afa7ec1bb4a2e15e57ef056cf546322df80b8ce696489e62c05ceb7e1b7954b9544141e90683807c751645784b2f2f363e7eb74e6e1c007e52e03b0e0fef980bca2e7033ea30d6ea26d154d52ca07c423f4caab424681751ba0db42b8e2bf5b2abc5e6c375d8e4ff5d9dd5c00f4f2f8ac2ece79acfd668ac797821748044b1de4799cc3fabf7b8ef12a83fb912235d5b0274945a175f0738a951198ea5ba13fb72ca3b87c7482484eb7aa7739430501427519498a791566e4a7178800e7b93707e00d1f2a19b01816f20f8734dc8b3ec61f48a7299fd712f2ff2eeef324a528238bb94fcc98c3d2e6a288bd641e12992200688f1d363c3766b4366c0bf1c26a857e584bd04fa3cc36a15447dfdd8c7884f14e2f0a40c0fa806854f932625cd6018b2b458526174174d36a086472a896c0ceadb38dd41f800f8666c92dc36e5761a95276f7df96d21dfa012d44ac8e9effe8cb2ec54d482f893602e5e9da95d049aa242b8b251ba87e3f8115c2b35f0a292b3dac22ab1c222f2ee950cb43be1060f72e64ea741fbd126f4329ead878904393d7599f5f7461fae09b13100103c458da4f649857e4d698a16d03d74ec23eea47fab337ca92cf7ae37f19e4c8b3517cb6b95d9ebb78b36954fe6f381232cb005e13f6a68b511682b94bd377501aefe2f165a2a0e5a1b16dec7b8ff10e4cddccd069dff3a91a55a60bd3a697e0ab17f80653c7f14c9fd98dd1bfc906a4f99703d5d36162ab3d522ef0e851a15f057b0f9ad1dbcb4f83115bb3ae333ed8cfbcd4d16f56124ff603b6d1bf249aad9475e9d95a42645627f45636d0a0d2830e90fa8087aaa9768d4dd0ce9c465e61745a215cf046d0ff72b28e3defb4617d7db17c37bf0363cb88da5b03a06dd3370697e3d8a09ba2f761f8af1a30e3b5a1cfebd4974cf20182cff940d8756841ab7007b372bae7d28bcd1c3e65cf8abaa11f05b5acd561501bdf5077a07ee458d0fa244f596eb99088309f6c93ef1ef8ad712efe75b8b5d2e8108b67c8ed1e263f1ff95c58cf7f50473d4d4824bf8ee8092583f01119e6913348919eaeb9bf626c66194ce06dec23f4ec494a461ee1a46c53f47f2be96e030715b0b9862995c95c578d2a47508d82d4fe072cd1e2152233114764f8846d0c2a933c7ace68437e650c113c084e39de628d253e72c1e5ad1ba48479ce37a5c8ca971ac5b133db5f0ed72ab26f2cb2093db10f22c2775cf747509c1225e2e63789e6582ea49090a14810e392042472a3bcfbaa072810db2887d32869951a0f041bedf0292f534a6ce3fb0ccf727d30e5a951eec112f42bfb79fe477ca4d3691c153a161d14756326053d73b4abb667532dc6609f8340ff030fbb0b274b4fd9c47fd14fc20753c4133695bfd62d3f9d5674e072b1d400705a4f73ba2a700111cc63253898f4ea270d10c9b28a4d3b53b62c4b577b9d68ec6800f1746c124a5b26f46922c2cbfc799f51f95f8cd67579199278627ace6b7d2e8b25310820ff7d553aa4a1bea47b33060583da8fdc4e048887e5fe4a9bdc39a3636301256f5c44e3b61588da6633d9526df8d21eecc0d622366b8ce095dfa85f1c1a9ec054c211c50ce0e605647629cc22df1b9424ae06371876e898e5f02e5303fc162073e5285ab1bb52393c7aa1a415722d45381c80908cb833bc3d5fbb5529dab1db8865138f3d983d2a46fb0a92afb15a1b6085adb02e00bf8cdeb3df416c71c103852f5cc0ebe8b48f1bade303169ae00fac07b7f6b731a3a94721366f3bbcd87a4a1479bf9acf12e246a5f354c5da5a136cada71389be14d77a7233a092fdbc5866787f11e676748da41572be0a35ad65f84a7cdeae24334251213fdf64d75742bfa6bd3a1c91eb8b81aa307c3bf752273dc75db2846f36d2088ddd1616ac6927cc2037cd23fd73f7a75b7d0840fff17ab6987342c14a3be9e32ee75ef4aa90d3270d5c1ac8f88d97b065ee004b69d59fe9d42eef2f60200df93015f641731e75be9d41a0e7ae1e06fadbcb8e70f6013894aa7a15505fe8713a636ead0d2939b81e9253e7c01929915040a6ca747ffb6fd4100224473b8dcd7d72ab1e9f8176be3a9a002401962545302406e6947fe63d6f36e2485afa5b116445c690a70c48f29e3efb81d117f609a7198a47a72bf1e7f04b97fd4b585d74dc18b888124cf15b701f38492e42a8cf4b4bbab9ed25d38d8ddc22fae004bf98da86bcc70d3656c12cbb91b483d06509272192251f7f89bfcdef570b21535c6f53a010d43459ce49d00668e9407446792fd0b5b5f89f358ae286d1180b1c7f8b7ff6ac14e396a628a6dac99c30ea70b029c5a8a7638a017ccb05820251212cdc9254cb391ada6907bc52505cfb54096deeca0708c4bda291817eae0e46688a2a5ce560e669f57d68a509199a0153007d5ff00a6b090a2bda6b51d4dbd29e9fc9b9f1db27c8b0bd24f7dbde59e42f065d7e916b2fbd5091c821c0a841d314a635406bda84634b183171b2e78dedcfb2aec50b2eca64676ceaefa2f58b246e69f4330380a24bb4843db0d1fa020230f0de090822bc3518d84382e148aa6691f7503ef0b8ee71c67be15fa4355cfc10090b260033ac6845ce4d04f7bee281d6fdd4b670cf2e10bcb7a81e956862c81d99baf896b3d5f48de20ae8f8a5829d5ae3e699942610a692a295ddbcecf09b03b778d19d7ba08a29271c341eb34b83197a2783d95c9847beea1ef717d7cf5fecd7250473d4195808ffddf7b870f52b8aefc42b3e89ab9baac3dd13197c1c5ad34e4884cabb1147aea4803d0019aa21f80cf73278e2906185d743f759122e9f3194b8328b8ea58a181eb3869dd187701628dbef97adb404130795b5d795541347f3b1c25eed7edfa8902228941196a9a0f2cfa345aa99bee485832ec0410a2361e44cc951f1917d06d4cbdc957f947b43f7132f5323581c2e3733830e111aea31664f19df8ea6841cb79786abb52500c01103beb827281cd5626a06f2c009c08b157128b68389160e28c09d6c819719c2083cc57f5241443173e8d2657148d07949aca1017af85937e16fe8c14666336d81b8c9b7b85db672a", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x1ce063247ca37058db551a8d99f2f15cfede61fc796acc464a9cdce4c18f6a46597283ea6b8648673305a3e06be6dd83b7bc1840081d50d4deef1ce53eba21e914248dbf89d86998772b66900d78e98980ea2afc3c8fe5b93f4b38052f3018a2301c346cb44aa03f8995eeee230970772d6268cd7606740f269bb4e609a01a3a15dcaa0b4c6840028f6d4fa8c460d5a7d687d1f81c9de453ef2f5ead88767fd22af0d0e90c36f95605510f00a9f0821675bc0c7b70e5c8d113b0426c21d627773b4a69b6ec0eda668471d806db625681a147efc35a4baeacf0bca95d12d13cd942", + "0x3f1467a096bcd71a5b6a0c8155e20810308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc": "0x000064a7b3b6e00d0000000000000000", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0200", + "0x4dcb50595177a3177648411a42aca0f54e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x1ce063247ca37058db551a8d99f2f15cfede61fc796acc464a9cdce4c18f6a46597283ea6b8648673305a3e06be6dd83b7bc1840081d50d4deef1ce53eba21e914248dbf89d86998772b66900d78e98980ea2afc3c8fe5b93f4b38052f3018a2301c346cb44aa03f8995eeee230970772d6268cd7606740f269bb4e609a01a3a15dcaa0b4c6840028f6d4fa8c460d5a7d687d1f81c9de453ef2f5ead88767fd22af0d0e90c36f95605510f00a9f0821675bc0c7b70e5c8d113b0426c21d627773b4a69b6ec0eda668471d806db625681a147efc35a4baeacf0bca95d12d13cd942", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0500", + "0xb8753e9383841da95f7b8871e5de32694e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00000000000000000000000000000000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb31a487e6cc5c348d84c0aa0240b2d7485675e52cdb283a87973652f6acb42c830a5a5faa80f7a707e": "0x1c346cb44aa03f8995eeee230970772d6268cd7606740f269bb4e609a01a3a15", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb32d8a863b519f21b700f379b621bd73c45c7d155d2a1fe6a04649e3ece7c7e03b70b3a6242bc7c127": "0xe063247ca37058db551a8d99f2f15cfede61fc796acc464a9cdce4c18f6a4659", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3313cc789e5100ca680b6f570f356fef7b891afa2e1c30fca89bc7a2cddd545fd8a173106fce3a11f": "0x4a69b6ec0eda668471d806db625681a147efc35a4baeacf0bca95d12d13cd942", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3624ad9e747f9464220d8c795eef2620fba2bde74dbc36461c07998ebf600ed265b746c1e05c70606": "0x248dbf89d86998772b66900d78e98980ea2afc3c8fe5b93f4b38052f3018a230", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb368a58d3eb991155a689e1a66fa33b75f66415021aacc4fa23f49306a3c21407748b8b2d39b4abf63": "0xf0d0e90c36f95605510f00a9f0821675bc0c7b70e5c8d113b0426c21d627773b", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb39f053c2746e722456610a5024c2a5db3d02056d4344d120ec7be283100d71a6715f09275167e4f38": "0xdcaa0b4c6840028f6d4fa8c460d5a7d687d1f81c9de453ef2f5ead88767fd22a", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3d21fbce2a623d5a4049bec59fb5fe6adea4578250578e89dd7e51ad88c7c92493d6f451c6680925c": "0x7283ea6b8648673305a3e06be6dd83b7bc1840081d50d4deef1ce53eba21e914", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19507a74cdfcf05a85226175726180f0d0e90c36f95605510f00a9f0821675bc0c7b70e5c8d113b0426c21d627773b": "0x689e1a66fa33b75f66415021aacc4fa23f49306a3c21407748b8b2d39b4abf63", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19507ae4ba128e21c1c36175726180dcaa0b4c6840028f6d4fa8c460d5a7d687d1f81c9de453ef2f5ead88767fd22a": "0x6610a5024c2a5db3d02056d4344d120ec7be283100d71a6715f09275167e4f38", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19507c0de6df75d99ec461757261801c346cb44aa03f8995eeee230970772d6268cd7606740f269bb4e609a01a3a15": "0x4c0aa0240b2d7485675e52cdb283a87973652f6acb42c830a5a5faa80f7a707e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19508f578a71d93450886175726180248dbf89d86998772b66900d78e98980ea2afc3c8fe5b93f4b38052f3018a230": "0x20d8c795eef2620fba2bde74dbc36461c07998ebf600ed265b746c1e05c70606", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950a074c92c894f26b161757261807283ea6b8648673305a3e06be6dd83b7bc1840081d50d4deef1ce53eba21e914": "0x049bec59fb5fe6adea4578250578e89dd7e51ad88c7c92493d6f451c6680925c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950a273e8876df6fc8e6175726180e063247ca37058db551a8d99f2f15cfede61fc796acc464a9cdce4c18f6a4659": "0x00f379b621bd73c45c7d155d2a1fe6a04649e3ece7c7e03b70b3a6242bc7c127", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d017874cd5fa32f261757261804a69b6ec0eda668471d806db625681a147efc35a4baeacf0bca95d12d13cd942": "0x80b6f570f356fef7b891afa2e1c30fca89bc7a2cddd545fd8a173106fce3a11f", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x1c00f379b621bd73c45c7d155d2a1fe6a04649e3ece7c7e03b70b3a6242bc7c127049bec59fb5fe6adea4578250578e89dd7e51ad88c7c92493d6f451c6680925c20d8c795eef2620fba2bde74dbc36461c07998ebf600ed265b746c1e05c706064c0aa0240b2d7485675e52cdb283a87973652f6acb42c830a5a5faa80f7a707e6610a5024c2a5db3d02056d4344d120ec7be283100d71a6715f09275167e4f38689e1a66fa33b75f66415021aacc4fa23f49306a3c21407748b8b2d39b4abf6380b6f570f356fef7b891afa2e1c30fca89bc7a2cddd545fd8a173106fce3a11f", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x1c00f379b621bd73c45c7d155d2a1fe6a04649e3ece7c7e03b70b3a6242bc7c127e063247ca37058db551a8d99f2f15cfede61fc796acc464a9cdce4c18f6a4659049bec59fb5fe6adea4578250578e89dd7e51ad88c7c92493d6f451c6680925c7283ea6b8648673305a3e06be6dd83b7bc1840081d50d4deef1ce53eba21e91420d8c795eef2620fba2bde74dbc36461c07998ebf600ed265b746c1e05c70606248dbf89d86998772b66900d78e98980ea2afc3c8fe5b93f4b38052f3018a2304c0aa0240b2d7485675e52cdb283a87973652f6acb42c830a5a5faa80f7a707e1c346cb44aa03f8995eeee230970772d6268cd7606740f269bb4e609a01a3a156610a5024c2a5db3d02056d4344d120ec7be283100d71a6715f09275167e4f38dcaa0b4c6840028f6d4fa8c460d5a7d687d1f81c9de453ef2f5ead88767fd22a689e1a66fa33b75f66415021aacc4fa23f49306a3c21407748b8b2d39b4abf63f0d0e90c36f95605510f00a9f0821675bc0c7b70e5c8d113b0426c21d627773b80b6f570f356fef7b891afa2e1c30fca89bc7a2cddd545fd8a173106fce3a11f4a69b6ec0eda668471d806db625681a147efc35a4baeacf0bca95d12d13cd942", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x04000000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs index 80d2376c6811..1f98d3ba964d 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs @@ -59,5 +59,5 @@ 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_foreign_assets_helpers_for_parachain!(AssetHubRococo, xcm::v3::Location); +impl_foreign_assets_helpers_for_parachain!(AssetHubRococo, xcm::v4::Location); impl_xcm_helpers_for_parachain!(AssetHubRococo); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs index 608690218d2f..6066adec52c3 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs @@ -59,5 +59,5 @@ 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_foreign_assets_helpers_for_parachain!(AssetHubWestend, xcm::v3::Location); +impl_foreign_assets_helpers_for_parachain!(AssetHubWestend, xcm::v4::Location); impl_xcm_helpers_for_parachain!(AssetHubWestend); 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 index 43d182facdd5..36a701d24c27 100644 --- 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 @@ -18,7 +18,9 @@ use sp_core::storage::Storage; // Cumulus use cumulus_primitives_core::ParaId; -use emulated_integration_tests_common::{build_genesis_storage, collators, SAFE_XCM_VERSION}; +use emulated_integration_tests_common::{ + accounts, build_genesis_storage, collators, SAFE_XCM_VERSION, +}; use parachains_common::Balance; pub const PARA_ID: u32 = 1004; @@ -27,6 +29,9 @@ pub const ED: Balance = testnet_parachains_constants::rococo::currency::EXISTENT pub fn genesis() -> Storage { let genesis_config = people_rococo_runtime::RuntimeGenesisConfig { system: people_rococo_runtime::SystemConfig::default(), + balances: people_rococo_runtime::BalancesConfig { + balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + }, parachain_info: people_rococo_runtime::ParachainInfoConfig { parachain_id: ParaId::from(PARA_ID), ..Default::default() 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 index 0b99f19bc130..942ec1b31d2b 100644 --- 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 @@ -18,7 +18,9 @@ use sp_core::storage::Storage; // Cumulus use cumulus_primitives_core::ParaId; -use emulated_integration_tests_common::{build_genesis_storage, collators, SAFE_XCM_VERSION}; +use emulated_integration_tests_common::{ + accounts, build_genesis_storage, collators, SAFE_XCM_VERSION, +}; use parachains_common::Balance; pub const PARA_ID: u32 = 1004; @@ -27,6 +29,9 @@ pub const ED: Balance = testnet_parachains_constants::westend::currency::EXISTEN pub fn genesis() -> Storage { let genesis_config = people_westend_runtime::RuntimeGenesisConfig { system: people_westend_runtime::SystemConfig::default(), + balances: people_westend_runtime::BalancesConfig { + balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + }, parachain_info: people_westend_runtime::ParachainInfoConfig { parachain_id: ParaId::from(PARA_ID), ..Default::default() diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index 7077fbbb0a9a..30e66ced1fb0 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -63,11 +63,11 @@ 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 PenpalTeleportableAssetLocation: xcm::v4::Location + = xcm::v4::Location::new(1, [ + xcm::v4::Junction::Parachain(PENPAL_ID), + xcm::v4::Junction::PalletInstance(ASSETS_PALLET_ID), + xcm::v4::Junction::GeneralIndex(TELEPORTABLE_ASSET_ID.into()), ] ); pub PenpalSiblingSovereignAccount: AccountId = Sibling::from(PENPAL_ID).into_account_truncating(); @@ -132,6 +132,7 @@ pub mod accounts { pub const EVE_STASH: &str = "Eve//stash"; pub const FERDIE_STASH: &str = "Ferdie//stash"; pub const FERDIE_BEEFY: &str = "Ferdie//stash"; + pub const DUMMY_EMPTY: &str = "JohnDoe"; pub fn init_balances() -> Vec { vec![ diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index b11adacbde5c..578bca84ce5a 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -228,7 +228,7 @@ macro_rules! test_parachain_is_trusted_teleporter_for_relay { $crate::macros::paste::paste! { // init Origin variables let sender = [<$sender_para Sender>]::get(); - let mut para_sender_balance_before = + let para_sender_balance_before = <$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 assets: Assets = (Parent, $amount).into(); @@ -302,9 +302,6 @@ macro_rules! test_parachain_is_trusted_teleporter_for_relay { assert_eq!(para_sender_balance_before - $amount - delivery_fees, para_sender_balance_after); assert!(relay_receiver_balance_after > relay_receiver_balance_before); - - // Update sender balance - 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 76179c6d82c6..7a289a3f1ac6 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs @@ -23,16 +23,15 @@ use xcm::{prelude::*, DoubleEncoded}; pub fn xcm_transact_paid_execution( call: DoubleEncoded<()>, origin_kind: OriginKind, - native_asset: Asset, + fees: Asset, beneficiary: AccountId, ) -> VersionedXcm<()> { let weight_limit = WeightLimit::Unlimited; let require_weight_at_most = Weight::from_parts(1000000000, 200000); - let native_assets: Assets = native_asset.clone().into(); VersionedXcm::from(Xcm(vec![ - WithdrawAsset(native_assets), - BuyExecution { fees: native_asset, weight_limit }, + WithdrawAsset(fees.clone().into()), + BuyExecution { fees, weight_limit }, Transact { require_weight_at_most, origin_kind, call }, RefundSurplus, DepositAsset { diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index 6309c0584107..87a090bf1ae6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -35,7 +35,9 @@ mod imports { // Cumulus pub use asset_test_utils::xcm_helpers; pub use emulated_integration_tests_common::{ - test_parachain_is_trusted_teleporter, + accounts::DUMMY_EMPTY, + get_account_id_from_seed, test_parachain_is_trusted_teleporter, + test_parachain_is_trusted_teleporter_for_relay, test_relay_is_trusted_teleporter, xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test, TestArgs, TestContext, TestExt, @@ -90,7 +92,6 @@ mod imports { 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; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index 313fa953dd05..70dde03d75a2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -493,9 +493,9 @@ fn para_to_para_through_relay_limited_reserve_transfer_assets( ) } -/// Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't work +/// Reserve Transfers of native asset from Relay Chain to the Asset Hub shouldn't work #[test] -fn reserve_transfer_native_asset_from_relay_to_system_para_fails() { +fn reserve_transfer_native_asset_from_relay_to_asset_hub_fails() { // Init values for Relay Chain let signed_origin = ::RuntimeOrigin::signed(RococoSender::get().into()); let destination = Rococo::child_location_of(AssetHubRococo::para_id()); @@ -526,10 +526,10 @@ fn reserve_transfer_native_asset_from_relay_to_system_para_fails() { }); } -/// Reserve Transfers of native asset from System Parachain to Relay Chain shouldn't work +/// Reserve Transfers of native asset from Asset Hub to Relay Chain shouldn't work #[test] -fn reserve_transfer_native_asset_from_system_para_to_relay_fails() { - // Init values for System Parachain +fn reserve_transfer_native_asset_from_asset_hub_to_relay_fails() { + // Init values for Asset Hub let signed_origin = ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); let destination = AssetHubRococo::parent_location(); @@ -691,10 +691,10 @@ fn reserve_transfer_native_asset_from_para_to_relay() { // ========================================================================= // ======= Reserve Transfers - Native Asset - AssetHub<>Parachain ========== // ========================================================================= -/// Reserve Transfers of native asset from System Parachain to Parachain should work +/// Reserve Transfers of native asset from Asset Hub to Parachain should work #[test] -fn reserve_transfer_native_asset_from_system_para_to_para() { - // Init values for System Parachain +fn reserve_transfer_native_asset_from_asset_hub_to_para() { + // Init values for Asset Hub let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id()); let sender = AssetHubRococoSender::get(); let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000; @@ -749,9 +749,9 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { assert!(receiver_assets_after < receiver_assets_before + amount_to_send); } -/// Reserve Transfers of native asset from Parachain to System Parachain should work +/// Reserve Transfers of native asset from Parachain to Asset Hub should work #[test] -fn reserve_transfer_native_asset_from_para_to_system_para() { +fn reserve_transfer_native_asset_from_para_to_asset_hub() { // Init values for Parachain let destination = PenpalA::sibling_location_of(AssetHubRococo::para_id()); let sender = PenpalASender::get(); @@ -768,12 +768,12 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { amount_to_send * 2, ); - // Init values for System Parachain + // Init values for Asset Hub 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 + // fund Parachain's SA on Asset Hub with the native tokens held in reserve AssetHubRococo::fund_accounts(vec![(sov_penpal_on_ahr.into(), amount_to_send * 2)]); // Init Test @@ -824,11 +824,11 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { // ================================================================================== // ======= Reserve Transfers - Native + Non-system Asset - AssetHub<>Parachain ====== // ================================================================================== -/// Reserve Transfers of a local asset and native asset from System Parachain to Parachain should +/// Reserve Transfers of a local asset and native asset from Asset Hub to Parachain should /// work #[test] -fn reserve_transfer_assets_from_system_para_to_para() { - // Init values for System Parachain +fn reserve_transfer_multiple_assets_from_asset_hub_to_para() { + // Init values for Asset Hub 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(); @@ -939,10 +939,12 @@ fn reserve_transfer_assets_from_system_para_to_para() { ); } -/// Reserve Transfers of a random asset and native asset from Parachain to System Para should -/// work +/// Reserve Transfers of a random asset and native asset from Parachain to Asset Hub should work +/// Receiver is empty account to show deposit works as long as transfer includes enough DOT for ED. +/// Once we have https://github.com/paritytech/polkadot-sdk/issues/5298, +/// we should do equivalent test with USDT instead of DOT. #[test] -fn reserve_transfer_assets_from_para_to_system_para() { +fn reserve_transfer_multiple_assets_from_para_to_asset_hub() { // Init values for Parachain let destination = PenpalA::sibling_location_of(AssetHubRococo::para_id()); let sender = PenpalASender::get(); @@ -965,24 +967,23 @@ fn reserve_transfer_assets_from_para_to_system_para() { // Fund Parachain's sender account with some foreign assets PenpalA::mint_foreign_asset( penpal_asset_owner_signer.clone(), - asset_location_on_penpal, + asset_location_on_penpal.clone(), 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, + system_asset_location_on_penpal.clone(), sender.clone(), fee_amount_to_send * 2, ); - // Init values for System Parachain - let receiver = AssetHubRococoReceiver::get(); + // Beneficiary is a new (empty) account + let receiver = get_account_id_from_seed::(DUMMY_EMPTY); + // Init values for Asset Hub 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 = RelayLocation::get(); - let system_para_foreign_asset_location = PenpalLocalReservableFromAssetHub::get(); let ah_asset_owner = AssetHubRococoAssetOwner::get(); let ah_asset_owner_signer = ::RuntimeOrigin::signed(ah_asset_owner); @@ -1017,11 +1018,11 @@ fn reserve_transfer_assets_from_para_to_system_para() { // Query initial balances let sender_system_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location.clone(), &sender) + >::balance(system_asset_location_on_penpal.clone(), &sender) }); let sender_foreign_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location.clone(), &sender) + >::balance(asset_location_on_penpal.clone(), &sender) }); let receiver_balance_before = test.receiver.balance; let receiver_assets_before = AssetHubRococo::execute_with(|| { @@ -1038,11 +1039,11 @@ fn reserve_transfer_assets_from_para_to_system_para() { // Query final balances let sender_system_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location.clone(), &sender) + >::balance(system_asset_location_on_penpal, &sender) }); let sender_foreign_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location, &sender) + >::balance(asset_location_on_penpal, &sender) }); let receiver_balance_after = test.receiver.balance; let receiver_assets_after = AssetHubRococo::execute_with(|| { 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 index 364fbd0d439f..29eaa9694643 100644 --- 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 @@ -18,7 +18,7 @@ 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() { +fn send_transact_as_superuser_from_relay_to_asset_hub_works() { AssetHubRococo::force_create_asset_from_relay_as_root( ASSET_ID, ASSET_MIN_BALANCE, @@ -29,28 +29,25 @@ fn send_transact_as_superuser_from_relay_to_system_para_works() { } /// 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 +/// - Parachain should be able to send XCM paying its fee at Asset Hub using system asset +/// - Parachain should be able to create a new Foreign Asset at Asset Hub #[test] -fn send_xcm_from_para_to_system_para_paying_fee_with_system_assets_works() { +fn send_xcm_from_para_to_asset_hub_paying_fee_with_system_asset() { let para_sovereign_account = AssetHubRococo::sovereign_account_id_of( AssetHubRococo::sibling_location_of(PenpalA::para_id()), ); - let asset_location_on_penpal = v3::Location::new( + let asset_location_on_penpal = Location::new( 0, - [ - v3::Junction::PalletInstance(ASSETS_PALLET_ID), - v3::Junction::GeneralIndex(ASSET_ID.into()), - ], + [Junction::PalletInstance(ASSETS_PALLET_ID), Junction::GeneralIndex(ASSET_ID.into())], ); let foreign_asset_at_asset_hub = - v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + Location::new(1, [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, + foreign_asset_at_asset_hub.clone(), ASSET_MIN_BALANCE, para_sovereign_account.clone(), ); @@ -86,12 +83,7 @@ fn send_xcm_from_para_to_system_para_paying_fee_with_system_assets_works() { AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - - AssetHubRococo::assert_xcmp_queue_success(Some(Weight::from_parts( - 15_594_564_000, - 562_893, - ))); - + AssetHubRococo::assert_xcmp_queue_success(None); assert_expected_events!( AssetHubRococo, vec![ @@ -115,15 +107,15 @@ fn send_xcm_from_para_to_system_para_paying_fee_with_system_assets_works() { } /// 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 +/// - Parachain should be able to send XCM paying its fee at Asset Hub using sufficient asset +/// - Parachain should be able to create a new Asset at Asset Hub #[test] -fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { +fn send_xcm_from_para_to_asset_hub_paying_fee_with_sufficient_asset() { 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 + // Force create and mint sufficient assets for Parachain's sovereign account AssetHubRococo::force_create_and_mint_asset( ASSET_ID, ASSET_MIN_BALANCE, @@ -170,12 +162,7 @@ fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - - AssetHubRococo::assert_xcmp_queue_success(Some(Weight::from_parts( - 15_594_564_000, - 562_893, - ))); - + AssetHubRococo::assert_xcmp_queue_success(None); assert_expected_events!( AssetHubRococo, vec![ diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs index 16e0512da960..ac0c90ba198d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs @@ -17,13 +17,10 @@ use crate::imports::*; #[test] fn swap_locally_on_chain_using_local_assets() { - let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap()); - let asset_one = Box::new(v3::Location::new( + let asset_native = Box::new(Location::try_from(RelayLocation::get()).unwrap()); + let asset_one = Box::new(Location::new( 0, - [ - v3::Junction::PalletInstance(ASSETS_PALLET_ID), - v3::Junction::GeneralIndex(ASSET_ID.into()), - ], + [Junction::PalletInstance(ASSETS_PALLET_ID), Junction::GeneralIndex(ASSET_ID.into())], )); AssetHubRococo::execute_with(|| { @@ -112,11 +109,11 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap()); + let asset_native = Box::new(Location::try_from(RelayLocation::get()).unwrap()); let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap(); + Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap(); let foreign_asset_at_asset_hub_rococo = - v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + Location::new(1, [Junction::Parachain(PenpalA::para_id().into())]) .appended_with(asset_location_on_penpal) .unwrap(); @@ -141,7 +138,7 @@ fn swap_locally_on_chain_using_foreign_assets() { // 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_rococo, + foreign_asset_at_asset_hub_rococo.clone(), sov_penpal_on_ahr.clone().into(), ASSET_HUB_ROCOCO_ED * 3_000_000_000_000, )); @@ -157,7 +154,7 @@ fn swap_locally_on_chain_using_foreign_assets() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), asset_native.clone(), - Box::new(foreign_asset_at_asset_hub_rococo), + Box::new(foreign_asset_at_asset_hub_rococo.clone()), )); assert_expected_events!( @@ -171,7 +168,7 @@ fn swap_locally_on_chain_using_foreign_assets() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), asset_native.clone(), - Box::new(foreign_asset_at_asset_hub_rococo), + Box::new(foreign_asset_at_asset_hub_rococo.clone()), 1_000_000_000_000, 2_000_000_000_000, 0, @@ -189,7 +186,7 @@ fn swap_locally_on_chain_using_foreign_assets() { ); // 4. Swap! - let path = vec![asset_native.clone(), Box::new(foreign_asset_at_asset_hub_rococo)]; + let path = vec![asset_native.clone(), Box::new(foreign_asset_at_asset_hub_rococo.clone())]; assert_ok!( ::AssetConversion::swap_exact_tokens_for_tokens( @@ -216,7 +213,7 @@ fn swap_locally_on_chain_using_foreign_assets() { assert_ok!(::AssetConversion::remove_liquidity( ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), asset_native.clone(), - Box::new(foreign_asset_at_asset_hub_rococo), + Box::new(foreign_asset_at_asset_hub_rococo.clone()), 1414213562273 - ASSET_HUB_ROCOCO_ED * 2, // all but the 2 EDs can't be retrieved. 0, 0, @@ -252,8 +249,8 @@ fn cannot_create_pool_from_pool_assets() { assert_matches::assert_matches!( ::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - Box::new(v3::Location::try_from(asset_native).unwrap()), - Box::new(v3::Location::try_from(asset_one).unwrap()), + Box::new(Location::try_from(asset_native).unwrap()), + Box::new(Location::try_from(asset_one).unwrap()), ), Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown")) ); @@ -262,12 +259,12 @@ fn cannot_create_pool_from_pool_assets() { #[test] fn pay_xcm_fee_with_some_asset_swapped_for_native() { - let asset_native = v3::Location::try_from(RelayLocation::get()).unwrap(); - let asset_one = xcm::v3::Location { + let asset_native = Location::try_from(RelayLocation::get()).unwrap(); + let asset_one = Location { parents: 0, interior: [ - xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), - xcm::v3::Junction::GeneralIndex(ASSET_ID.into()), + Junction::PalletInstance(ASSETS_PALLET_ID), + Junction::GeneralIndex(ASSET_ID.into()), ] .into(), }; @@ -296,8 +293,8 @@ fn pay_xcm_fee_with_some_asset_swapped_for_native() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - Box::new(asset_native), - Box::new(asset_one), + Box::new(asset_native.clone()), + Box::new(asset_one.clone()), )); assert_expected_events!( diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs index f74378d7631a..c8da801a14bf 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs @@ -15,53 +15,6 @@ 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, @@ -92,22 +45,6 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { ); } -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 = RelayLocation::get(); @@ -141,7 +78,6 @@ fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { ); 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); @@ -159,7 +95,7 @@ fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { 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, + asset_id: *asset_id == expected_foreign_asset_id, owner: *owner == t.receiver.account_id, amount: *amount == expected_foreign_asset_amount, }, @@ -173,7 +109,6 @@ fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) { 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![ @@ -189,7 +124,7 @@ fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) { }, // 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, + asset_id: *asset_id == expected_foreign_asset_id, owner: *owner == t.sender.account_id, balance: *balance == expected_foreign_asset_amount, }, @@ -232,17 +167,6 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { ); } -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, @@ -276,90 +200,41 @@ fn system_para_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResul ) } -/// 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::teleport_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; +fn teleport_to_other_system_parachains_works() { + let amount = ASSET_HUB_ROCOCO_ED * 100; + let native_asset: Assets = (Parent, amount).into(); - // 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); + test_parachain_is_trusted_teleporter!( + AssetHubRococo, // Origin + AssetHubRococoXcmConfig, // XCM Configuration + vec![BridgeHubRococo], // Destinations + (native_asset, amount) + ); } -/// 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(); +fn teleport_from_and_to_relay() { + let amount = ROCOCO_ED * 100; + let native_asset: Assets = (Here, amount).into(); - let sender_balance_after = test.sender.balance; - let receiver_balance_after = test.receiver.balance; - - let delivery_fees = AssetHubRococo::execute_with(|| { - xcm_helpers::teleport_assets_delivery_fees::< - ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) - }); + test_relay_is_trusted_teleporter!( + Rococo, + RococoXcmConfig, + vec![AssetHubRococo], + (native_asset, amount) + ); - // 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); + test_parachain_is_trusted_teleporter_for_relay!( + AssetHubRococo, + AssetHubRococoXcmConfig, + Rococo, + amount + ); } /// 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` +/// shouldn'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 @@ -399,19 +274,6 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { 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 while paying /// fees using (reserve transferred) native asset. pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index 060c3fb39254..a887ee6a532a 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -26,16 +26,15 @@ mod imports { }; // Polkadot - pub use xcm::{ - prelude::{AccountId32 as AccountId32Junction, *}, - v3, - }; + pub use xcm::prelude::{AccountId32 as AccountId32Junction, *}; pub use xcm_executor::traits::TransferType; // Cumulus pub use asset_test_utils::xcm_helpers; pub use emulated_integration_tests_common::{ - test_parachain_is_trusted_teleporter, + accounts::DUMMY_EMPTY, + get_account_id_from_seed, test_parachain_is_trusted_teleporter, + test_parachain_is_trusted_teleporter_for_relay, test_relay_is_trusted_teleporter, xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test, TestArgs, TestContext, TestExt, @@ -90,7 +89,6 @@ mod imports { 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; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 82ef74fdab10..59f63d380590 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -493,9 +493,9 @@ fn para_to_para_through_relay_limited_reserve_transfer_assets( ) } -/// Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't work +/// Reserve Transfers of native asset from Relay Chain to the Asset Hub shouldn't work #[test] -fn reserve_transfer_native_asset_from_relay_to_system_para_fails() { +fn reserve_transfer_native_asset_from_relay_to_asset_hub_fails() { // Init values for Relay Chain let signed_origin = ::RuntimeOrigin::signed(WestendSender::get().into()); let destination = Westend::child_location_of(AssetHubWestend::para_id()); @@ -526,10 +526,10 @@ fn reserve_transfer_native_asset_from_relay_to_system_para_fails() { }); } -/// Reserve Transfers of native asset from System Parachain to Relay Chain shouldn't work +/// Reserve Transfers of native asset from Asset Hub to Relay Chain shouldn't work #[test] -fn reserve_transfer_native_asset_from_system_para_to_relay_fails() { - // Init values for System Parachain +fn reserve_transfer_native_asset_from_asset_hub_to_relay_fails() { + // Init values for Asset Hub let signed_origin = ::RuntimeOrigin::signed(AssetHubWestendSender::get().into()); let destination = AssetHubWestend::parent_location(); @@ -691,10 +691,10 @@ fn reserve_transfer_native_asset_from_para_to_relay() { // ========================================================================= // ======= Reserve Transfers - Native Asset - AssetHub<>Parachain ========== // ========================================================================= -/// Reserve Transfers of native asset from System Parachain to Parachain should work +/// Reserve Transfers of native asset from Asset Hub to Parachain should work #[test] -fn reserve_transfer_native_asset_from_system_para_to_para() { - // Init values for System Parachain +fn reserve_transfer_native_asset_from_asset_hub_to_para() { + // Init values for Asset Hub let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); let sender = AssetHubWestendSender::get(); let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 2000; @@ -749,9 +749,9 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { assert!(receiver_assets_after < receiver_assets_before + amount_to_send); } -/// Reserve Transfers of native asset from Parachain to System Parachain should work +/// Reserve Transfers of native asset from Parachain to Asset Hub should work #[test] -fn reserve_transfer_native_asset_from_para_to_system_para() { +fn reserve_transfer_native_asset_from_para_to_asset_hub() { // Init values for Parachain let destination = PenpalA::sibling_location_of(AssetHubWestend::para_id()); let sender = PenpalASender::get(); @@ -768,13 +768,13 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { amount_to_send * 2, ); - // Init values for System Parachain + // Init values for Asset Hub 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 + // fund Parachain's SA on Asset Hub with the native tokens held in reserve AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahr.into(), amount_to_send * 2)]); // Init Test @@ -825,11 +825,11 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { // ========================================================================= // ======= Reserve Transfers - Non-system Asset - AssetHub<>Parachain ====== // ========================================================================= -/// Reserve Transfers of a local asset and native asset from System Parachain to Parachain should +/// Reserve Transfers of a local asset and native asset from Asset Hub to Parachain should /// work #[test] -fn reserve_transfer_assets_from_system_para_to_para() { - // Init values for System Parachain +fn reserve_transfer_multiple_assets_from_asset_hub_to_para() { + // Init values for Asset Hub 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(); @@ -940,10 +940,12 @@ fn reserve_transfer_assets_from_system_para_to_para() { ); } -/// Reserve Transfers of a random asset and native asset from Parachain to System Para should -/// work +/// Reserve Transfers of a random asset and native asset from Parachain to Asset Hub should work +/// Receiver is empty account to show deposit works as long as transfer includes enough DOT for ED. +/// Once we have https://github.com/paritytech/polkadot-sdk/issues/5298, +/// we should do equivalent test with USDT instead of DOT. #[test] -fn reserve_transfer_assets_from_para_to_system_para() { +fn reserve_transfer_multiple_assets_from_para_to_asset_hub() { // Init values for Parachain let destination = PenpalA::sibling_location_of(AssetHubWestend::para_id()); let sender = PenpalASender::get(); @@ -966,25 +968,24 @@ fn reserve_transfer_assets_from_para_to_system_para() { // Fund Parachain's sender account with some foreign assets PenpalA::mint_foreign_asset( penpal_asset_owner_signer.clone(), - asset_location_on_penpal, + asset_location_on_penpal.clone(), 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, + system_asset_location_on_penpal.clone(), sender.clone(), fee_amount_to_send * 2, ); - // Init values for System Parachain - let receiver = AssetHubWestendReceiver::get(); + // Beneficiary is a new (empty) account + let receiver = get_account_id_from_seed::(DUMMY_EMPTY); + // Init values for Asset Hub 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 = RelayLocation::get(); - let system_para_foreign_asset_location = PenpalLocalReservableFromAssetHub::get(); let ah_asset_owner = AssetHubWestendAssetOwner::get(); let ah_asset_owner_signer = ::RuntimeOrigin::signed(ah_asset_owner); @@ -1019,11 +1020,11 @@ fn reserve_transfer_assets_from_para_to_system_para() { // Query initial balances let sender_system_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location.clone(), &sender) + >::balance(system_asset_location_on_penpal.clone(), &sender) }); let sender_foreign_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location.clone(), &sender) + >::balance(asset_location_on_penpal.clone(), &sender) }); let receiver_balance_before = test.receiver.balance; let receiver_assets_before = AssetHubWestend::execute_with(|| { @@ -1040,11 +1041,11 @@ fn reserve_transfer_assets_from_para_to_system_para() { // Query final balances let sender_system_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_native_asset_location, &sender) + >::balance(system_asset_location_on_penpal, &sender) }); let sender_foreign_assets_after = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; - >::balance(system_para_foreign_asset_location, &sender) + >::balance(asset_location_on_penpal, &sender) }); let receiver_balance_after = test.receiver.balance; let receiver_assets_after = AssetHubWestend::execute_with(|| { 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 index eb0e985cc0ce..761c7c12255c 100644 --- 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 @@ -18,7 +18,7 @@ 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() { +fn send_transact_as_superuser_from_relay_to_asset_hub_works() { AssetHubWestend::force_create_asset_from_relay_as_root( ASSET_ID, ASSET_MIN_BALANCE, @@ -29,28 +29,25 @@ fn send_transact_as_superuser_from_relay_to_system_para_works() { } /// 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 +/// - Parachain should be able to send XCM paying its fee at Asset Hub using system asset +/// - Parachain should be able to create a new Foreign Asset at Asset Hub #[test] -fn send_xcm_from_para_to_system_para_paying_fee_with_system_assets_works() { +fn send_xcm_from_para_to_asset_hub_paying_fee_with_system_asset() { let para_sovereign_account = AssetHubWestend::sovereign_account_id_of( AssetHubWestend::sibling_location_of(PenpalA::para_id()), ); - let asset_location_on_penpal = v3::Location::new( + let asset_location_on_penpal = Location::new( 0, - [ - v3::Junction::PalletInstance(ASSETS_PALLET_ID), - v3::Junction::GeneralIndex(ASSET_ID.into()), - ], + [Junction::PalletInstance(ASSETS_PALLET_ID), Junction::GeneralIndex(ASSET_ID.into())], ); let foreign_asset_at_asset_hub = - v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())]) + Location::new(1, [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, + foreign_asset_at_asset_hub.clone(), ASSET_MIN_BALANCE, para_sovereign_account.clone(), ); @@ -86,12 +83,7 @@ fn send_xcm_from_para_to_system_para_paying_fee_with_system_assets_works() { AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - - AssetHubWestend::assert_xcmp_queue_success(Some(Weight::from_parts( - 15_594_564_000, - 562_893, - ))); - + AssetHubWestend::assert_xcmp_queue_success(None); assert_expected_events!( AssetHubWestend, vec![ @@ -115,15 +107,15 @@ fn send_xcm_from_para_to_system_para_paying_fee_with_system_assets_works() { } /// 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 +/// - Parachain should be able to send XCM paying its fee at Asset Hub using sufficient asset +/// - Parachain should be able to create a new Asset at Asset Hub #[test] -fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { +fn send_xcm_from_para_to_asset_hub_paying_fee_with_sufficient_asset() { 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 + // Force create and mint sufficient assets for Parachain's sovereign account AssetHubWestend::force_create_and_mint_asset( ASSET_ID, ASSET_MIN_BALANCE, @@ -170,12 +162,7 @@ fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - - AssetHubWestend::assert_xcmp_queue_success(Some(Weight::from_parts( - 15_594_564_000, - 562_893, - ))); - + AssetHubWestend::assert_xcmp_queue_success(None); assert_expected_events!( AssetHubWestend, vec![ diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs index cf429378cf6d..1a2821452155 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs @@ -18,12 +18,12 @@ use crate::imports::*; #[test] fn swap_locally_on_chain_using_local_assets() { let asset_native = - Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works")); - let asset_one = Box::new(v3::Location { + Box::new(Location::try_from(RelayLocation::get()).expect("conversion works")); + let asset_one = Box::new(Location { parents: 0, interior: [ - v3::Junction::PalletInstance(ASSETS_PALLET_ID), - v3::Junction::GeneralIndex(ASSET_ID.into()), + Junction::PalletInstance(ASSETS_PALLET_ID), + Junction::GeneralIndex(ASSET_ID.into()), ] .into(), }); @@ -112,11 +112,11 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap()); + let asset_native = Box::new(Location::try_from(RelayLocation::get()).unwrap()); let asset_location_on_penpal = - v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion_works"); + 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())]) + Location::new(1, [Junction::Parachain(PenpalA::para_id().into())]) .appended_with(asset_location_on_penpal) .unwrap(); @@ -141,7 +141,7 @@ fn swap_locally_on_chain_using_foreign_assets() { // 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, + foreign_asset_at_asset_hub_westend.clone(), sov_penpal_on_ahr.clone().into(), ASSET_HUB_WESTEND_ED * 3_000_000_000_000, )); @@ -157,7 +157,7 @@ fn swap_locally_on_chain_using_foreign_assets() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), asset_native.clone(), - Box::new(foreign_asset_at_asset_hub_westend), + Box::new(foreign_asset_at_asset_hub_westend.clone()), )); assert_expected_events!( @@ -171,7 +171,7 @@ fn swap_locally_on_chain_using_foreign_assets() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), asset_native.clone(), - Box::new(foreign_asset_at_asset_hub_westend), + Box::new(foreign_asset_at_asset_hub_westend.clone()), 1_000_000_000_000_000, 2_000_000_000_000_000, 0, @@ -189,7 +189,7 @@ fn swap_locally_on_chain_using_foreign_assets() { ); // 4. Swap! - let path = vec![asset_native.clone(), Box::new(foreign_asset_at_asset_hub_westend)]; + let path = vec![asset_native.clone(), Box::new(foreign_asset_at_asset_hub_westend.clone())]; assert_ok!( ::AssetConversion::swap_exact_tokens_for_tokens( @@ -252,8 +252,8 @@ fn cannot_create_pool_from_pool_assets() { assert_matches::assert_matches!( ::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - Box::new(v3::Location::try_from(asset_native).expect("conversion works")), - Box::new(v3::Location::try_from(asset_one).expect("conversion works")), + Box::new(Location::try_from(asset_native).expect("conversion works")), + Box::new(Location::try_from(asset_one).expect("conversion works")), ), Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown")) ); @@ -262,12 +262,12 @@ fn cannot_create_pool_from_pool_assets() { #[test] fn pay_xcm_fee_with_some_asset_swapped_for_native() { - let asset_native = v3::Location::try_from(RelayLocation::get()).expect("conversion works"); - let asset_one = xcm::v3::Location { + let asset_native = Location::try_from(RelayLocation::get()).expect("conversion works"); + let asset_one = Location { parents: 0, interior: [ - xcm::v3::Junction::PalletInstance(ASSETS_PALLET_ID), - xcm::v3::Junction::GeneralIndex(ASSET_ID.into()), + Junction::PalletInstance(ASSETS_PALLET_ID), + Junction::GeneralIndex(ASSET_ID.into()), ] .into(), }; @@ -296,8 +296,8 @@ fn pay_xcm_fee_with_some_asset_swapped_for_native() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - Box::new(asset_native), - Box::new(asset_one), + Box::new(asset_native.clone()), + Box::new(asset_one.clone()), )); assert_expected_events!( diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs index a524b87b2daf..15d39858acca 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs @@ -15,53 +15,6 @@ 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, @@ -92,22 +45,6 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { ); } -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 = RelayLocation::get(); @@ -141,7 +78,6 @@ fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { ); 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); @@ -159,7 +95,7 @@ fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { 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, + asset_id: *asset_id == expected_foreign_asset_id, owner: *owner == t.receiver.account_id, amount: *amount == expected_foreign_asset_amount, }, @@ -173,7 +109,6 @@ fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) { 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![ @@ -189,7 +124,7 @@ fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) { }, // 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, + asset_id: *asset_id == expected_foreign_asset_id, owner: *owner == t.sender.account_id, balance: *balance == expected_foreign_asset_amount, }, @@ -232,17 +167,6 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { ); } -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, @@ -276,90 +200,41 @@ fn system_para_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResul ) } -/// 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::teleport_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; +fn teleport_to_other_system_parachains_works() { + let amount = ASSET_HUB_WESTEND_ED * 100; + let native_asset: Assets = (Parent, amount).into(); - // 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); + test_parachain_is_trusted_teleporter!( + AssetHubWestend, // Origin + AssetHubWestendXcmConfig, // XCM Configuration + vec![BridgeHubWestend], // Destinations + (native_asset, amount) + ); } -/// 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(); +fn teleport_from_and_to_relay() { + let amount = WESTEND_ED * 100; + let native_asset: Assets = (Here, amount).into(); - let sender_balance_after = test.sender.balance; - let receiver_balance_after = test.receiver.balance; - - let delivery_fees = AssetHubWestend::execute_with(|| { - xcm_helpers::teleport_assets_delivery_fees::< - ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) - }); + test_relay_is_trusted_teleporter!( + Westend, + WestendXcmConfig, + vec![AssetHubWestend], + (native_asset, amount) + ); - // 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); + test_parachain_is_trusted_teleporter_for_relay!( + AssetHubWestend, + AssetHubWestendXcmConfig, + Westend, + amount + ); } /// 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` +/// shouldn'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 @@ -399,19 +274,6 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { 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 while paying /// fees using (reserve transferred) native asset. pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt( diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index 3ee509389c67..ac08e48ded68 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -23,7 +23,8 @@ mod imports { pub use xcm::{ latest::ParentThen, prelude::{AccountId32 as AccountId32Junction, *}, - v3::{self, NetworkId::Westend as WestendId}, + v4, + v4::NetworkId::Westend as WestendId, }; pub use xcm_executor::traits::TransferType; @@ -31,7 +32,8 @@ mod imports { pub use emulated_integration_tests_common::{ accounts::ALICE, impls::Inspect, - test_parachain_is_trusted_teleporter, + test_parachain_is_trusted_teleporter, test_parachain_is_trusted_teleporter_for_relay, + test_relay_is_trusted_teleporter, xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt, }, @@ -59,15 +61,20 @@ mod imports { }, PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner, }, - rococo_emulated_chain::{genesis::ED as ROCOCO_ED, RococoRelayPallet as RococoPallet}, + rococo_emulated_chain::{ + genesis::ED as ROCOCO_ED, rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig, + RococoRelayPallet as RococoPallet, + }, AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver, AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend, AssetHubWestendParaReceiver as AssetHubWestendReceiver, AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo, + BridgeHubRococoParaReceiver as BridgeHubRococoReceiver, BridgeHubRococoParaSender as BridgeHubRococoSender, BridgeHubWestendPara as BridgeHubWestend, PenpalAPara as PenpalA, PenpalAParaReceiver as PenpalAReceiver, PenpalAParaSender as PenpalASender, - RococoRelay as Rococo, + RococoRelay as Rococo, RococoRelayReceiver as RococoReceiver, + RococoRelaySender as RococoSender, }; pub const ASSET_MIN_BALANCE: u128 = 1000; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index 6053936487b2..8a674f89c9ef 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -36,10 +36,10 @@ fn send_assets_over_bridge(send_fn: F) { fn set_up_rocs_for_penpal_rococo_through_ahr_to_ahw( sender: &AccountId, amount: u128, -) -> (Location, v3::Location) { +) -> (Location, v4::Location) { let roc_at_rococo_parachains = roc_at_ah_rococo(); - let roc_at_asset_hub_westend = bridged_roc_at_ah_westend().try_into().unwrap(); - create_foreign_on_ah_westend(roc_at_asset_hub_westend, true); + let roc_at_asset_hub_westend = bridged_roc_at_ah_westend(); + create_foreign_on_ah_westend(roc_at_asset_hub_westend.clone(), true); let penpal_location = AssetHubRococo::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location); @@ -121,11 +121,11 @@ fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() { let amount = ASSET_HUB_ROCOCO_ED * 1_000_000; let sender = AssetHubRococoSender::get(); let receiver = AssetHubWestendReceiver::get(); - let roc_at_asset_hub_rococo: v3::Location = roc_at_ah_rococo().try_into().unwrap(); - let bridged_roc_at_asset_hub_westend = bridged_roc_at_ah_westend().try_into().unwrap(); + let roc_at_asset_hub_rococo = roc_at_ah_rococo(); + let bridged_roc_at_asset_hub_westend = bridged_roc_at_ah_westend(); - create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend, true); - set_up_pool_with_wnd_on_ah_westend(bridged_roc_at_asset_hub_westend); + create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), true); + set_up_pool_with_wnd_on_ah_westend(bridged_roc_at_asset_hub_westend.clone()); //////////////////////////////////////////////////////////// // Let's first send over just some ROCs as a simple example @@ -138,12 +138,13 @@ fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() { ::account_data_of(sov_ahw_on_ahr.clone()).free; let sender_rocs_before = ::account_data_of(sender.clone()).free; let receiver_rocs_before = - foreign_balance_on_ah_westend(bridged_roc_at_asset_hub_westend, &receiver); + foreign_balance_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), &receiver); // send ROCs, use them for fees send_assets_over_bridge(|| { let destination = asset_hub_westend_location(); - let assets: Assets = (Location::try_from(roc_at_asset_hub_rococo).unwrap(), amount).into(); + let assets: Assets = + (Location::try_from(roc_at_asset_hub_rococo.clone()).unwrap(), amount).into(); let fee_idx = 0; assert_ok!(send_assets_from_asset_hub_rococo(destination, assets, fee_idx)); }); @@ -185,9 +186,9 @@ fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() { ///////////////////////////////////////////////////////////// let usdt_at_asset_hub_rococo = usdt_at_ah_rococo(); - let bridged_usdt_at_asset_hub_westend = bridged_usdt_at_ah_westend().try_into().unwrap(); + let bridged_usdt_at_asset_hub_westend = bridged_usdt_at_ah_westend(); // wETH has same relative location on both Rococo and Westend AssetHubs - let bridged_weth_at_ah = weth_at_asset_hubs().try_into().unwrap(); + let bridged_weth_at_ah = weth_at_asset_hubs(); // mint USDT in sender's account (USDT already created in genesis) AssetHubRococo::mint_asset( @@ -197,19 +198,23 @@ fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() { amount * 2, ); // create wETH at src and dest and prefund sender's account - create_foreign_on_ah_rococo(bridged_weth_at_ah, true, vec![(sender.clone(), amount * 2)]); - create_foreign_on_ah_westend(bridged_weth_at_ah, true); - create_foreign_on_ah_westend(bridged_usdt_at_asset_hub_westend, true); - set_up_pool_with_wnd_on_ah_westend(bridged_usdt_at_asset_hub_westend); + create_foreign_on_ah_rococo( + bridged_weth_at_ah.clone(), + true, + vec![(sender.clone(), amount * 2)], + ); + create_foreign_on_ah_westend(bridged_weth_at_ah.clone(), true); + create_foreign_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone(), true); + set_up_pool_with_wnd_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone()); let receiver_usdts_before = - foreign_balance_on_ah_westend(bridged_usdt_at_asset_hub_westend, &receiver); - let receiver_weth_before = foreign_balance_on_ah_westend(bridged_weth_at_ah, &receiver); + foreign_balance_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone(), &receiver); + let receiver_weth_before = foreign_balance_on_ah_westend(bridged_weth_at_ah.clone(), &receiver); // send USDTs and wETHs let assets: Assets = vec![ (usdt_at_asset_hub_rococo.clone(), amount).into(), - (Location::try_from(bridged_weth_at_ah).unwrap(), amount).into(), + (Location::try_from(bridged_weth_at_ah.clone()).unwrap(), amount).into(), ] .into(); // use USDT for fees @@ -258,9 +263,8 @@ fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() { let sender = AssetHubRococoSender::get(); let receiver = AssetHubWestendReceiver::get(); let wnd_at_asset_hub_rococo = bridged_wnd_at_ah_rococo(); - let wnd_at_asset_hub_rococo_v3 = wnd_at_asset_hub_rococo.clone().try_into().unwrap(); let prefund_accounts = vec![(sender.clone(), prefund_amount)]; - create_foreign_on_ah_rococo(wnd_at_asset_hub_rococo_v3, true, prefund_accounts); + create_foreign_on_ah_rococo(wnd_at_asset_hub_rococo.clone(), true, prefund_accounts); // 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( @@ -273,14 +277,14 @@ fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() { ::account_data_of(sov_ahr_on_ahw.clone()).free; assert_eq!(wnds_in_reserve_on_ahw_before, prefund_amount); - let sender_wnds_before = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo_v3, &sender); + let sender_wnds_before = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo.clone(), &sender); assert_eq!(sender_wnds_before, prefund_amount); let receiver_wnds_before = ::account_data_of(receiver.clone()).free; // send back WNDs, use them for fees send_assets_over_bridge(|| { let destination = asset_hub_westend_location(); - let assets: Assets = (wnd_at_asset_hub_rococo, amount_to_send).into(); + let assets: Assets = (wnd_at_asset_hub_rococo.clone(), amount_to_send).into(); let fee_idx = 0; assert_ok!(send_assets_from_asset_hub_rococo(destination, assets, fee_idx)); }); @@ -309,7 +313,7 @@ fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() { ); }); - let sender_wnds_after = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo_v3, &sender); + let sender_wnds_after = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo, &sender); let receiver_wnds_after = ::account_data_of(receiver).free; let wnds_in_reserve_on_ahw_after = ::account_data_of(sov_ahr_on_ahw).free; @@ -341,7 +345,8 @@ fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() type ForeignAssets = ::ForeignAssets; >::balance(roc_at_rococo_parachains.clone(), &sender) }); - let receiver_rocs_before = foreign_balance_on_ah_westend(roc_at_asset_hub_westend, &receiver); + let receiver_rocs_before = + foreign_balance_on_ah_westend(roc_at_asset_hub_westend.clone(), &receiver); // Send ROCs over bridge { @@ -372,7 +377,7 @@ fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() vec![ // issue ROCs on AHW RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == roc_at_rococo_parachains.clone().try_into().unwrap(), + asset_id: *asset_id == roc_at_rococo_parachains.clone(), owner: owner == &receiver, }, // message processed successfully @@ -403,7 +408,6 @@ fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() #[test] fn send_back_wnds_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() { let wnd_at_rococo_parachains = bridged_wnd_at_ah_rococo(); - let wnd_at_rococo_parachains_v3 = wnd_at_rococo_parachains.clone().try_into().unwrap(); let amount = ASSET_HUB_ROCOCO_ED * 10_000_000; let sender = PenpalASender::get(); let receiver = AssetHubWestendReceiver::get(); @@ -416,7 +420,7 @@ fn send_back_wnds_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_weste let penpal_location = AssetHubRococo::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location); let prefund_accounts = vec![(sov_penpal_on_ahr, amount * 2)]; - create_foreign_on_ah_rococo(wnd_at_rococo_parachains_v3, true, prefund_accounts); + create_foreign_on_ah_rococo(wnd_at_rococo_parachains.clone(), true, prefund_accounts); let asset_owner: AccountId = AssetHubRococo::account_id_of(ALICE); PenpalA::force_create_foreign_asset( wnd_at_rococo_parachains.clone(), diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs index ceccf98a0240..6ce8ecef0df3 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs @@ -69,7 +69,7 @@ pub(crate) fn weth_at_asset_hubs() -> Location { } pub(crate) fn create_foreign_on_ah_rococo( - id: v3::Location, + id: v4::Location, sufficient: bool, prefund_accounts: Vec<(AccountId, u128)>, ) { @@ -78,18 +78,18 @@ pub(crate) fn create_foreign_on_ah_rococo( AssetHubRococo::force_create_foreign_asset(id, owner, sufficient, min, prefund_accounts); } -pub(crate) fn create_foreign_on_ah_westend(id: v3::Location, sufficient: bool) { +pub(crate) fn create_foreign_on_ah_westend(id: v4::Location, sufficient: bool) { let owner = AssetHubWestend::account_id_of(ALICE); AssetHubWestend::force_create_foreign_asset(id, owner, sufficient, ASSET_MIN_BALANCE, vec![]); } -pub(crate) fn foreign_balance_on_ah_rococo(id: v3::Location, who: &AccountId) -> u128 { +pub(crate) fn foreign_balance_on_ah_rococo(id: v4::Location, who: &AccountId) -> u128 { AssetHubRococo::execute_with(|| { type Assets = ::ForeignAssets; >::balance(id, who) }) } -pub(crate) fn foreign_balance_on_ah_westend(id: v3::Location, who: &AccountId) -> u128 { +pub(crate) fn foreign_balance_on_ah_westend(id: v4::Location, who: &AccountId) -> u128 { AssetHubWestend::execute_with(|| { type Assets = ::ForeignAssets; >::balance(id, who) @@ -97,8 +97,8 @@ pub(crate) fn foreign_balance_on_ah_westend(id: v3::Location, who: &AccountId) - } // set up pool -pub(crate) fn set_up_pool_with_wnd_on_ah_westend(foreign_asset: v3::Location) { - let wnd: v3::Location = v3::Parent.into(); +pub(crate) fn set_up_pool_with_wnd_on_ah_westend(foreign_asset: v4::Location) { + let wnd: v4::Location = v4::Parent.into(); AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let owner = AssetHubWestendSender::get(); @@ -106,14 +106,14 @@ pub(crate) fn set_up_pool_with_wnd_on_ah_westend(foreign_asset: v3::Location) { assert_ok!(::ForeignAssets::mint( signed_owner.clone(), - foreign_asset.into(), + foreign_asset.clone().into(), owner.clone().into(), 3_000_000_000_000, )); assert_ok!(::AssetConversion::create_pool( signed_owner.clone(), - Box::new(wnd), - Box::new(foreign_asset), + Box::new(wnd.clone()), + Box::new(foreign_asset.clone()), )); assert_expected_events!( AssetHubWestend, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs index 652447fa5601..3f2038b4bdd1 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs @@ -29,7 +29,7 @@ fn send_xcm_from_rococo_relay_to_westend_asset_hub_should_fail_on_not_applicable let xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit, check_origin }, ExportMessage { - network: WestendId.into(), + network: WestendId, destination: [Parachain(AssetHubWestend::para_id().into())].into(), xcm: remote_xcm, }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index 40a1968ec557..84328fb7c6d2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -560,7 +560,6 @@ fn send_token_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u12 2, [EthereumNetwork::get().into(), AccountKey20 { network: None, key: WETH }], ); - // (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }) // Fund asset hub sovereign on bridge hub let asset_hub_sovereign = BridgeHubRococo::sovereign_account_id_of(Location::new( 1, @@ -669,8 +668,8 @@ fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficie #[test] fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_sufficient_fee_but_do_not_satisfy_ed( ) { - // On AH the xcm fee is 33_873_024 and the ED is 3_300_000 - send_token_from_ethereum_to_asset_hub_with_fee([1; 32], 36_000_000); + // On AH the xcm fee is 26_789_690 and the ED is 3_300_000 + send_token_from_ethereum_to_asset_hub_with_fee([1; 32], 30_000_000); AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/teleport.rs index 1fb03748d926..8cdd9613dc52 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/teleport.rs @@ -27,3 +27,23 @@ fn teleport_to_other_system_parachains_works() { (native_asset, amount) ); } + +#[test] +fn teleport_from_and_to_relay() { + let amount = ROCOCO_ED * 100; + let native_asset: Assets = (Here, amount).into(); + + test_relay_is_trusted_teleporter!( + Rococo, + RococoXcmConfig, + vec![BridgeHubRococo], + (native_asset, amount) + ); + + test_parachain_is_trusted_teleporter_for_relay!( + BridgeHubRococo, + BridgeHubRococoXcmConfig, + Rococo, + amount + ); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 782b83bac475..5e0462d14882 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -23,8 +23,7 @@ mod imports { pub use xcm::{ latest::ParentThen, prelude::{AccountId32 as AccountId32Junction, *}, - v3, - v4::NetworkId::Rococo as RococoId, + v4::{self, NetworkId::Rococo as RococoId}, }; pub use xcm_executor::traits::TransferType; @@ -32,7 +31,8 @@ mod imports { pub use emulated_integration_tests_common::{ accounts::ALICE, impls::Inspect, - test_parachain_is_trusted_teleporter, + test_parachain_is_trusted_teleporter, test_parachain_is_trusted_teleporter_for_relay, + test_relay_is_trusted_teleporter, xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt, }, @@ -55,14 +55,19 @@ mod imports { penpal_runtime::xcm_config::UniversalLocation as PenpalUniversalLocation, PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet, }, - westend_emulated_chain::WestendRelayPallet as WestendPallet, + westend_emulated_chain::{ + genesis::ED as WESTEND_ED, westend_runtime::xcm_config::XcmConfig as WestendXcmConfig, + 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, + BridgeHubWestendParaReceiver as BridgeHubWestendReceiver, BridgeHubWestendParaSender as BridgeHubWestendSender, PenpalBPara as PenpalB, PenpalBParaSender as PenpalBSender, WestendRelay as Westend, + WestendRelayReceiver as WestendReceiver, WestendRelaySender as WestendSender, }; pub const ASSET_MIN_BALANCE: u128 = 1000; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index 0c0b04cd45a9..fc8b772a9c7e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -35,10 +35,10 @@ fn send_assets_over_bridge(send_fn: F) { fn set_up_wnds_for_penpal_westend_through_ahw_to_ahr( sender: &AccountId, amount: u128, -) -> (Location, v3::Location) { +) -> (Location, v4::Location) { let wnd_at_westend_parachains = wnd_at_ah_westend(); - let wnd_at_asset_hub_rococo = bridged_wnd_at_ah_rococo().try_into().unwrap(); - create_foreign_on_ah_rococo(wnd_at_asset_hub_rococo, true); + let wnd_at_asset_hub_rococo = bridged_wnd_at_ah_rococo(); + create_foreign_on_ah_rococo(wnd_at_asset_hub_rococo.clone(), true); let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id()); let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_location); @@ -116,10 +116,10 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { let sender = AssetHubWestendSender::get(); let receiver = AssetHubRococoReceiver::get(); let wnd_at_asset_hub_westend = wnd_at_ah_westend(); - let bridged_wnd_at_asset_hub_rococo = bridged_wnd_at_ah_rococo().try_into().unwrap(); - create_foreign_on_ah_rococo(bridged_wnd_at_asset_hub_rococo, true); + let bridged_wnd_at_asset_hub_rococo = bridged_wnd_at_ah_rococo(); + create_foreign_on_ah_rococo(bridged_wnd_at_asset_hub_rococo.clone(), true); - set_up_pool_with_roc_on_ah_rococo(bridged_wnd_at_asset_hub_rococo, true); + set_up_pool_with_roc_on_ah_rococo(bridged_wnd_at_asset_hub_rococo.clone(), true); let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( Rococo, @@ -129,7 +129,7 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { ::account_data_of(sov_ahr_on_ahw.clone()).free; let sender_wnds_before = ::account_data_of(sender.clone()).free; let receiver_wnds_before = - foreign_balance_on_ah_rococo(bridged_wnd_at_asset_hub_rococo, &receiver); + foreign_balance_on_ah_rococo(bridged_wnd_at_asset_hub_rococo.clone(), &receiver); // send WNDs, use them for fees send_assets_over_bridge(|| { @@ -187,10 +187,8 @@ fn send_back_rocs_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { let sender = AssetHubWestendSender::get(); let receiver = AssetHubRococoReceiver::get(); let bridged_roc_at_asset_hub_westend = bridged_roc_at_ah_westend(); - let bridged_roc_at_asset_hub_westend_v3 = - bridged_roc_at_asset_hub_westend.clone().try_into().unwrap(); let prefund_accounts = vec![(sender.clone(), prefund_amount)]; - create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend_v3, true, prefund_accounts); + create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), true, prefund_accounts); //////////////////////////////////////////////////////////// // Let's first send back just some ROCs as a simple example @@ -208,14 +206,14 @@ fn send_back_rocs_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { assert_eq!(rocs_in_reserve_on_ahr_before, prefund_amount); let sender_rocs_before = - foreign_balance_on_ah_westend(bridged_roc_at_asset_hub_westend_v3, &sender); + foreign_balance_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), &sender); assert_eq!(sender_rocs_before, prefund_amount); let receiver_rocs_before = ::account_data_of(receiver.clone()).free; // send back ROCs, use them for fees send_assets_over_bridge(|| { let destination = asset_hub_rococo_location(); - let assets: Assets = (bridged_roc_at_asset_hub_westend, amount_to_send).into(); + let assets: Assets = (bridged_roc_at_asset_hub_westend.clone(), amount_to_send).into(); let fee_idx = 0; assert_ok!(send_assets_from_asset_hub_westend(destination, assets, fee_idx)); }); @@ -245,7 +243,7 @@ fn send_back_rocs_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { }); let sender_rocs_after = - foreign_balance_on_ah_westend(bridged_roc_at_asset_hub_westend_v3, &sender); + foreign_balance_on_ah_westend(bridged_roc_at_asset_hub_westend, &sender); let receiver_rocs_after = ::account_data_of(receiver.clone()).free; let rocs_in_reserve_on_ahr_after = ::account_data_of(sov_ahw_on_ahr.clone()).free; @@ -262,14 +260,14 @@ fn send_back_rocs_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { ////////////////////////////////////////////////////////////////// // wETH has same relative location on both Rococo and Westend AssetHubs - let bridged_weth_at_ah = weth_at_asset_hubs().try_into().unwrap(); - let bridged_usdt_at_asset_hub_westend = bridged_usdt_at_ah_westend().try_into().unwrap(); + let bridged_weth_at_ah = weth_at_asset_hubs(); + let bridged_usdt_at_asset_hub_westend = bridged_usdt_at_ah_westend(); // set up destination chain AH Rococo: // create a ROC/USDT pool to be able to pay fees with USDT (USDT created in genesis) - set_up_pool_with_roc_on_ah_rococo(usdt_at_ah_rococo().try_into().unwrap(), false); + set_up_pool_with_roc_on_ah_rococo(usdt_at_ah_rococo(), false); // create wETH on Rococo (IRL it's already created by Snowbridge) - create_foreign_on_ah_rococo(bridged_weth_at_ah, true); + create_foreign_on_ah_rococo(bridged_weth_at_ah.clone(), true); // prefund AHW's sovereign account on AHR to be able to withdraw USDT and wETH from reserves let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( Westend, @@ -283,7 +281,7 @@ fn send_back_rocs_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { ); AssetHubRococo::mint_foreign_asset( ::RuntimeOrigin::signed(AssetHubRococo::account_id_of(ALICE)), - bridged_weth_at_ah, + bridged_weth_at_ah.clone(), sov_ahw_on_ahr, amount_to_send * 2, ); @@ -291,21 +289,21 @@ fn send_back_rocs_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { // set up source chain AH Westend: // create wETH and USDT foreign assets on Westend and prefund sender's account let prefund_accounts = vec![(sender.clone(), amount_to_send * 2)]; - create_foreign_on_ah_westend(bridged_weth_at_ah, true, prefund_accounts.clone()); - create_foreign_on_ah_westend(bridged_usdt_at_asset_hub_westend, true, prefund_accounts); + create_foreign_on_ah_westend(bridged_weth_at_ah.clone(), true, prefund_accounts.clone()); + create_foreign_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone(), true, prefund_accounts); // check balances before let receiver_usdts_before = AssetHubRococo::execute_with(|| { type Assets = ::Assets; >::balance(USDT_ID, &receiver) }); - let receiver_weth_before = foreign_balance_on_ah_rococo(bridged_weth_at_ah, &receiver); + let receiver_weth_before = foreign_balance_on_ah_rococo(bridged_weth_at_ah.clone(), &receiver); let usdt_id: AssetId = Location::try_from(bridged_usdt_at_asset_hub_westend).unwrap().into(); // send USDTs and wETHs let assets: Assets = vec![ (usdt_id.clone(), amount_to_send).into(), - (Location::try_from(bridged_weth_at_ah).unwrap(), amount_to_send).into(), + (Location::try_from(bridged_weth_at_ah.clone()).unwrap(), amount_to_send).into(), ] .into(); // use USDT for fees @@ -367,7 +365,8 @@ fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() type ForeignAssets = ::ForeignAssets; >::balance(wnd_at_westend_parachains.clone(), &sender) }); - let receiver_wnds_before = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo, &receiver); + let receiver_wnds_before = + foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo.clone(), &receiver); // Send WNDs over bridge { @@ -398,7 +397,7 @@ fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() vec![ // issue WNDs on AHR RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { - asset_id: *asset_id == wnd_at_westend_parachains.clone().try_into().unwrap(), + asset_id: *asset_id == wnd_at_westend_parachains.clone(), owner: owner == &receiver, }, // message processed successfully @@ -429,7 +428,6 @@ fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() #[test] fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() { let roc_at_westend_parachains = bridged_roc_at_ah_westend(); - let roc_at_westend_parachains_v3 = roc_at_westend_parachains.clone().try_into().unwrap(); let amount = ASSET_HUB_WESTEND_ED * 10_000_000; let sender = PenpalBSender::get(); let receiver = AssetHubRococoReceiver::get(); @@ -442,7 +440,7 @@ fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_roc let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id()); let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(penpal_location); let prefund_accounts = vec![(sov_penpal_on_ahr, amount * 2)]; - create_foreign_on_ah_westend(roc_at_westend_parachains_v3, true, prefund_accounts); + create_foreign_on_ah_westend(roc_at_westend_parachains.clone(), true, prefund_accounts); let asset_owner: AccountId = AssetHubWestend::account_id_of(ALICE); PenpalB::force_create_foreign_asset( roc_at_westend_parachains.clone(), @@ -454,7 +452,7 @@ fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_roc // 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, + Westend, AssetHubWestend::para_id(), ); AssetHubRococo::fund_accounts(vec![(sov_ahw_on_ahr.clone(), amount * 2)]); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 87ae9aedd6f1..bf894a3baf58 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -69,13 +69,13 @@ pub(crate) fn weth_at_asset_hubs() -> Location { ) } -pub(crate) fn create_foreign_on_ah_rococo(id: v3::Location, sufficient: bool) { +pub(crate) fn create_foreign_on_ah_rococo(id: v4::Location, sufficient: bool) { let owner = AssetHubRococo::account_id_of(ALICE); AssetHubRococo::force_create_foreign_asset(id, owner, sufficient, ASSET_MIN_BALANCE, vec![]); } pub(crate) fn create_foreign_on_ah_westend( - id: v3::Location, + id: v4::Location, sufficient: bool, prefund_accounts: Vec<(AccountId, u128)>, ) { @@ -84,13 +84,13 @@ pub(crate) fn create_foreign_on_ah_westend( AssetHubWestend::force_create_foreign_asset(id, owner, sufficient, min, prefund_accounts); } -pub(crate) fn foreign_balance_on_ah_rococo(id: v3::Location, who: &AccountId) -> u128 { +pub(crate) fn foreign_balance_on_ah_rococo(id: v4::Location, who: &AccountId) -> u128 { AssetHubRococo::execute_with(|| { type Assets = ::ForeignAssets; >::balance(id, who) }) } -pub(crate) fn foreign_balance_on_ah_westend(id: v3::Location, who: &AccountId) -> u128 { +pub(crate) fn foreign_balance_on_ah_westend(id: v4::Location, who: &AccountId) -> u128 { AssetHubWestend::execute_with(|| { type Assets = ::ForeignAssets; >::balance(id, who) @@ -98,8 +98,8 @@ pub(crate) fn foreign_balance_on_ah_westend(id: v3::Location, who: &AccountId) - } // set up pool -pub(crate) fn set_up_pool_with_roc_on_ah_rococo(asset: v3::Location, is_foreign: bool) { - let roc: v3::Location = v3::Parent.into(); +pub(crate) fn set_up_pool_with_roc_on_ah_rococo(asset: v4::Location, is_foreign: bool) { + let roc: v4::Location = v4::Parent.into(); AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let owner = AssetHubRococoSender::get(); @@ -108,13 +108,13 @@ pub(crate) fn set_up_pool_with_roc_on_ah_rococo(asset: v3::Location, is_foreign: if is_foreign { assert_ok!(::ForeignAssets::mint( signed_owner.clone(), - asset.into(), + asset.clone().into(), owner.clone().into(), 3_000_000_000_000, )); } else { - let asset_id = match asset.interior.split_last() { - (_, Some(v3::Junction::GeneralIndex(id))) => id as u32, + let asset_id = match asset.interior.last() { + Some(v4::Junction::GeneralIndex(id)) => *id as u32, _ => unreachable!(), }; assert_ok!(::Assets::mint( @@ -126,8 +126,8 @@ pub(crate) fn set_up_pool_with_roc_on_ah_rococo(asset: v3::Location, is_foreign: } assert_ok!(::AssetConversion::create_pool( signed_owner.clone(), - Box::new(roc), - Box::new(asset), + Box::new(roc.clone()), + Box::new(asset.clone()), )); assert_expected_events!( AssetHubRococo, 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 index 64378a844f52..a5add3b82957 100644 --- 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 @@ -27,3 +27,23 @@ fn teleport_to_other_system_parachains_works() { (native_asset, amount) ); } + +#[test] +fn teleport_from_and_to_relay() { + let amount = WESTEND_ED * 100; + let native_asset: Assets = (Here, amount).into(); + + test_relay_is_trusted_teleporter!( + Westend, + WestendXcmConfig, + vec![BridgeHubWestend], + (native_asset, amount) + ); + + test_parachain_is_trusted_teleporter_for_relay!( + BridgeHubWestend, + BridgeHubWestendXcmConfig, + Westend, + amount + ); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml index 3012e2b19f53..c4d281b75a77 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml @@ -23,6 +23,7 @@ pallet-assets = { workspace = true } pallet-treasury = { workspace = true } pallet-message-queue = { workspace = true } pallet-utility = { workspace = true } +pallet-whitelist = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship.rs b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship.rs new file mode 100644 index 000000000000..f97599bda7f0 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship.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. + +use crate::*; +use codec::Encode; +use collectives_fellowship::pallet_fellowship_origins::Origin::Fellows as FellowsOrigin; +use frame_support::{assert_ok, sp_runtime::traits::Dispatchable}; + +#[test] +fn fellows_whitelist_call() { + CollectivesWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeCall = ::RuntimeCall; + type RuntimeOrigin = ::RuntimeOrigin; + type Runtime = ::Runtime; + type WestendCall = ::RuntimeCall; + type WestendRuntime = ::Runtime; + + let call_hash = [1u8; 32].into(); + + let whitelist_call = RuntimeCall::PolkadotXcm(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::from(Location::parent())), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: Weight::from_parts(5_000_000_000, 500_000), + call: WestendCall::Whitelist( + pallet_whitelist::Call::::whitelist_call { call_hash } + ) + .encode() + .into(), + } + ]))), + }); + + let fellows_origin: RuntimeOrigin = FellowsOrigin.into(); + + assert_ok!(whitelist_call.dispatch(fellows_origin)); + + assert_expected_events!( + CollectivesWestend, + vec![ + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + Westend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::Whitelist(pallet_whitelist::Event::CallWhitelisted { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_salary.rs b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_salary.rs new file mode 100644 index 000000000000..840d2da49463 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_salary.rs @@ -0,0 +1,66 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 collectives_fellowship::FellowshipSalaryPaymaster; +use frame_support::{ + assert_ok, + traits::{fungibles::Mutate, tokens::Pay}, +}; +use xcm_executor::traits::ConvertLocation; + +const FELLOWSHIP_SALARY_PALLET_ID: u8 = 64; + +#[test] +fn pay_salary() { + let asset_id: u32 = 1984; + let fellowship_salary = ( + Parent, + Parachain(CollectivesWestend::para_id().into()), + PalletInstance(FELLOWSHIP_SALARY_PALLET_ID), + ); + let pay_from = + AssetHubLocationToAccountId::convert_location(&fellowship_salary.into()).unwrap(); + let pay_to = Westend::account_id_of(ALICE); + let pay_amount = 9_000_000_000; + + AssetHubWestend::execute_with(|| { + type AssetHubAssets = ::Assets; + assert_ok!(>::mint_into(asset_id, &pay_from, pay_amount * 2)); + }); + + CollectivesWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(FellowshipSalaryPaymaster::pay(&pay_to, (), pay_amount)); + assert_expected_events!( + CollectivesWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::Assets(pallet_assets::Event::Transferred { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/mod.rs index 40e98a8b6869..ef4e4885183d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/mod.rs @@ -13,5 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod fellowship; +mod fellowship_salary; mod fellowship_treasury; mod teleport; 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 index 3c0533f775e2..06b0b6ba6005 100644 --- 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 @@ -15,15 +15,8 @@ #[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, - }; + pub use frame_support::{assert_ok, sp_runtime::DispatchResult, traits::fungibles::Inspect}; // Polkadot pub use xcm::prelude::*; @@ -37,20 +30,14 @@ mod imports { pub use parachains_common::Balance; pub use rococo_system_emulated_network::{ people_rococo_emulated_chain::{ - genesis::ED as PEOPLE_ROCOCO_ED, people_rococo_runtime::{ - people, xcm_config::XcmConfig as PeopleRococoXcmConfig, - ExistentialDeposit as PeopleRococoExistentialDeposit, Runtime as PeopleRuntime, + xcm_config::XcmConfig as PeopleRococoXcmConfig, + ExistentialDeposit as PeopleRococoExistentialDeposit, }, PeopleRococoParaPallet as PeopleRococoPallet, }, rococo_emulated_chain::{ - genesis::ED as ROCOCO_ED, - rococo_runtime::{ - xcm_config::XcmConfig as RococoXcmConfig, BasicDeposit, ByteDeposit, - MaxAdditionalFields, MaxSubAccounts, Runtime as RococoRuntime, - RuntimeOrigin as RococoOrigin, SubAccountDeposit, - }, + genesis::ED as ROCOCO_ED, rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig, RococoRelayPallet as RococoPallet, }, PeopleRococoPara as PeopleRococo, PeopleRococoParaReceiver as PeopleRococoReceiver, @@ -58,7 +45,6 @@ mod imports { RococoRelayReceiver as RococoReceiver, RococoRelaySender as RococoSender, }; - pub type RelayToSystemParaTest = Test; pub type SystemParaToRelayTest = Test; } diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/mod.rs index 3f18621224ac..08749b295dc2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/mod.rs @@ -14,5 +14,4 @@ // limitations under the License. mod claim_assets; -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 deleted file mode 100644 index 10f0c61ed63c..000000000000 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/reap_identity.rs +++ /dev/null @@ -1,545 +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. - -//! # 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, IdentityOf, SubsOf}; -use people::{ - BasicDeposit as BasicDepositParachain, ByteDeposit as ByteDepositParachain, - IdentityInfo as IdentityInfoParachain, SubAccountDeposit as SubAccountDepositParachain, -}; -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!(IdentityOf::::get(PeopleRococoSender::get()).is_some()); - - let (_, sub_accounts) = SubsOf::::get(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!(IdentityOf::::get(RococoRelaySender::get()).is_none()); - - // Subs should be gone. - let (_, sub_accounts) = SubsOf::::get(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 index 4410d1bd40dc..44e6b3934f0e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs @@ -14,68 +14,38 @@ // limitations under the License. use crate::imports::*; +use emulated_integration_tests_common::{ + test_parachain_is_trusted_teleporter_for_relay, test_relay_is_trusted_teleporter, +}; -fn relay_origin_assertions(t: RelayToSystemParaTest) { - type RuntimeEvent = ::RuntimeEvent; - Rococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(627_959_000, 7_200))); +#[test] +fn teleport_from_and_to_relay() { + let amount = ROCOCO_ED * 100; + let native_asset: Assets = (Here, amount).into(); - assert_expected_events!( + test_relay_is_trusted_teleporter!( 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)), + RococoXcmConfig, + vec![PeopleRococo], + (native_asset, amount) ); - assert_expected_events!( + test_parachain_is_trusted_teleporter_for_relay!( + PeopleRococo, + PeopleRococoXcmConfig, 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, - }, - ] + amount ); } 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)), - ); + Rococo::assert_ump_queue_processed(false, Some(PeopleRococo::para_id()), None); } 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_xcm_pallet_attempted_complete(None); PeopleRococo::assert_parachain_system_ump_sent(); @@ -91,33 +61,6 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { ); } -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, @@ -129,92 +72,8 @@ fn system_para_limited_teleport_assets(t: SystemParaToRelayTest) -> DispatchResu ) } -/// 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::teleport_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::teleport_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` +/// shouldn'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 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 index f7e1cce85a2c..aa6eebc5458f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml @@ -15,6 +15,7 @@ frame-support = { workspace = true } pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } +pallet-xcm = { workspace = true } sp-runtime = { workspace = true } # Polkadot 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 index 689409fe5040..418cfea07ddc 100644 --- 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 @@ -15,14 +15,8 @@ #[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, - }; + pub use frame_support::{assert_ok, sp_runtime::DispatchResult, traits::fungibles::Inspect}; // Polkadot pub use xcm::prelude::*; @@ -37,20 +31,14 @@ mod imports { pub use westend_system_emulated_network::{ self, people_westend_emulated_chain::{ - genesis::ED as PEOPLE_WESTEND_ED, people_westend_runtime::{ - people, xcm_config::XcmConfig as PeopleWestendXcmConfig, - ExistentialDeposit as PeopleWestendExistentialDeposit, Runtime as PeopleRuntime, + xcm_config::XcmConfig as PeopleWestendXcmConfig, + ExistentialDeposit as PeopleWestendExistentialDeposit, }, PeopleWestendParaPallet as PeopleWestendPallet, }, westend_emulated_chain::{ - genesis::ED as WESTEND_ED, - westend_runtime::{ - xcm_config::XcmConfig as WestendXcmConfig, BasicDeposit, ByteDeposit, - MaxAdditionalFields, MaxSubAccounts, Runtime as WestendRuntime, - RuntimeOrigin as WestendOrigin, SubAccountDeposit, - }, + genesis::ED as WESTEND_ED, westend_runtime::xcm_config::XcmConfig as WestendXcmConfig, WestendRelayPallet as WestendPallet, }, PeopleWestendPara as PeopleWestend, PeopleWestendParaReceiver as PeopleWestendReceiver, @@ -58,7 +46,6 @@ mod imports { WestendRelayReceiver as WestendReceiver, WestendRelaySender as WestendSender, }; - pub type RelayToSystemParaTest = Test; pub type SystemParaToRelayTest = Test; } diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs index 3f18621224ac..08749b295dc2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs @@ -14,5 +14,4 @@ // limitations under the License. mod claim_assets; -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 deleted file mode 100644 index cfbf4d7d9580..000000000000 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/reap_identity.rs +++ /dev/null @@ -1,547 +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. - -//! # 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, IdentityOf, SubsOf}; -use people::{ - BasicDeposit as BasicDepositParachain, ByteDeposit as ByteDepositParachain, - IdentityInfo as IdentityInfoParachain, SubAccountDeposit as SubAccountDepositParachain, -}; -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!(IdentityOf::::get(PeopleWestendSender::get()).is_some()); - - let (_, sub_accounts) = SubsOf::::get(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!(IdentityOf::::get(WestendRelaySender::get()).is_none()); - - // Subs should be gone. - let (_, sub_accounts) = SubsOf::::get(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 index 6fd3cdeb61fb..83888031723f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs @@ -14,68 +14,38 @@ // limitations under the License. use crate::imports::*; +use emulated_integration_tests_common::{ + test_parachain_is_trusted_teleporter_for_relay, test_relay_is_trusted_teleporter, +}; -fn relay_origin_assertions(t: RelayToSystemParaTest) { - type RuntimeEvent = ::RuntimeEvent; - Westend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(627_959_000, 7_200))); +#[test] +fn teleport_from_and_to_relay() { + let amount = WESTEND_ED * 100; + let native_asset: Assets = (Here, amount).into(); - assert_expected_events!( + test_relay_is_trusted_teleporter!( 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)), + WestendXcmConfig, + vec![PeopleWestend], + (native_asset, amount) ); - assert_expected_events!( + test_parachain_is_trusted_teleporter_for_relay!( + PeopleWestend, + PeopleWestendXcmConfig, 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, - }, - ] + amount ); } 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)), - ); + Westend::assert_ump_queue_processed(false, Some(PeopleWestend::para_id()), None); } 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_xcm_pallet_attempted_complete(None); PeopleWestend::assert_parachain_system_ump_sent(); @@ -91,33 +61,6 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { ); } -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, @@ -129,92 +72,8 @@ fn system_para_limited_teleport_assets(t: SystemParaToRelayTest) -> DispatchResu ) } -/// 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::teleport_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::teleport_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` +/// shouldn'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 diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 98df41090a40..0143c09036d2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -258,4 +258,4 @@ metadata-hash = ["substrate-wasm-builder/metadata-hash"] # 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 = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] 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 7414adacc448..4c7356707ab6 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -83,10 +83,13 @@ use sp_runtime::{Perbill, RuntimeDebug}; use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ ForeignAssetsConvertedConcreteId, ForeignCreatorsSovereignAccountOf, GovernanceLocation, - PoolAssetsConvertedConcreteId, TokenLocation, TokenLocationV3, - TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocationV3, + PoolAssetsConvertedConcreteId, TokenLocation, TrustBackedAssetsConvertedConcreteId, + TrustBackedAssetsPalletLocation, }; +#[cfg(test)] +mod tests; + #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -324,11 +327,11 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< Assets, ForeignAssets, LocalFromLeft< - AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssetsConvert, AssetIdForTrustBackedAssets, - xcm::v3::Location, + xcm::v4::Location, >, - xcm::v3::Location, + xcm::v4::Location, AccountId, >; @@ -336,25 +339,25 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< pub type NativeAndAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, - TargetFromLeft, - xcm::v3::Location, + TargetFromLeft, + xcm::v4::Location, AccountId, >; pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< AssetConversionPalletId, - (xcm::v3::Location, xcm::v3::Location), + (xcm::v4::Location, xcm::v4::Location), >; impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; - type AssetKind = xcm::v3::Location; + type AssetKind = xcm::v4::Location; type Assets = NativeAndAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< - TokenLocationV3, + TokenLocation, AccountId, Self::AssetKind, PoolIdToAccountId, @@ -362,7 +365,7 @@ impl pallet_asset_conversion::Config for Runtime { type PoolAssetId = u32; type PoolAssets = PoolAssets; type PoolSetupFee = ConstU128<0>; // Asset class deposit fees are sufficient to prevent spam - type PoolSetupFeeAsset = TokenLocationV3; + type PoolSetupFeeAsset = TokenLocation; type PoolSetupFeeTarget = ResolveAssetTo; type LiquidityWithdrawalFee = LiquidityWithdrawalFee; type LPFee = ConstU32<3>; @@ -372,10 +375,10 @@ impl pallet_asset_conversion::Config for Runtime { type WeightInfo = weights::pallet_asset_conversion::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = assets_common::benchmarks::AssetPairFactory< - TokenLocationV3, + TokenLocation, parachain_info::Pallet, xcm_config::TrustBackedAssetsPalletIndex, - xcm::v3::Location, + xcm::v4::Location, >; } @@ -409,17 +412,17 @@ 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 AssetId = xcm::v4::Location; + type AssetIdParameter = xcm::v4::Location; type Currency = Balances; type CreateOrigin = ForeignCreators< ( - FromSiblingParachain, xcm::v3::Location>, - FromNetwork, + FromSiblingParachain, xcm::v4::Location>, + FromNetwork, ), ForeignCreatorsSovereignAccountOf, AccountId, - xcm::v3::Location, + xcm::v4::Location, >; type ForceOrigin = AssetsForceOrigin; type AssetDeposit = ForeignAssetsAssetDeposit; @@ -804,9 +807,9 @@ parameter_types! { impl pallet_asset_conversion_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type AssetId = xcm::v3::Location; + type AssetId = xcm::v4::Location; type OnChargeAssetTransaction = SwapAssetAdapter< - TokenLocationV3, + TokenLocation, NativeAndAssets, AssetConversion, ResolveAssetTo, @@ -1019,7 +1022,6 @@ pub type SignedExtra = ( pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -#[allow(deprecated)] pub type Migrations = ( InitStorageVersions, // unreleased @@ -1234,16 +1236,16 @@ impl_runtime_apis! { impl pallet_asset_conversion::AssetConversionApi< Block, Balance, - xcm::v3::Location, + xcm::v4::Location, > for Runtime { - fn quote_price_exact_tokens_for_tokens(asset1: xcm::v3::Location, asset2: xcm::v3::Location, amount: Balance, include_fee: bool) -> Option { + fn quote_price_exact_tokens_for_tokens(asset1: xcm::v4::Location, asset2: xcm::v4::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: xcm::v3::Location, asset2: xcm::v3::Location, amount: Balance, include_fee: bool) -> Option { + fn quote_price_tokens_for_exact_tokens(asset1: xcm::v4::Location, asset2: xcm::v4::Location, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) } - fn get_reserves(asset1: xcm::v3::Location, asset2: xcm::v3::Location) -> Option<(Balance, Balance)> { + fn get_reserves(asset1: xcm::v4::Location, asset2: xcm::v4::Location) -> Option<(Balance, Balance)> { AssetConversion::get_reserves(asset1, asset2).ok() } } @@ -1781,64 +1783,3 @@ 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 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] - 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-rococo/src/tests/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/tests/mod.rs new file mode 100644 index 000000000000..12c0bc4e1688 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/tests/mod.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. + +//! # Tests for the Rococo runtime. + +use super::*; +use crate::{CENTS, MILLICENTS}; +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] +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-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 03d3785dccbd..3d6ae6ddd1d2 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 @@ -16,10 +16,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-11-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-696hpswk-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: @@ -54,8 +54,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 21_643_000 picoseconds. - Weight::from_parts(22_410_000, 3593) + // Minimum execution time: 34_180_000 picoseconds. + Weight::from_parts(34_745_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,8 +65,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 43_758_000 picoseconds. - Weight::from_parts(44_654_000, 6196) + // Minimum execution time: 42_638_000 picoseconds. + Weight::from_parts(43_454_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -90,20 +90,17 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `246` // Estimated: `8799` - // Minimum execution time: 87_978_000 picoseconds. - Weight::from_parts(88_517_000, 8799) + // Minimum execution time: 102_916_000 picoseconds. + Weight::from_parts(105_699_000, 8799) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(5)) } - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) pub fn reserve_asset_deposited() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 6_883_000 picoseconds. - Weight::from_parts(6_979_000, 1489) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 1_805_000 picoseconds. + Weight::from_parts(1_901_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -125,8 +122,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `246` // Estimated: `6196` - // Minimum execution time: 198_882_000 picoseconds. - Weight::from_parts(199_930_000, 6196) + // Minimum execution time: 108_018_000 picoseconds. + Weight::from_parts(110_310_000, 6196) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -134,8 +131,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_343_000 picoseconds. - Weight::from_parts(3_487_000, 0) + // Minimum execution time: 3_507_000 picoseconds. + Weight::from_parts(3_724_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -143,13 +140,11 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 19_399_000 picoseconds. - Weight::from_parts(19_659_000, 3593) + // Minimum execution time: 26_269_000 picoseconds. + Weight::from_parts(26_706_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) @@ -160,6 +155,8 @@ impl WeightInfo { // 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) @@ -168,8 +165,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `6196` - // Minimum execution time: 59_017_000 picoseconds. - Weight::from_parts(60_543_000, 6196) + // Minimum execution time: 84_759_000 picoseconds. + Weight::from_parts(86_157_000, 6196) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -193,8 +190,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 45_409_000 picoseconds. - Weight::from_parts(47_041_000, 3610) + // Minimum execution time: 50_876_000 picoseconds. + Weight::from_parts(51_512_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/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index a11dca4f6d7c..2d1914e059bf 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 @@ -65,7 +65,6 @@ use xcm_executor::XcmExecutor; parameter_types! { 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: InteriorLocation = @@ -74,8 +73,6 @@ parameter_types! { 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: Location = @@ -177,7 +174,7 @@ pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConverte StartsWithExplicitGlobalConsensus, ), Balance, - xcm::v3::Location, + xcm::v4::Location, >; /// Means for transacting foreign assets from different global consensus. @@ -361,7 +358,7 @@ impl xcm_executor::Config for XcmConfig { ResolveTo, >, cumulus_primitives_utility::SwapFirstAssetTrader< - TokenLocationV3, + TokenLocation, crate::AssetConversion, WeightToFee, crate::NativeAndAssets, @@ -369,7 +366,7 @@ impl xcm_executor::Config for XcmConfig { TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, Balance, - xcm::v3::Location, + xcm::v4::Location, >, ForeignAssetsConvertedConcreteId, ), @@ -502,9 +499,9 @@ 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) -> xcm::v3::Location { - xcm::v3::Location::new(1, [xcm::v3::Junction::Parachain(id)]) +impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { + fn create_asset_id_parameter(id: u32) -> xcm::v4::Location { + xcm::v4::Location::new(1, [xcm::v4::Junction::Parachain(id)]) } } 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 ee1461b7f9c8..83f4f9ec3dc5 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -85,7 +85,7 @@ fn slot_durations() -> SlotDurations { fn setup_pool_for_paying_fees_with_foreign_assets( (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( AccountId, - xcm::v3::Location, + Location, Balance, ), ) { @@ -93,7 +93,7 @@ fn setup_pool_for_paying_fees_with_foreign_assets( // 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 native_asset = Location::parent(); let pool_liquidity: Balance = existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); @@ -105,15 +105,15 @@ fn setup_pool_for_paying_fees_with_foreign_assets( assert_ok!(ForeignAssets::mint( RuntimeOrigin::signed(foreign_asset_owner), - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().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()) + Box::new(native_asset.clone().into()), + Box::new(foreign_asset_id_location.clone().into()) )); assert_ok!(AssetConversion::add_liquidity( @@ -217,24 +217,14 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { assert_ok!(AssetConversion::create_pool( RuntimeHelper::origin_of(bob.clone()), - Box::new( - xcm::v3::Location::try_from(native_location.clone()).expect("conversion works") - ), - Box::new( - xcm::v3::Location::try_from(asset_1_location.clone()) - .expect("conversion works") - ) + Box::new(Location::try_from(native_location.clone()).expect("conversion works")), + Box::new(Location::try_from(asset_1_location.clone()).expect("conversion works")) )); assert_ok!(AssetConversion::add_liquidity( RuntimeHelper::origin_of(bob.clone()), - Box::new( - xcm::v3::Location::try_from(native_location.clone()).expect("conversion works") - ), - Box::new( - xcm::v3::Location::try_from(asset_1_location.clone()) - .expect("conversion works") - ), + Box::new(Location::try_from(native_location.clone()).expect("conversion works")), + Box::new(Location::try_from(asset_1_location.clone()).expect("conversion works")), pool_liquidity, pool_liquidity, 1, @@ -270,8 +260,8 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { 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( - xcm::v3::Location::try_from(native_location).expect("conversion works"), - xcm::v3::Location::try_from(asset_1_location.clone()).expect("conversion works"), + Location::try_from(native_location).expect("conversion works"), + Location::try_from(asset_1_location.clone()).expect("conversion works"), ) .unwrap(); let asset_refund = @@ -309,14 +299,10 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { let bob: AccountId = SOME_ASSET_ADMIN.into(); let staking_pot = CollatorSelection::account_id(); let native_location = - xcm::v3::Location::try_from(TokenLocation::get()).expect("conversion works"); - let foreign_location = xcm::v3::Location { + Location::try_from(TokenLocation::get()).expect("conversion works"); + let foreign_location = Location { parents: 1, - interior: ( - xcm::v3::Junction::Parachain(1234), - xcm::v3::Junction::GeneralIndex(12345), - ) - .into(), + interior: (Junction::Parachain(1234), Junction::GeneralIndex(12345)).into(), }; // bob's initial balance for native and `asset1` assets. let initial_balance = 200 * UNITS; @@ -325,26 +311,26 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { // init asset, balances and pool. assert_ok!(>::create( - foreign_location, + foreign_location.clone(), bob.clone(), true, 10 )); - assert_ok!(ForeignAssets::mint_into(foreign_location, &bob, initial_balance)); + assert_ok!(ForeignAssets::mint_into(foreign_location.clone(), &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) + Box::new(native_location.clone()), + Box::new(foreign_location.clone()) )); assert_ok!(AssetConversion::add_liquidity( RuntimeHelper::origin_of(bob.clone()), - Box::new(native_location), - Box::new(foreign_location), + Box::new(native_location.clone()), + Box::new(foreign_location.clone()), pool_liquidity, pool_liquidity, 1, @@ -353,11 +339,9 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { )); // keep initial total issuance to assert later. - let asset_total_issuance = ForeignAssets::total_issuance(foreign_location); + let asset_total_issuance = ForeignAssets::total_issuance(foreign_location.clone()); 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); @@ -365,7 +349,7 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { 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(); + let payment: Asset = (foreign_location.clone(), asset_fee + extra_amount).into(); // init trader and buy weight. let mut trader = ::Trader::new(); @@ -373,13 +357,11 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { 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); + let unused_amount = + unused_asset.fungible.get(&foreign_location.clone().into()).map_or(0, |a| *a); assert_eq!(unused_amount, extra_amount); assert_eq!( - ForeignAssets::total_issuance(foreign_location), + ForeignAssets::total_issuance(foreign_location.clone()), asset_total_issuance + asset_fee ); @@ -387,13 +369,13 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { 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(); + AssetConversion::get_reserves(native_location, foreign_location.clone()).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_eq!(actual_refund, (foreign_location.clone(), asset_refund).into()); // assert. assert_eq!(Balances::balance(&staking_pot), initial_balance); @@ -500,17 +482,13 @@ fn test_foreign_asset_xcm_take_first_trader() { .execute_with(|| { // We need root origin to create a sufficient asset let minimum_asset_balance = 3333333_u128; - let foreign_location = xcm::v3::Location { + let foreign_location = Location { parents: 1, - interior: ( - xcm::v3::Junction::Parachain(1234), - xcm::v3::Junction::GeneralIndex(12345), - ) - .into(), + interior: (Junction::Parachain(1234), Junction::GeneralIndex(12345)).into(), }; assert_ok!(ForeignAssets::force_create( RuntimeHelper::root_origin(), - foreign_location.into(), + foreign_location.clone().into(), AccountId::from(ALICE).into(), true, minimum_asset_balance @@ -519,13 +497,11 @@ fn test_foreign_asset_xcm_take_first_trader() { // 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(), + foreign_location.clone().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)); @@ -535,7 +511,7 @@ fn test_foreign_asset_xcm_take_first_trader() { // Lets calculate amount needed let asset_amount_needed = ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles( - foreign_location, + foreign_location.clone(), bought ) .expect("failed to compute"); @@ -543,7 +519,7 @@ fn test_foreign_asset_xcm_take_first_trader() { // 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(); + (foreign_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 }; @@ -551,16 +527,15 @@ fn test_foreign_asset_xcm_take_first_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_location_v4, asset_amount_extra).into()) - ); + assert_ok!(unused_assets + .ensure_contains(&(foreign_location.clone(), 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)), + ForeignAssets::balance(foreign_location.clone(), AccountId::from(ALICE)), minimum_asset_balance + asset_amount_needed ); @@ -841,15 +816,13 @@ fn test_assets_balances_api_works() { .build() .execute_with(|| { let local_asset_id = 1; - let foreign_asset_id_location = xcm::v3::Location::new( - 1, - [xcm::v3::Junction::Parachain(1234), xcm::v3::Junction::GeneralIndex(12345)], - ); + let foreign_asset_id_location = + Location::new(1, [Junction::Parachain(1234), Junction::GeneralIndex(12345)]); // check before assert_eq!(Assets::balance(local_asset_id, AccountId::from(ALICE)), 0); assert_eq!( - ForeignAssets::balance(foreign_asset_id_location, AccountId::from(ALICE)), + ForeignAssets::balance(foreign_asset_id_location.clone(), AccountId::from(ALICE)), 0 ); assert_eq!(Balances::free_balance(AccountId::from(ALICE)), 0); @@ -886,7 +859,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_location, + foreign_asset_id_location.clone(), AccountId::from(SOME_ASSET_ADMIN).into(), false, foreign_asset_minimum_asset_balance @@ -895,7 +868,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_location, + foreign_asset_id_location.clone(), AccountId::from(ALICE).into(), 6 * foreign_asset_minimum_asset_balance )); @@ -906,7 +879,7 @@ fn test_assets_balances_api_works() { minimum_asset_balance ); assert_eq!( - ForeignAssets::balance(foreign_asset_id_location, AccountId::from(ALICE)), + ForeignAssets::balance(foreign_asset_id_location.clone(), AccountId::from(ALICE)), 6 * minimum_asset_balance ); assert_eq!(Balances::free_balance(AccountId::from(ALICE)), some_currency); @@ -932,10 +905,8 @@ fn test_assets_balances_api_works() { .into()))); // check foreign asset assert!(result.inner().iter().any(|asset| asset.eq(&( - WithLatestLocationConverter::::convert_back( - &foreign_asset_id_location - ) - .unwrap(), + WithLatestLocationConverter::::convert_back(&foreign_asset_id_location) + .unwrap(), 6 * foreign_asset_minimum_asset_balance ) .into()))); @@ -1025,14 +996,11 @@ asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_ Runtime, XcmConfig, ForeignAssetsInstance, - xcm::v3::Location, + Location, JustTry, collator_session_keys(), ExistentialDeposit::get(), - xcm::v3::Location::new( - 1, - [xcm::v3::Junction::Parachain(1313), xcm::v3::Junction::GeneralIndex(12345)] - ), + Location::new(1, [Junction::Parachain(1313), Junction::GeneralIndex(12345)]), Box::new(|| { assert!(Assets::asset_ids().collect::>().is_empty()); }), @@ -1047,8 +1015,8 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p WeightToFee, ForeignCreatorsSovereignAccountOf, ForeignAssetsInstance, - xcm::v3::Location, - WithLatestLocationConverter, + Location, + WithLatestLocationConverter, collator_session_keys(), ExistentialDeposit::get(), AssetDeposit::get(), @@ -1138,16 +1106,17 @@ mod asset_hub_rococo_tests { 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_location = + Location::new(2, [Junction::GlobalConsensus(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); + let foreign_asset_create_params = ( + foreign_asset_owner, + foreign_asset_id_location.clone(), + foreign_asset_id_minimum_balance, + ); asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< Runtime, @@ -1181,7 +1150,7 @@ mod asset_hub_rococo_tests { // check now foreign asset for staking pot assert_eq!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &staking_pot ), 0 @@ -1195,7 +1164,7 @@ mod asset_hub_rococo_tests { // staking pot receives no foreign assets assert_eq!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &staking_pot ), 0 @@ -1211,16 +1180,17 @@ mod asset_hub_rococo_tests { 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_location = + Location::new(2, [Junction::GlobalConsensus(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); + let foreign_asset_create_params = ( + foreign_asset_owner, + foreign_asset_id_location.clone(), + foreign_asset_id_minimum_balance, + ); asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< Runtime, @@ -1245,7 +1215,7 @@ mod asset_hub_rococo_tests { // check block author before assert_eq!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &block_author_account ), 0 @@ -1255,7 +1225,7 @@ mod asset_hub_rococo_tests { // `TakeFirstAssetTrader` puts fees to the block author assert!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &block_author_account ) > 0 ); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 2f244a07e8f1..77130ff846b5 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -262,4 +262,4 @@ metadata-hash = ["substrate-wasm-builder/metadata-hash"] # 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 = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] 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 201698ecb7f9..ebbc000d1413 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -80,8 +80,7 @@ use testnet_parachains_constants::westend::{ use xcm_config::{ ForeignAssetsConvertedConcreteId, ForeignCreatorsSovereignAccountOf, PoolAssetsConvertedConcreteId, TrustBackedAssetsConvertedConcreteId, - TrustBackedAssetsPalletLocationV3, WestendLocation, WestendLocationV3, - XcmOriginToTransactDispatchOrigin, + TrustBackedAssetsPalletLocation, WestendLocation, XcmOriginToTransactDispatchOrigin, }; #[cfg(any(feature = "std", test))] @@ -326,11 +325,11 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< Assets, ForeignAssets, LocalFromLeft< - AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssetsConvert, AssetIdForTrustBackedAssets, - xcm::v3::Location, + xcm::v4::Location, >, - xcm::v3::Location, + xcm::v4::Location, AccountId, >; @@ -338,25 +337,25 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< pub type NativeAndAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, - TargetFromLeft, - xcm::v3::Location, + TargetFromLeft, + xcm::v4::Location, AccountId, >; pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< AssetConversionPalletId, - (xcm::v3::Location, xcm::v3::Location), + (xcm::v4::Location, xcm::v4::Location), >; impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; - type AssetKind = xcm::v3::Location; + type AssetKind = xcm::v4::Location; type Assets = NativeAndAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< - WestendLocationV3, + WestendLocation, AccountId, Self::AssetKind, PoolIdToAccountId, @@ -364,7 +363,7 @@ impl pallet_asset_conversion::Config for Runtime { type PoolAssetId = u32; type PoolAssets = PoolAssets; type PoolSetupFee = ConstU128<0>; // Asset class deposit fees are sufficient to prevent spam - type PoolSetupFeeAsset = WestendLocationV3; + type PoolSetupFeeAsset = WestendLocation; type PoolSetupFeeTarget = ResolveAssetTo; type LiquidityWithdrawalFee = LiquidityWithdrawalFee; type LPFee = ConstU32<3>; @@ -374,10 +373,10 @@ impl pallet_asset_conversion::Config for Runtime { type WeightInfo = weights::pallet_asset_conversion::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = assets_common::benchmarks::AssetPairFactory< - WestendLocationV3, + WestendLocation, parachain_info::Pallet, xcm_config::TrustBackedAssetsPalletIndex, - xcm::v3::Location, + xcm::v4::Location, >; } @@ -411,17 +410,17 @@ 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 AssetId = xcm::v4::Location; + type AssetIdParameter = xcm::v4::Location; type Currency = Balances; type CreateOrigin = ForeignCreators< ( - FromSiblingParachain, xcm::v3::Location>, - FromNetwork, + FromSiblingParachain, xcm::v4::Location>, + FromNetwork, ), ForeignCreatorsSovereignAccountOf, AccountId, - xcm::v3::Location, + xcm::v4::Location, >; type ForceOrigin = AssetsForceOrigin; type AssetDeposit = ForeignAssetsAssetDeposit; @@ -801,9 +800,9 @@ parameter_types! { impl pallet_asset_conversion_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type AssetId = xcm::v3::Location; + type AssetId = xcm::v4::Location; type OnChargeAssetTransaction = SwapAssetAdapter< - WestendLocationV3, + WestendLocation, NativeAndAssets, AssetConversion, ResolveAssetTo, @@ -1331,18 +1330,18 @@ impl_runtime_apis! { impl pallet_asset_conversion::AssetConversionApi< Block, Balance, - xcm::v3::Location, + xcm::v4::Location, > for Runtime { - fn quote_price_exact_tokens_for_tokens(asset1: xcm::v3::Location, asset2: xcm::v3::Location, amount: Balance, include_fee: bool) -> Option { + fn quote_price_exact_tokens_for_tokens(asset1: xcm::v4::Location, asset2: xcm::v4::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: xcm::v3::Location, asset2: xcm::v3::Location, amount: Balance, include_fee: bool) -> Option { + fn quote_price_tokens_for_exact_tokens(asset1: xcm::v4::Location, asset2: xcm::v4::Location, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) } - fn get_reserves(asset1: xcm::v3::Location, asset2: xcm::v3::Location) -> Option<(Balance, Balance)> { + fn get_reserves(asset1: xcm::v4::Location, asset2: xcm::v4::Location) -> Option<(Balance, Balance)> { AssetConversion::get_reserves(asset1, asset2).ok() } } 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 fe8d18613925..f7891aedc496 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,24 +1,25 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-vmdtonbz-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-696hpswk-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: @@ -53,8 +54,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 20_295_000 picoseconds. - Weight::from_parts(21_142_000, 3593) + // Minimum execution time: 32_612_000 picoseconds. + Weight::from_parts(33_359_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -64,8 +65,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 42_356_000 picoseconds. - Weight::from_parts(43_552_000, 6196) + // Minimum execution time: 41_144_000 picoseconds. + Weight::from_parts(41_788_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -89,20 +90,17 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `246` // Estimated: `8799` - // Minimum execution time: 85_553_000 picoseconds. - Weight::from_parts(87_177_000, 8799) + // Minimum execution time: 101_340_000 picoseconds. + Weight::from_parts(103_686_000, 8799) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(5)) } - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) pub fn reserve_asset_deposited() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 6_166_000 picoseconds. - Weight::from_parts(6_352_000, 1489) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 1_682_000 picoseconds. + Weight::from_parts(1_734_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -124,8 +122,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `246` // Estimated: `6196` - // Minimum execution time: 184_462_000 picoseconds. - Weight::from_parts(189_593_000, 6196) + // Minimum execution time: 107_335_000 picoseconds. + Weight::from_parts(109_665_000, 6196) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -133,8 +131,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_018_000 picoseconds. - Weight::from_parts(3_098_000, 0) + // Minimum execution time: 3_345_000 picoseconds. + Weight::from_parts(3_548_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -142,13 +140,11 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 18_583_000 picoseconds. - Weight::from_parts(19_057_000, 3593) + // Minimum execution time: 25_560_000 picoseconds. + Weight::from_parts(26_779_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) @@ -159,6 +155,8 @@ impl WeightInfo { // 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) @@ -167,8 +165,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `6196` - // Minimum execution time: 56_666_000 picoseconds. - Weight::from_parts(58_152_000, 6196) + // Minimum execution time: 84_453_000 picoseconds. + Weight::from_parts(86_755_000, 6196) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -192,8 +190,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 44_197_000 picoseconds. - Weight::from_parts(45_573_000, 3610) + // Minimum execution time: 50_463_000 picoseconds. + Weight::from_parts(51_587_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/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index bf45e146e334..d61381d3f50b 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 @@ -62,7 +62,6 @@ use xcm_executor::XcmExecutor; parameter_types! { 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: InteriorLocation = @@ -71,8 +70,6 @@ parameter_types! { 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: Location = @@ -174,7 +171,7 @@ pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConverte StartsWithExplicitGlobalConsensus, ), Balance, - xcm::v3::Location, + xcm::v4::Location, >; /// Means for transacting foreign assets from different global consensus. @@ -384,7 +381,7 @@ impl xcm_executor::Config for XcmConfig { ResolveTo, >, cumulus_primitives_utility::SwapFirstAssetTrader< - WestendLocationV3, + WestendLocation, crate::AssetConversion, WeightToFee, crate::NativeAndAssets, @@ -392,7 +389,7 @@ impl xcm_executor::Config for XcmConfig { TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, Balance, - xcm::v3::Location, + xcm::v4::Location, >, ForeignAssetsConvertedConcreteId, ), @@ -526,9 +523,9 @@ 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) -> xcm::v3::Location { - xcm::v3::Location::new(1, [xcm::v3::Junction::Parachain(id)]) +impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { + fn create_asset_id_parameter(id: u32) -> xcm::v4::Location { + xcm::v4::Location::new(1, [xcm::v4::Junction::Parachain(id)]) } } 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 48e6c11d268c..1c334d6f84f8 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -86,7 +86,7 @@ fn slot_durations() -> SlotDurations { fn setup_pool_for_paying_fees_with_foreign_assets( (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( AccountId, - xcm::v3::Location, + xcm::v4::Location, Balance, ), ) { @@ -94,7 +94,7 @@ fn setup_pool_for_paying_fees_with_foreign_assets( // 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 native_asset = xcm::v4::Location::parent(); let pool_liquidity: Balance = existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); @@ -106,15 +106,15 @@ fn setup_pool_for_paying_fees_with_foreign_assets( assert_ok!(ForeignAssets::mint( RuntimeOrigin::signed(foreign_asset_owner), - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().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()) + Box::new(native_asset.clone().into()), + Box::new(foreign_asset_id_location.clone().into()) )); assert_ok!(AssetConversion::add_liquidity( @@ -219,10 +219,10 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { assert_ok!(AssetConversion::create_pool( RuntimeHelper::origin_of(bob.clone()), Box::new( - xcm::v3::Location::try_from(native_location.clone()).expect("conversion works") + xcm::v4::Location::try_from(native_location.clone()).expect("conversion works") ), Box::new( - xcm::v3::Location::try_from(asset_1_location.clone()) + xcm::v4::Location::try_from(asset_1_location.clone()) .expect("conversion works") ) )); @@ -230,10 +230,10 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { assert_ok!(AssetConversion::add_liquidity( RuntimeHelper::origin_of(bob.clone()), Box::new( - xcm::v3::Location::try_from(native_location.clone()).expect("conversion works") + xcm::v4::Location::try_from(native_location.clone()).expect("conversion works") ), Box::new( - xcm::v3::Location::try_from(asset_1_location.clone()) + xcm::v4::Location::try_from(asset_1_location.clone()) .expect("conversion works") ), pool_liquidity, @@ -271,8 +271,8 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() { 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( - xcm::v3::Location::try_from(native_location).expect("conversion works"), - xcm::v3::Location::try_from(asset_1_location.clone()).expect("conversion works"), + xcm::v4::Location::try_from(native_location).expect("conversion works"), + xcm::v4::Location::try_from(asset_1_location.clone()).expect("conversion works"), ) .unwrap(); let asset_refund = @@ -310,12 +310,12 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { let bob: AccountId = SOME_ASSET_ADMIN.into(); let staking_pot = CollatorSelection::account_id(); let native_location = - xcm::v3::Location::try_from(WestendLocation::get()).expect("conversion works"); - let foreign_location = xcm::v3::Location { + xcm::v4::Location::try_from(WestendLocation::get()).expect("conversion works"); + let foreign_location = xcm::v4::Location { parents: 1, interior: ( - xcm::v3::Junction::Parachain(1234), - xcm::v3::Junction::GeneralIndex(12345), + xcm::v4::Junction::Parachain(1234), + xcm::v4::Junction::GeneralIndex(12345), ) .into(), }; @@ -326,26 +326,26 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { // init asset, balances and pool. assert_ok!(>::create( - foreign_location, + foreign_location.clone(), bob.clone(), true, 10 )); - assert_ok!(ForeignAssets::mint_into(foreign_location, &bob, initial_balance)); + assert_ok!(ForeignAssets::mint_into(foreign_location.clone(), &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) + Box::new(native_location.clone()), + Box::new(foreign_location.clone()) )); assert_ok!(AssetConversion::add_liquidity( RuntimeHelper::origin_of(bob.clone()), - Box::new(native_location), - Box::new(foreign_location), + Box::new(native_location.clone()), + Box::new(foreign_location.clone()), pool_liquidity, pool_liquidity, 1, @@ -354,11 +354,9 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { )); // keep initial total issuance to assert later. - let asset_total_issuance = ForeignAssets::total_issuance(foreign_location); + let asset_total_issuance = ForeignAssets::total_issuance(foreign_location.clone()); 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); @@ -366,7 +364,7 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { 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(); + let payment: Asset = (foreign_location.clone(), asset_fee + extra_amount).into(); // init trader and buy weight. let mut trader = ::Trader::new(); @@ -374,13 +372,11 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { 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); + let unused_amount = + unused_asset.fungible.get(&foreign_location.clone().into()).map_or(0, |a| *a); assert_eq!(unused_amount, extra_amount); assert_eq!( - ForeignAssets::total_issuance(foreign_location), + ForeignAssets::total_issuance(foreign_location.clone()), asset_total_issuance + asset_fee ); @@ -388,13 +384,13 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() { 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(); + AssetConversion::get_reserves(native_location, foreign_location.clone()).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_eq!(actual_refund, (foreign_location.clone(), asset_refund).into()); // assert. assert_eq!(Balances::balance(&staking_pot), initial_balance); @@ -501,17 +497,17 @@ fn test_foreign_asset_xcm_take_first_trader() { .execute_with(|| { // We need root origin to create a sufficient asset let minimum_asset_balance = 3333333_u128; - let foreign_location = xcm::v3::Location { + let foreign_location = xcm::v4::Location { parents: 1, interior: ( - xcm::v3::Junction::Parachain(1234), - xcm::v3::Junction::GeneralIndex(12345), + xcm::v4::Junction::Parachain(1234), + xcm::v4::Junction::GeneralIndex(12345), ) .into(), }; assert_ok!(ForeignAssets::force_create( RuntimeHelper::root_origin(), - foreign_location.into(), + foreign_location.clone().into(), AccountId::from(ALICE).into(), true, minimum_asset_balance @@ -520,12 +516,12 @@ fn test_foreign_asset_xcm_take_first_trader() { // 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(), + foreign_location.clone().into(), AccountId::from(ALICE).into(), minimum_asset_balance )); - let asset_location_v4: Location = foreign_location.try_into().unwrap(); + let asset_location_v4: Location = foreign_location.clone().try_into().unwrap(); // Set Alice as block author, who will receive fees RuntimeHelper::run_to_block(2, AccountId::from(ALICE)); @@ -534,7 +530,7 @@ fn test_foreign_asset_xcm_take_first_trader() { 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) + let asset_amount_needed = ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger::charge_weight_in_fungibles(foreign_location.clone(), bought) .expect("failed to compute"); // Lets pay with: asset_amount_needed + asset_amount_extra @@ -557,7 +553,7 @@ fn test_foreign_asset_xcm_take_first_trader() { // Make sure author(Alice) has received the amount assert_eq!( - ForeignAssets::balance(foreign_location, AccountId::from(ALICE)), + ForeignAssets::balance(foreign_location.clone(), AccountId::from(ALICE)), minimum_asset_balance + asset_amount_needed ); @@ -837,11 +833,11 @@ fn test_assets_balances_api_works() { .build() .execute_with(|| { let local_asset_id = 1; - let foreign_asset_id_location = xcm::v3::Location { + let foreign_asset_id_location = xcm::v4::Location { parents: 1, interior: [ - xcm::v3::Junction::Parachain(1234), - xcm::v3::Junction::GeneralIndex(12345), + xcm::v4::Junction::Parachain(1234), + xcm::v4::Junction::GeneralIndex(12345), ] .into(), }; @@ -849,7 +845,7 @@ fn test_assets_balances_api_works() { // check before assert_eq!(Assets::balance(local_asset_id, AccountId::from(ALICE)), 0); assert_eq!( - ForeignAssets::balance(foreign_asset_id_location, AccountId::from(ALICE)), + ForeignAssets::balance(foreign_asset_id_location.clone(), AccountId::from(ALICE)), 0 ); assert_eq!(Balances::free_balance(AccountId::from(ALICE)), 0); @@ -886,7 +882,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_location, + foreign_asset_id_location.clone(), AccountId::from(SOME_ASSET_ADMIN).into(), false, foreign_asset_minimum_asset_balance @@ -895,7 +891,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_location, + foreign_asset_id_location.clone(), AccountId::from(ALICE).into(), 6 * foreign_asset_minimum_asset_balance )); @@ -906,7 +902,7 @@ fn test_assets_balances_api_works() { minimum_asset_balance ); assert_eq!( - ForeignAssets::balance(foreign_asset_id_location, AccountId::from(ALICE)), + ForeignAssets::balance(foreign_asset_id_location.clone(), AccountId::from(ALICE)), 6 * minimum_asset_balance ); assert_eq!(Balances::free_balance(AccountId::from(ALICE)), some_currency); @@ -932,7 +928,7 @@ fn test_assets_balances_api_works() { .into()))); // check foreign asset assert!(result.inner().iter().any(|asset| asset.eq(&( - WithLatestLocationConverter::::convert_back( + WithLatestLocationConverter::::convert_back( &foreign_asset_id_location ) .unwrap(), @@ -1025,13 +1021,13 @@ asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_ Runtime, XcmConfig, ForeignAssetsInstance, - xcm::v3::Location, + xcm::v4::Location, JustTry, collator_session_keys(), ExistentialDeposit::get(), - xcm::v3::Location { + xcm::v4::Location { parents: 1, - interior: [xcm::v3::Junction::Parachain(1313), xcm::v3::Junction::GeneralIndex(12345)] + interior: [xcm::v4::Junction::Parachain(1313), xcm::v4::Junction::GeneralIndex(12345)] .into() }, Box::new(|| { @@ -1048,8 +1044,8 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p WeightToFee, ForeignCreatorsSovereignAccountOf, ForeignAssetsInstance, - xcm::v3::Location, - WithLatestLocationConverter, + xcm::v4::Location, + WithLatestLocationConverter, collator_session_keys(), ExistentialDeposit::get(), AssetDeposit::get(), @@ -1127,12 +1123,12 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_pool_s let staking_pot = StakingPot::get(); let foreign_asset_id_location = - xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Rococo)]); + xcm::v4::Location::new(2, [xcm::v4::Junction::GlobalConsensus(xcm::v4::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); + (foreign_asset_owner, foreign_asset_id_location.clone(), foreign_asset_id_minimum_balance); asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< Runtime, @@ -1166,7 +1162,7 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_pool_s // check now foreign asset for staking pot assert_eq!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &staking_pot ), 0 @@ -1180,7 +1176,7 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_pool_s // staking pot receives no foreign assets assert_eq!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &staking_pot ), 0 @@ -1196,12 +1192,12 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_suffic let staking_pot = StakingPot::get(); let foreign_asset_id_location = - xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Rococo)]); + xcm::v4::Location::new(2, [xcm::v4::Junction::GlobalConsensus(xcm::v4::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); + (foreign_asset_owner, foreign_asset_id_location.clone(), foreign_asset_id_minimum_balance); asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< Runtime, @@ -1226,7 +1222,7 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_suffic // check block author before assert_eq!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &block_author_account ), 0 @@ -1236,7 +1232,7 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_suffic // `TakeFirstAssetTrader` puts fees to the block author assert!( ForeignAssets::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &block_author_account ) > 0 ); 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 884b71369e79..67b585ecfe86 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -367,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: @@ -381,11 +381,11 @@ pub fn teleports_for_foreign_assets_works< { // foreign parachain with the same consensus currency as asset let foreign_para_id = 2222; - let foreign_asset_id_location = xcm::v3::Location { + let foreign_asset_id_location = xcm::v4::Location { parents: 1, interior: [ - xcm::v3::Junction::Parachain(foreign_para_id), - xcm::v3::Junction::GeneralIndex(1234567), + xcm::v4::Junction::Parachain(foreign_para_id), + xcm::v4::Junction::GeneralIndex(1234567), ] .into(), }; @@ -438,14 +438,14 @@ pub fn teleports_for_foreign_assets_works< ); assert_eq!( >::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &target_account ), 0.into() ); assert_eq!( >::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &CheckingAccount::get() ), 0.into() @@ -454,14 +454,14 @@ pub fn teleports_for_foreign_assets_works< assert_total::< pallet_assets::Pallet, AccountIdOf, - >(foreign_asset_id_location, 0, 0); + >(foreign_asset_id_location.clone(), 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_location.into(), + foreign_asset_id_location.clone().into(), asset_owner.into(), false, asset_minimum_asset_balance.into() @@ -470,12 +470,9 @@ pub fn teleports_for_foreign_assets_works< assert_total::< pallet_assets::Pallet, AccountIdOf, - >(foreign_asset_id_location, 0, 0); + >(foreign_asset_id_location.clone(), 0, 0); assert!(teleported_foreign_asset_amount > asset_minimum_asset_balance); - 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 @@ -489,12 +486,12 @@ pub fn teleports_for_foreign_assets_works< }, // Process teleported asset ReceiveTeleportedAsset(Assets::from(vec![Asset { - id: AssetId(foreign_asset_id_location_latest.clone()), + id: AssetId(foreign_asset_id_location.clone()), fun: Fungible(teleported_foreign_asset_amount), }])), DepositAsset { assets: Wild(AllOf { - id: AssetId(foreign_asset_id_location_latest.clone()), + id: AssetId(foreign_asset_id_location.clone()), fun: WildFungibility::Fungible, }), beneficiary: Location { @@ -526,7 +523,7 @@ pub fn teleports_for_foreign_assets_works< ); assert_eq!( >::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &target_account ), teleported_foreign_asset_amount.into() @@ -538,7 +535,7 @@ pub fn teleports_for_foreign_assets_works< ); assert_eq!( >::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &CheckingAccount::get() ), 0.into() @@ -548,7 +545,7 @@ pub fn teleports_for_foreign_assets_works< pallet_assets::Pallet, AccountIdOf, >( - foreign_asset_id_location, + foreign_asset_id_location.clone(), teleported_foreign_asset_amount, teleported_foreign_asset_amount, ); @@ -566,7 +563,7 @@ pub fn teleports_for_foreign_assets_works< let target_account_balance_before_teleport = >::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &target_account, ); let asset_to_teleport_away = asset_minimum_asset_balance * 3; @@ -580,7 +577,7 @@ 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::teleport_assets_delivery_fees::( - (foreign_asset_id_location_latest.clone(), asset_to_teleport_away).into(), + (foreign_asset_id_location.clone(), asset_to_teleport_away).into(), 0, Unlimited, dest_beneficiary.clone(), @@ -596,7 +593,7 @@ pub fn teleports_for_foreign_assets_works< RuntimeHelper::::origin_of(target_account.clone()), dest, dest_beneficiary, - (foreign_asset_id_location_latest.clone(), asset_to_teleport_away), + (foreign_asset_id_location.clone(), asset_to_teleport_away), Some((runtime_para_id, foreign_para_id)), included_head, &alice, @@ -606,14 +603,14 @@ pub fn teleports_for_foreign_assets_works< // check balances assert_eq!( >::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &target_account ), (target_account_balance_before_teleport - asset_to_teleport_away.into()) ); assert_eq!( >::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &CheckingAccount::get() ), 0.into() @@ -623,7 +620,7 @@ pub fn teleports_for_foreign_assets_works< pallet_assets::Pallet, AccountIdOf, >( - foreign_asset_id_location, + foreign_asset_id_location.clone(), teleported_foreign_asset_amount - asset_to_teleport_away, teleported_foreign_asset_amount - asset_to_teleport_away, ); @@ -1559,9 +1556,6 @@ pub fn reserve_transfer_native_asset_to_non_teleport_para_works< ) .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()); 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 0b2364dbb8bd..e0b3f70c7546 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 @@ -331,7 +331,7 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< block_author_account: AccountIdOf, (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( AccountIdOf, - xcm::v3::Location, + xcm::v4::Location, u128, ), foreign_asset_id_amount_to_transfer: u128, @@ -357,9 +357,9 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< BalanceOf: From + Into, XcmConfig: xcm_executor::Config, >::AssetId: - From + Into, + From + Into, >::AssetIdParameter: - From + Into, + From + Into, >::Balance: From + Into + From, ::AccountId: Into<<::RuntimeOrigin as OriginTrait>::AccountId> @@ -390,7 +390,7 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< assert_ok!( >::force_create( RuntimeHelper::::root_origin(), - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), foreign_asset_owner.into(), true, // is_sufficient=true foreign_asset_id_minimum_balance.into() @@ -409,7 +409,7 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< // ForeignAssets balances before assert_eq!( >::balance( - foreign_asset_id_location.into(), + foreign_asset_id_location.clone().into(), &target_account ), 0.into() @@ -418,11 +418,8 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< // 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()), + id: AssetId(foreign_asset_id_location.clone()), fun: Fungible(foreign_asset_id_amount_to_transfer), }]); let expected_beneficiary = Location::new( @@ -439,7 +436,7 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< ClearOrigin, BuyExecution { fees: Asset { - id: AssetId(foreign_asset_id_location_latest.clone()), + id: AssetId(foreign_asset_id_location.clone()), fun: Fungible(foreign_asset_id_amount_to_transfer), }, weight_limit: Unlimited, 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 f3e03aa74300..6d0fbd7d5c66 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -3,7 +3,7 @@ name = "bridge-hub-rococo-runtime" version = "0.5.0" authors.workspace = true edition.workspace = true -description = "Rococo's BridgeHub parachain runtime" +description = "Rococo's BridgeHub parachain runtime" license = "Apache-2.0" [lints] @@ -305,4 +305,4 @@ 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"] +on-chain-release-build = [] 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 index 5522a325f192..d0a7ed25363d 100644 --- 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 @@ -17,7 +17,7 @@ //! Autogenerated weights for `pallet_bridge_messages` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-07-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-07-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-7wrmsoux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 @@ -60,8 +60,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `654` // Estimated: `52645` - // Minimum execution time: 37_206_000 picoseconds. - Weight::from_parts(38_545_000, 0) + // Minimum execution time: 36_836_000 picoseconds. + Weight::from_parts(37_858_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,11 +80,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `654` // Estimated: `52645` - // Minimum execution time: 37_075_000 picoseconds. - Weight::from_parts(37_757_000, 0) + // Minimum execution time: 36_587_000 picoseconds. + Weight::from_parts(37_516_000, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 5_776 - .saturating_add(Weight::from_parts(11_586_768, 0).saturating_mul(n.into())) + // Standard Error: 8_655 + .saturating_add(Weight::from_parts(11_649_169, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -100,8 +100,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `654` // Estimated: `52645` - // Minimum execution time: 42_087_000 picoseconds. - Weight::from_parts(42_970_000, 0) + // Minimum execution time: 42_157_000 picoseconds. + Weight::from_parts(43_105_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) @@ -120,11 +120,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `654` // Estimated: `52645` - // Minimum execution time: 35_055_000 picoseconds. - Weight::from_parts(36_987_740, 0) + // Minimum execution time: 35_536_000 picoseconds. + Weight::from_parts(37_452_828, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(2_316, 0).saturating_mul(n.into())) + // Standard Error: 3 + .saturating_add(Weight::from_parts(2_269, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -134,15 +134,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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`) + /// Storage: `BridgePolkadotBulletinMessages::OutboundMessages` (r:0 w:1) + /// Proof: `BridgePolkadotBulletinMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_single_message() -> Weight { // Proof Size summary in bytes: // Measured: `621` // Estimated: `2543` - // Minimum execution time: 24_326_000 picoseconds. - Weight::from_parts(25_169_000, 0) + // Minimum execution time: 25_800_000 picoseconds. + Weight::from_parts(26_666_000, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -150,15 +152,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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`) + /// Storage: `BridgePolkadotBulletinMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgePolkadotBulletinMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { // Proof Size summary in bytes: // Measured: `621` // Estimated: `2543` - // Minimum execution time: 24_484_000 picoseconds. - Weight::from_parts(25_130_000, 0) + // Minimum execution time: 27_262_000 picoseconds. + Weight::from_parts(27_997_000, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -166,15 +170,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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`) + /// Storage: `BridgePolkadotBulletinMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgePolkadotBulletinMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { // Proof Size summary in bytes: // Measured: `621` // Estimated: `2543` - // Minimum execution time: 24_450_000 picoseconds. - Weight::from_parts(25_164_000, 0) + // Minimum execution time: 26_992_000 picoseconds. + Weight::from_parts(27_921_000, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -204,11 +210,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `813` // Estimated: `52645` - // Minimum execution time: 54_317_000 picoseconds. - Weight::from_parts(59_171_547, 0) + // Minimum execution time: 55_509_000 picoseconds. + Weight::from_parts(59_826_763, 0) .saturating_add(Weight::from_parts(0, 52645)) // Standard Error: 7 - .saturating_add(Weight::from_parts(7_566, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(7_565, 0).saturating_mul(n.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_westend.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs index 9c05dae979da..dc6c917c6d00 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs @@ -17,7 +17,7 @@ //! Autogenerated weights for `pallet_bridge_messages` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-07-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-07-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-7wrmsoux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 @@ -62,8 +62,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `658` // Estimated: `52645` - // Minimum execution time: 41_396_000 picoseconds. - Weight::from_parts(43_141_000, 0) + // Minimum execution time: 40_198_000 picoseconds. + Weight::from_parts(42_079_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -84,11 +84,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `658` // Estimated: `52645` - // Minimum execution time: 41_095_000 picoseconds. - Weight::from_parts(42_030_000, 0) + // Minimum execution time: 39_990_000 picoseconds. + Weight::from_parts(41_381_000, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 5_702 - .saturating_add(Weight::from_parts(11_627_951, 0).saturating_mul(n.into())) + // Standard Error: 8_459 + .saturating_add(Weight::from_parts(11_710_167, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -106,8 +106,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `658` // Estimated: `52645` - // Minimum execution time: 45_912_000 picoseconds. - Weight::from_parts(47_564_000, 0) + // Minimum execution time: 45_940_000 picoseconds. + Weight::from_parts(47_753_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -128,11 +128,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `658` // Estimated: `52645` - // Minimum execution time: 39_175_000 picoseconds. - Weight::from_parts(41_674_095, 0) + // Minimum execution time: 39_067_000 picoseconds. + Weight::from_parts(41_787_019, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(2_305, 0).saturating_mul(n.into())) + // Standard Error: 5 + .saturating_add(Weight::from_parts(2_295, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -146,15 +146,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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`) + /// Storage: `BridgeWestendMessages::OutboundMessages` (r:0 w:1) + /// Proof: `BridgeWestendMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_single_message() -> Weight { // Proof Size summary in bytes: // Measured: `501` // Estimated: `3966` - // Minimum execution time: 32_033_000 picoseconds. - Weight::from_parts(33_131_000, 0) + // Minimum execution time: 33_107_000 picoseconds. + Weight::from_parts(34_364_000, 0) .saturating_add(Weight::from_parts(0, 3966)) .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeWestendMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -166,15 +168,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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`) + /// Storage: `BridgeWestendMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgeWestendMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { // Proof Size summary in bytes: // Measured: `501` // Estimated: `3966` - // Minimum execution time: 32_153_000 picoseconds. - Weight::from_parts(33_126_000, 0) + // Minimum execution time: 34_826_000 picoseconds. + Weight::from_parts(35_563_000, 0) .saturating_add(Weight::from_parts(0, 3966)) .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeWestendMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -186,15 +190,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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`) + /// Storage: `BridgeWestendMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgeWestendMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { // Proof Size summary in bytes: // Measured: `501` // Estimated: `6086` - // Minimum execution time: 36_387_000 picoseconds. - Weight::from_parts(37_396_000, 0) + // Minimum execution time: 38_725_000 picoseconds. + Weight::from_parts(39_727_000, 0) .saturating_add(Weight::from_parts(0, 6086)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeWestendMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -224,11 +230,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `789` // Estimated: `52645` - // Minimum execution time: 56_562_000 picoseconds. - Weight::from_parts(61_452_871, 0) + // Minimum execution time: 56_892_000 picoseconds. + Weight::from_parts(61_941_659, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 9 - .saturating_add(Weight::from_parts(7_587, 0).saturating_mul(n.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(7_580, 0).saturating_mul(n.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/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 057dc4313510..f2cee0e3e807 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 @@ -16,10 +16,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-11-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-08-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-696hpswk-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: 19_610_000 picoseconds. - Weight::from_parts(19_980_000, 3593) + // Minimum execution time: 30_988_000 picoseconds. + Weight::from_parts(31_496_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,8 +65,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `153` // Estimated: `6196` - // Minimum execution time: 44_411_000 picoseconds. - Weight::from_parts(45_110_000, 6196) + // Minimum execution time: 42_805_000 picoseconds. + Weight::from_parts(44_207_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -90,8 +90,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `223` // Estimated: `8799` - // Minimum execution time: 89_739_000 picoseconds. - Weight::from_parts(91_256_000, 8799) + // Minimum execution time: 103_376_000 picoseconds. + Weight::from_parts(104_770_000, 8799) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -124,8 +124,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `171` // Estimated: `6196` - // Minimum execution time: 60_045_000 picoseconds. - Weight::from_parts(60_710_000, 6196) + // Minimum execution time: 71_234_000 picoseconds. + Weight::from_parts(72_990_000, 6196) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -133,8 +133,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_257_000 picoseconds. - Weight::from_parts(3_392_000, 0) + // Minimum execution time: 2_636_000 picoseconds. + Weight::from_parts(2_777_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -142,13 +142,11 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `52` // Estimated: `3593` - // Minimum execution time: 19_423_000 picoseconds. - Weight::from_parts(19_823_000, 3593) + // Minimum execution time: 23_839_000 picoseconds. + Weight::from_parts(24_568_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) @@ -159,6 +157,8 @@ impl WeightInfo { // 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) @@ -167,8 +167,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `122` // Estimated: `6196` - // Minimum execution time: 60_484_000 picoseconds. - Weight::from_parts(61_634_000, 6196) + // Minimum execution time: 78_345_000 picoseconds. + Weight::from_parts(80_558_000, 6196) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -192,8 +192,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `70` // Estimated: `3593` - // Minimum execution time: 44_863_000 picoseconds. - Weight::from_parts(45_549_000, 3593) + // Minimum execution time: 46_614_000 picoseconds. + Weight::from_parts(47_354_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-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index a9381501359e..1c9d8c0207b9 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -292,6 +292,6 @@ 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"] +on-chain-release-build = [] fast-runtime = [] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs index 386342d7ea5d..1033387b527e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs @@ -17,7 +17,7 @@ //! Autogenerated weights for `pallet_bridge_messages` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-07-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-07-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-7wrmsoux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 @@ -62,8 +62,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `522` // Estimated: `52645` - // Minimum execution time: 40_748_000 picoseconds. - Weight::from_parts(41_836_000, 0) + // Minimum execution time: 40_289_000 picoseconds. + Weight::from_parts(42_150_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -83,11 +83,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `522` // Estimated: `52645` - // Minimum execution time: 40_923_000 picoseconds. - Weight::from_parts(41_287_000, 0) + // Minimum execution time: 40_572_000 picoseconds. + Weight::from_parts(41_033_000, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 9_774 - .saturating_add(Weight::from_parts(11_469_207, 0).saturating_mul(n.into())) + // Standard Error: 12_000 + .saturating_add(Weight::from_parts(11_710_588, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,8 +105,8 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `522` // Estimated: `52645` - // Minimum execution time: 45_946_000 picoseconds. - Weight::from_parts(47_547_000, 0) + // Minimum execution time: 46_655_000 picoseconds. + Weight::from_parts(49_576_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -126,11 +126,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `522` // Estimated: `52645` - // Minimum execution time: 39_668_000 picoseconds. - Weight::from_parts(41_908_980, 0) + // Minimum execution time: 40_245_000 picoseconds. + Weight::from_parts(43_461_320, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 11 - .saturating_add(Weight::from_parts(2_209, 0).saturating_mul(n.into())) + // Standard Error: 21 + .saturating_add(Weight::from_parts(2_246, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -144,15 +144,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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`) + /// Storage: `BridgeRococoMessages::OutboundMessages` (r:0 w:1) + /// Proof: `BridgeRococoMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_single_message() -> Weight { // Proof Size summary in bytes: // Measured: `357` // Estimated: `3822` - // Minimum execution time: 30_544_000 picoseconds. - Weight::from_parts(31_171_000, 0) + // Minimum execution time: 32_001_000 picoseconds. + Weight::from_parts(32_842_000, 0) .saturating_add(Weight::from_parts(0, 3822)) .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) + .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`) @@ -164,15 +166,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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`) + /// Storage: `BridgeRococoMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgeRococoMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { // Proof Size summary in bytes: // Measured: `357` // Estimated: `3822` - // Minimum execution time: 30_593_000 picoseconds. - Weight::from_parts(31_261_000, 0) + // Minimum execution time: 33_287_000 picoseconds. + Weight::from_parts(33_769_000, 0) .saturating_add(Weight::from_parts(0, 3822)) .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -184,15 +188,17 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// 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`) + /// Storage: `BridgeRococoMessages::OutboundMessages` (r:0 w:2) + /// Proof: `BridgeRococoMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(65568), added: 68043, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { // Proof Size summary in bytes: // Measured: `357` // Estimated: `6086` - // Minimum execution time: 34_682_000 picoseconds. - Weight::from_parts(35_277_000, 0) + // Minimum execution time: 37_136_000 picoseconds. + Weight::from_parts(38_294_000, 0) .saturating_add(Weight::from_parts(0, 6086)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -221,11 +227,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `653` // Estimated: `52645` - // Minimum execution time: 56_465_000 picoseconds. - Weight::from_parts(61_575_775, 0) + // Minimum execution time: 55_942_000 picoseconds. + Weight::from_parts(60_615_769, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 15 - .saturating_add(Weight::from_parts(7_197, 0).saturating_mul(n.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(7_225, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 4310b2456475..5bd1d1680aa1 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/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 @@ -16,11 +16,11 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-vmdtonbz-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain @@ -33,10 +33,10 @@ // --heap-pages=4096 // --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_xcm_benchmarks::fungible -// --chain=bridge-hub-rococo-dev +// --chain=bridge-hub-westend-dev // --header=./cumulus/file_header.txt // --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/ +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,8 +54,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 19_037_000 picoseconds. - Weight::from_parts(19_602_000, 3593) + // Minimum execution time: 30_218_000 picoseconds. + Weight::from_parts(30_783_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,15 +65,13 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `153` // Estimated: `6196` - // Minimum execution time: 43_115_000 picoseconds. - Weight::from_parts(43_897_000, 6196) + // Minimum execution time: 42_631_000 picoseconds. + Weight::from_parts(43_127_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } // Storage: `System::Account` (r:3 w:3) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - // Storage: 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) @@ -90,11 +88,11 @@ 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: `294` + // Measured: `260` // Estimated: `8799` - // Minimum execution time: 90_267_000 picoseconds. - Weight::from_parts(91_460_000, 8799) - .saturating_add(T::DbWeight::get().reads(11)) + // Minimum execution time: 100_978_000 picoseconds. + Weight::from_parts(102_819_000, 8799) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(5)) } // Storage: `Benchmark::Override` (r:0 w:0) @@ -106,8 +104,6 @@ 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) @@ -126,19 +122,19 @@ 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: `242` + // Measured: `208` // Estimated: `6196` - // Minimum execution time: 60_477_000 picoseconds. - Weight::from_parts(61_314_000, 6196) - .saturating_add(T::DbWeight::get().reads(10)) + // Minimum execution time: 71_533_000 picoseconds. + Weight::from_parts(72_922_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: 2_996_000 picoseconds. - Weight::from_parts(3_107_000, 0) + // Minimum execution time: 2_863_000 picoseconds. + Weight::from_parts(2_997_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -146,15 +142,11 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `52` // Estimated: `3593` - // Minimum execution time: 18_907_000 picoseconds. - Weight::from_parts(19_475_000, 3593) + // Minimum execution time: 23_763_000 picoseconds. + Weight::from_parts(24_438_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - // Storage: 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) @@ -165,21 +157,21 @@ impl WeightInfo { // 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 deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `193` + // Measured: `159` // Estimated: `6196` - // Minimum execution time: 59_143_000 picoseconds. - Weight::from_parts(60_316_000, 6196) - .saturating_add(T::DbWeight::get().reads(10)) + // Minimum execution time: 78_182_000 picoseconds. + Weight::from_parts(79_575_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) .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) @@ -198,11 +190,11 @@ 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: `141` - // Estimated: `3606` - // Minimum execution time: 44_459_000 picoseconds. - Weight::from_parts(45_365_000, 3606) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `107` + // Estimated: `3593` + // Minimum execution time: 46_767_000 picoseconds. + Weight::from_parts(47_823_000, 3593) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 43fc9083937c..e98508ea02e6 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -243,4 +243,4 @@ std = [ # 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"] +on-chain-release-build = [] diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs index ae4fe9e84337..08b1d192b0be 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -48,6 +48,7 @@ use xcm_builder::{ use xcm_executor::XcmExecutor; parameter_types! { + pub const RootLocation: Location = Location::here(); pub const WndLocation: Location = Location::parent(); pub const RelayNetwork: Option = Some(NetworkId::Westend); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); @@ -139,6 +140,13 @@ impl Contains for ParentOrParentsPlurality { } } +pub struct LocalPlurality; +impl Contains for LocalPlurality { + fn contains(loc: &Location) -> bool { + matches!(loc.unpack(), (0, [Plurality { .. }])) + } +} + pub type Barrier = TrailingSetTopicAsId< DenyThenTry< DenyReserveTransferToRelayChain, @@ -173,6 +181,8 @@ pub type Barrier = TrailingSetTopicAsId< pub type WaivedLocations = ( RelayOrOtherSystemParachains, Equals, + Equals, + LocalPlurality, ); /// Cases where a remote origin is accepted as trusted Teleporter for a given asset: diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index 1fcebb3f16a9..dfa75b8d3cf3 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -203,4 +203,4 @@ 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"] +on-chain-release-build = [] diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index 2920bc428d90..07d133c80be7 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -209,4 +209,4 @@ metadata-hash = ["substrate-wasm-builder/metadata-hash"] # 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 = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 73a719805307..c8dbdadf7b15 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-05-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-08-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-unxyhko3-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-696hpswk-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: @@ -54,8 +54,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 26_642_000 picoseconds. - Weight::from_parts(27_583_000, 3593) + // Minimum execution time: 29_812_000 picoseconds. + Weight::from_parts(30_526_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,8 +65,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 35_124_000 picoseconds. - Weight::from_parts(36_510_000, 6196) + // Minimum execution time: 39_430_000 picoseconds. + Weight::from_parts(39_968_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -88,8 +88,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `207` // Estimated: `6196` - // Minimum execution time: 55_950_000 picoseconds. - Weight::from_parts(57_207_000, 6196) + // Minimum execution time: 65_555_000 picoseconds. + Weight::from_parts(67_161_000, 6196) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -118,8 +118,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 23_747_000 picoseconds. - Weight::from_parts(24_424_000, 3571) + // Minimum execution time: 30_491_000 picoseconds. + Weight::from_parts(31_991_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -127,8 +127,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_853_000 picoseconds. - Weight::from_parts(1_998_000, 0) + // Minimum execution time: 2_568_000 picoseconds. + Weight::from_parts(2_703_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 +136,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 19_164_000 picoseconds. - Weight::from_parts(19_643_000, 3593) + // Minimum execution time: 22_159_000 picoseconds. + Weight::from_parts(22_517_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -159,8 +159,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3593` - // Minimum execution time: 48_708_000 picoseconds. - Weight::from_parts(49_610_000, 3593) + // Minimum execution time: 57_126_000 picoseconds. + Weight::from_parts(58_830_000, 3593) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -180,8 +180,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 20_586_000 picoseconds. - Weight::from_parts(21_147_000, 3571) + // Minimum execution time: 26_589_000 picoseconds. + Weight::from_parts(27_285_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index 07a4332800d7..5029c82f971d 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -206,4 +206,4 @@ metadata-hash = ["substrate-wasm-builder/metadata-hash"] # 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 = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index ddfc599fa579..935636651eb9 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-05-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-08-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-unxyhko3-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-696hpswk-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: @@ -54,8 +54,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 26_842_000 picoseconds. - Weight::from_parts(27_606_000, 3593) + // Minimum execution time: 29_866_000 picoseconds. + Weight::from_parts(30_363_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,8 +65,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 35_076_000 picoseconds. - Weight::from_parts(36_109_000, 6196) + // Minimum execution time: 39_434_000 picoseconds. + Weight::from_parts(40_274_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -88,8 +88,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `207` // Estimated: `6196` - // Minimum execution time: 56_951_000 picoseconds. - Weight::from_parts(58_286_000, 6196) + // Minimum execution time: 66_303_000 picoseconds. + Weight::from_parts(68_294_000, 6196) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -118,8 +118,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 23_796_000 picoseconds. - Weight::from_parts(24_692_000, 3571) + // Minimum execution time: 30_523_000 picoseconds. + Weight::from_parts(31_289_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -127,8 +127,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_990_000 picoseconds. - Weight::from_parts(2_142_000, 0) + // Minimum execution time: 2_517_000 picoseconds. + Weight::from_parts(2_634_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 +136,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 19_572_000 picoseconds. - Weight::from_parts(20_017_000, 3593) + // Minimum execution time: 22_151_000 picoseconds. + Weight::from_parts(22_907_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -159,8 +159,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3593` - // Minimum execution time: 49_336_000 picoseconds. - Weight::from_parts(50_507_000, 3593) + // Minimum execution time: 57_763_000 picoseconds. + Weight::from_parts(58_941_000, 3593) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -180,8 +180,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 21_230_000 picoseconds. - Weight::from_parts(21_870_000, 3571) + // Minimum execution time: 26_322_000 picoseconds. + Weight::from_parts(27_197_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml index d20b62a557b9..09b4ef679d24 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml @@ -136,4 +136,4 @@ 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"] +on-chain-release-build = [] diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index a732bec2352d..c676587b1de4 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -191,3 +191,8 @@ 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 = [] diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs index 11c1bad9aa17..58007173ae1d 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs @@ -5,7 +5,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -60,10 +60,8 @@ impl XcmWeightInfo for PeopleRococoXcmWeight { 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 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()) @@ -114,12 +112,8 @@ impl XcmWeightInfo for PeopleRococoXcmWeight { 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) + assets.weigh_assets(XcmFungibleWeight::::deposit_asset()) } fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) @@ -132,7 +126,7 @@ impl XcmWeightInfo for PeopleRococoXcmWeight { _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_assets(XcmGeneric::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) } fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::initiate_teleport()) 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 index 2364798596d5..4dd44e66dd5e 100644 --- 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 @@ -1,41 +1,42 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-kusama-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-696hpswk-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: -// ./artifacts/polkadot-parachain +// target/production/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 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::fungible +// --chain=people-rococo-dev +// --header=./cumulus/file_header.txt +// --template=./cumulus/templates/xcm-bench-template.hbs +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,110 +48,140 @@ use core::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) + // 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) + // Minimum execution time: 30_428_000 picoseconds. + Weight::from_parts(31_184_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: System Account (r:2 w:2) - // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: `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) + // Minimum execution time: 41_912_000 picoseconds. + Weight::from_parts(43_346_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) + // 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: 71_204_000 picoseconds. - Weight::from_parts(72_121_000, 6196) + // Minimum execution time: 67_706_000 picoseconds. + Weight::from_parts(69_671_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_790_000 picoseconds. + Weight::from_parts(30_655_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_559_000 picoseconds. - Weight::from_parts(3_616_000, 0) + // Minimum execution time: 2_438_000 picoseconds. + Weight::from_parts(2_597_000, 0) } - // 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`) 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) + // Minimum execution time: 24_040_000 picoseconds. + Weight::from_parts(24_538_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) + // 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: `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: 49_030_000 picoseconds. - Weight::from_parts(49_828_000, 3593) + // Minimum execution time: 58_275_000 picoseconds. + Weight::from_parts(59_899_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) + // 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: 27_142_000 picoseconds. - Weight::from_parts(27_416_000, 3535) + // Minimum execution time: 25_638_000 picoseconds. + Weight::from_parts(26_514_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 index a50c8860c48f..729a32117041 100644 --- 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 @@ -1,41 +1,42 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-09, 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 +//! HOSTNAME: `runner-696hpswk-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: -// ./artifacts/polkadot-parachain +// target/production/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 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::generic +// --chain=people-rococo-dev +// --header=./cumulus/file_header.txt +// --template=./cumulus/templates/xcm-bench-template.hbs +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,24 +48,24 @@ use core::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) + // 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: `70` // Estimated: `3535` - // Minimum execution time: 30_210_000 picoseconds. - Weight::from_parts(30_864_000, 3535) + // Minimum execution time: 29_430_000 picoseconds. + Weight::from_parts(30_111_000, 3535) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -72,97 +73,97 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_808_000 picoseconds. - Weight::from_parts(2_848_000, 0) + // Minimum execution time: 607_000 picoseconds. + Weight::from_parts(672_000, 0) } - // Storage: PolkadotXcm Queries (r:1 w:0) - // Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + // 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: `32` // Estimated: `3497` - // Minimum execution time: 10_353_000 picoseconds. - Weight::from_parts(10_569_000, 3497) + // Minimum execution time: 7_445_000 picoseconds. + Weight::from_parts(7_623_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) + // Minimum execution time: 6_749_000 picoseconds. + Weight::from_parts(7_073_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) + // Minimum execution time: 1_275_000 picoseconds. + Weight::from_parts(1_409_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) + // Minimum execution time: 670_000 picoseconds. + Weight::from_parts(709_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) + // Minimum execution time: 635_000 picoseconds. + Weight::from_parts(723_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) + // Minimum execution time: 650_000 picoseconds. + Weight::from_parts(699_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) + // Minimum execution time: 678_000 picoseconds. + Weight::from_parts(728_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) + // Minimum execution time: 657_000 picoseconds. + Weight::from_parts(703_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) + // 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: `70` // Estimated: `3535` - // Minimum execution time: 24_737_000 picoseconds. - Weight::from_parts(25_106_000, 3535) + // Minimum execution time: 25_795_000 picoseconds. + Weight::from_parts(26_415_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) + // 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: `90` // Estimated: `3555` - // Minimum execution time: 14_712_000 picoseconds. - Weight::from_parts(14_976_000, 3555) + // Minimum execution time: 10_792_000 picoseconds. + Weight::from_parts(11_061_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -170,114 +171,93 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_689_000 picoseconds. - Weight::from_parts(2_739_000, 0) + // Minimum execution time: 624_000 picoseconds. + Weight::from_parts(682_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) + // 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: `38` // Estimated: `3503` - // Minimum execution time: 26_478_000 picoseconds. - Weight::from_parts(26_695_000, 3503) + // Minimum execution time: 23_906_000 picoseconds. + Weight::from_parts(24_740_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) + // 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_811_000 picoseconds. - Weight::from_parts(5_062_000, 0) + // Minimum execution time: 2_621_000 picoseconds. + Weight::from_parts(2_788_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) + // Minimum execution time: 954_000 picoseconds. + Weight::from_parts(1_046_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) + // Minimum execution time: 742_000 picoseconds. + Weight::from_parts(790_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) + // Minimum execution time: 664_000 picoseconds. + Weight::from_parts(722_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) + // Minimum execution time: 619_000 picoseconds. + Weight::from_parts(672_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) + // Minimum execution time: 798_000 picoseconds. + Weight::from_parts(851_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) + // 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: `70` // Estimated: `3535` - // Minimum execution time: 27_235_000 picoseconds. - Weight::from_parts(27_811_000, 3535) + // Minimum execution time: 29_580_000 picoseconds. + Weight::from_parts(31_100_000, 3535) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -285,27 +265,27 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_807_000 picoseconds. - Weight::from_parts(4_918_000, 0) + // Minimum execution time: 3_150_000 picoseconds. + Weight::from_parts(3_326_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) + // 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: `70` // Estimated: `3535` - // Minimum execution time: 24_698_000 picoseconds. - Weight::from_parts(25_077_000, 3535) + // Minimum execution time: 26_152_000 picoseconds. + Weight::from_parts(26_635_000, 3535) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -313,35 +293,35 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_613_000 picoseconds. - Weight::from_parts(2_703_000, 0) + // Minimum execution time: 693_000 picoseconds. + Weight::from_parts(724_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) + // Minimum execution time: 632_000 picoseconds. + Weight::from_parts(678_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) + // Minimum execution time: 646_000 picoseconds. + Weight::from_parts(694_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) + // Minimum execution time: 622_000 picoseconds. + Weight::from_parts(656_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) + // Minimum execution time: 639_000 picoseconds. + Weight::from_parts(679_000, 0) } } diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index 20c7e691ebc8..ab7dd04bb78a 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -191,3 +191,8 @@ 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 = [] 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 index b1fc7ad8ed83..b44e8d4b61b8 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs @@ -5,7 +5,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -60,10 +60,8 @@ 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 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()) @@ -114,12 +112,8 @@ impl XcmWeightInfo for PeopleWestendXcmWeight { 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) + assets.weigh_assets(XcmFungibleWeight::::deposit_asset()) } fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) @@ -132,13 +126,10 @@ impl XcmWeightInfo for PeopleWestendXcmWeight { _reserve: &Location, _xcm: &Xcm<()>, ) -> Weight { - assets.weigh_assets(XcmGeneric::::initiate_reserve_withdraw()) + assets.weigh_assets(XcmFungibleWeight::::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) + assets.weigh_assets(XcmFungibleWeight::::initiate_teleport()) } fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { XcmGeneric::::report_holding() 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 index 92d08a246180..8f6bfde986bb 100644 --- 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 @@ -1,41 +1,42 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-polkadot-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-696hpswk-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: -// ./artifacts/polkadot-parachain +// target/production/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 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::fungible +// --chain=people-westend-dev +// --header=./cumulus/file_header.txt +// --template=./cumulus/templates/xcm-bench-template.hbs +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,110 +48,140 @@ use core::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) + // 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) + // Minimum execution time: 30_040_000 picoseconds. + Weight::from_parts(30_758_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: System Account (r:2 w:2) - // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + // Storage: `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) + // Minimum execution time: 42_135_000 picoseconds. + Weight::from_parts(42_970_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) + // 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: 74_134_000 picoseconds. - Weight::from_parts(74_719_000, 6196) + // Minimum execution time: 67_385_000 picoseconds. + Weight::from_parts(69_776_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_804_000 picoseconds. + Weight::from_parts(30_662_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_726_000 picoseconds. - Weight::from_parts(3_881_000, 0) + // Minimum execution time: 2_358_000 picoseconds. + Weight::from_parts(2_497_000, 0) } - // 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`) 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) + // Minimum execution time: 23_732_000 picoseconds. + Weight::from_parts(24_098_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) + // 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: `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: 51_084_000 picoseconds. - Weight::from_parts(51_859_000, 3593) + // Minimum execution time: 58_449_000 picoseconds. + Weight::from_parts(60_235_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) + // 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: 28_038_000 picoseconds. - Weight::from_parts(28_438_000, 3535) + // Minimum execution time: 25_708_000 picoseconds. + Weight::from_parts(26_495_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 index 861f03819959..1377d31f2db7 100644 --- 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 @@ -1,41 +1,42 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-09, 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 +//! HOSTNAME: `runner-696hpswk-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: -// ./artifacts/polkadot-parachain +// target/production/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 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::generic +// --chain=people-westend-dev +// --header=./cumulus/file_header.txt +// --template=./cumulus/templates/xcm-bench-template.hbs +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,24 +48,24 @@ use core::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) + // 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: `70` // Estimated: `3535` - // Minimum execution time: 30_819_000 picoseconds. - Weight::from_parts(31_157_000, 3535) + // Minimum execution time: 29_537_000 picoseconds. + Weight::from_parts(30_513_000, 3535) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -72,97 +73,97 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_869_000 picoseconds. - Weight::from_parts(2_920_000, 0) + // Minimum execution time: 683_000 picoseconds. + Weight::from_parts(738_000, 0) } - // Storage: PolkadotXcm Queries (r:1 w:0) - // Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + // 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: `32` // Estimated: `3497` - // Minimum execution time: 10_268_000 picoseconds. - Weight::from_parts(10_496_000, 3497) + // Minimum execution time: 7_498_000 picoseconds. + Weight::from_parts(7_904_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) + // Minimum execution time: 7_029_000 picoseconds. + Weight::from_parts(7_325_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) + // Minimum execution time: 1_343_000 picoseconds. + Weight::from_parts(1_410_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) + // Minimum execution time: 696_000 picoseconds. + Weight::from_parts(734_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) + // Minimum execution time: 690_000 picoseconds. + Weight::from_parts(740_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) + // Minimum execution time: 667_000 picoseconds. + Weight::from_parts(697_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) + // Minimum execution time: 692_000 picoseconds. + Weight::from_parts(743_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) + // Minimum execution time: 670_000 picoseconds. + Weight::from_parts(712_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) + // 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: `70` // Estimated: `3535` - // Minimum execution time: 24_446_000 picoseconds. - Weight::from_parts(24_854_000, 3535) + // Minimum execution time: 26_405_000 picoseconds. + Weight::from_parts(26_877_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) + // 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: `90` // Estimated: `3555` - // Minimum execution time: 14_713_000 picoseconds. - Weight::from_parts(15_010_000, 3555) + // Minimum execution time: 10_953_000 picoseconds. + Weight::from_parts(11_345_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -170,114 +171,93 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_702_000 picoseconds. - Weight::from_parts(2_744_000, 0) + // Minimum execution time: 644_000 picoseconds. + Weight::from_parts(693_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) + // 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: `38` // Estimated: `3503` - // Minimum execution time: 25_955_000 picoseconds. - Weight::from_parts(26_632_000, 3503) + // Minimum execution time: 24_157_000 picoseconds. + Weight::from_parts(24_980_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) + // 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_965_000 picoseconds. - Weight::from_parts(5_168_000, 0) + // Minimum execution time: 2_767_000 picoseconds. + Weight::from_parts(2_844_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) + // Minimum execution time: 1_079_000 picoseconds. + Weight::from_parts(1_141_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) + // Minimum execution time: 776_000 picoseconds. + Weight::from_parts(829_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) + // Minimum execution time: 696_000 picoseconds. + Weight::from_parts(740_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) + // Minimum execution time: 655_000 picoseconds. + Weight::from_parts(684_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) + // Minimum execution time: 825_000 picoseconds. + Weight::from_parts(853_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) + // 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: `70` // Estimated: `3535` - // Minimum execution time: 28_600_000 picoseconds. - Weight::from_parts(29_001_000, 3535) + // Minimum execution time: 30_222_000 picoseconds. + Weight::from_parts(31_110_000, 3535) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -285,27 +265,27 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_748_000 picoseconds. - Weight::from_parts(4_813_000, 0) + // Minimum execution time: 3_108_000 picoseconds. + Weight::from_parts(3_325_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) + // 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: `70` // Estimated: `3535` - // Minimum execution time: 25_483_000 picoseconds. - Weight::from_parts(25_737_000, 3535) + // Minimum execution time: 26_548_000 picoseconds. + Weight::from_parts(26_911_000, 3535) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -313,35 +293,35 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_755_000 picoseconds. - Weight::from_parts(2_817_000, 0) + // Minimum execution time: 684_000 picoseconds. + Weight::from_parts(726_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) + // Minimum execution time: 649_000 picoseconds. + Weight::from_parts(700_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) + // Minimum execution time: 650_000 picoseconds. + Weight::from_parts(686_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) + // Minimum execution time: 652_000 picoseconds. + Weight::from_parts(703_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) + // Minimum execution time: 673_000 picoseconds. + Weight::from_parts(742_000, 0) } } diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml index a0ad248bb704..9c905c876277 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml @@ -134,3 +134,8 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", ] + +# 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 = [] diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 5d6548447463..383e0f158bf4 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -2,9 +2,9 @@ name = "polkadot-parachain-bin" version = "4.0.0" authors.workspace = true -build = "build.rs" edition.workspace = true -description = "Runs a polkadot parachain node which could be a collator." +build = "build.rs" +description = "Runs a polkadot parachain node" license = "Apache-2.0" [lints] @@ -15,19 +15,14 @@ name = "polkadot-parachain" path = "src/main.rs" [dependencies] -async-trait = { workspace = true } -clap = { features = ["derive"], workspace = true } -codec = { workspace = true, default-features = true } color-eyre = { workspace = true } -color-print = { workspace = true } -futures = { workspace = true } hex-literal = { workspace = true, default-features = true } log = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -docify = { workspace = true } # Local +polkadot-parachain-lib = { features = ["rococo-native", "westend-native"], workspace = true } rococo-parachain-runtime = { workspace = true } shell-runtime = { workspace = true } glutton-westend-runtime = { workspace = true } @@ -41,7 +36,6 @@ coretime-rococo-runtime = { workspace = true } coretime-westend-runtime = { workspace = true } bridge-hub-westend-runtime = { workspace = true, default-features = true } penpal-runtime = { workspace = true } -jsonrpsee = { features = ["server"], workspace = true } people-rococo-runtime = { workspace = true } people-westend-runtime = { workspace = true } parachains-common = { workspace = true, default-features = true } @@ -51,83 +45,32 @@ testnet-parachains-constants = { features = [ ], workspace = true } # Substrate -frame-benchmarking = { workspace = true, default-features = true } -frame-benchmarking-cli = { workspace = true, default-features = true } sp-runtime = { workspace = true } -sp-io = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-session = { workspace = true, default-features = true } -frame-try-runtime = { optional = true, workspace = true, default-features = true } -sc-consensus = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-executor = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sc-telemetry = { workspace = true, default-features = true } -sc-transaction-pool = { workspace = true, default-features = true } -sp-transaction-pool = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -sc-network-sync = { workspace = true, default-features = true } -sc-basic-authorship = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-genesis-builder = { workspace = true } -sp-block-builder = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } -sc-rpc = { workspace = true, default-features = true } -sp-version = { workspace = true, default-features = true } -sc-tracing = { workspace = true, default-features = true } -sp-offchain = { workspace = true, default-features = true } -frame-system-rpc-runtime-api = { workspace = true, default-features = true } -pallet-transaction-payment = { workspace = true, default-features = true } -pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } -sp-inherents = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sp-consensus-aura = { workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -substrate-frame-rpc-system = { workspace = true, default-features = true } -pallet-transaction-payment-rpc = { workspace = true, default-features = true } -substrate-state-trie-migration-rpc = { workspace = true, default-features = true } # Polkadot -# Use rococo-native as this is currently the default "local" relay chain -polkadot-cli = { features = ["rococo-native", "westend-native"], workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-service = { workspace = true, default-features = true } xcm = { workspace = true, default-features = true } # Cumulus -cumulus-client-cli = { workspace = true, default-features = true } -cumulus-client-collator = { workspace = true, default-features = true } -cumulus-client-consensus-aura = { workspace = true, default-features = true } -cumulus-client-consensus-relay-chain = { workspace = true, default-features = true } -cumulus-client-consensus-common = { workspace = true, default-features = true } -cumulus-client-consensus-proposer = { workspace = true, default-features = true } -cumulus-client-parachain-inherent = { workspace = true, default-features = true } -cumulus-client-service = { workspace = true, default-features = true } -cumulus-primitives-aura = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } -cumulus-relay-chain-interface = { workspace = true, default-features = true } [build-dependencies] substrate-build-script-utils = { workspace = true, default-features = true } -[dev-dependencies] -assert_cmd = { workspace = true } -nix = { features = ["signal"], workspace = true } -tempfile = { workspace = true } -tokio = { version = "1.32.0", features = ["macros", "parking_lot", "time"] } -wait-timeout = { workspace = true } - [features] default = [] runtime-benchmarks = [ + "cumulus-primitives-core/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-parachain-lib/runtime-benchmarks", + "polkadot-service/runtime-benchmarks", + "sc-service/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "asset-hub-rococo-runtime/runtime-benchmarks", "asset-hub-westend-runtime/runtime-benchmarks", "bridge-hub-rococo-runtime/runtime-benchmarks", @@ -136,23 +79,17 @@ 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", - "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", "rococo-parachain-runtime/runtime-benchmarks", - "sc-service/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", ] try-runtime = [ + "polkadot-parachain-lib/try-runtime", + "polkadot-service/try-runtime", + "sp-runtime/try-runtime", + "asset-hub-rococo-runtime/try-runtime", "asset-hub-westend-runtime/try-runtime", "bridge-hub-rococo-runtime/try-runtime", @@ -161,17 +98,11 @@ try-runtime = [ "contracts-rococo-runtime/try-runtime", "coretime-rococo-runtime/try-runtime", "coretime-westend-runtime/try-runtime", - "frame-support/try-runtime", - "frame-try-runtime/try-runtime", "glutton-westend-runtime/try-runtime", - "pallet-transaction-payment/try-runtime", "penpal-runtime/try-runtime", "people-rococo-runtime/try-runtime", "people-westend-runtime/try-runtime", - "polkadot-cli/try-runtime", - "polkadot-service/try-runtime", "shell-runtime/try-runtime", - "sp-runtime/try-runtime", ] fast-runtime = [ "bridge-hub-rococo-runtime/fast-runtime", diff --git a/cumulus/polkadot-parachain/chain-specs/contracts-rococo.json b/cumulus/polkadot-parachain/chain-specs/contracts-rococo.json deleted file mode 120000 index b9f8e8f31e84..000000000000 --- a/cumulus/polkadot-parachain/chain-specs/contracts-rococo.json +++ /dev/null @@ -1 +0,0 @@ -../../parachains/chain-specs/contracts-rococo.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/chain-specs/coretime-polkadot.json b/cumulus/polkadot-parachain/chain-specs/coretime-polkadot.json new file mode 120000 index 000000000000..f6f2bc686917 --- /dev/null +++ b/cumulus/polkadot-parachain/chain-specs/coretime-polkadot.json @@ -0,0 +1 @@ +../../parachains/chain-specs/coretime-polkadot.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml b/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml new file mode 100644 index 000000000000..066cbfae53ae --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml @@ -0,0 +1,119 @@ +[package] +name = "polkadot-parachain-lib" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +description = "Helper library that can be used to build a parachain node" +license = "Apache-2.0" + +[lints] +workspace = true + +[lib] +path = "src/lib.rs" + +[dependencies] +async-trait = { workspace = true } +clap = { features = ["derive"], workspace = true } +codec = { workspace = true, default-features = true } +color-print = { workspace = true } +futures = { workspace = true } +log = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +docify = { workspace = true } + +# Local +jsonrpsee = { features = ["server"], workspace = true } +parachains-common = { workspace = true, default-features = true } + +# Substrate +frame-benchmarking = { optional = true, workspace = true, default-features = true } +frame-benchmarking-cli = { workspace = true, default-features = true } +sp-runtime = { workspace = true } +sp-core = { workspace = true, default-features = true } +sp-session = { workspace = true, default-features = true } +frame-try-runtime = { optional = true, workspace = true, default-features = true } +sc-consensus = { workspace = true, default-features = true } +frame-support = { optional = true, workspace = true, default-features = true } +sc-cli = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-client-db = { workspace = true, default-features = true } +sc-executor = { workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } +sc-telemetry = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } +sp-transaction-pool = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-basic-authorship = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } +sp-genesis-builder = { workspace = true } +sp-block-builder = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sc-chain-spec = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } +sp-weights = { workspace = true, default-features = true } +sc-tracing = { workspace = true, default-features = true } +frame-system-rpc-runtime-api = { workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } +sp-consensus-aura = { workspace = true, default-features = true } +sc-sysinfo = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +substrate-frame-rpc-system = { workspace = true, default-features = true } +pallet-transaction-payment-rpc = { workspace = true, default-features = true } +substrate-state-trie-migration-rpc = { workspace = true, default-features = true } + +# Polkadot +polkadot-cli = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } + +# Cumulus +cumulus-client-cli = { workspace = true, default-features = true } +cumulus-client-collator = { workspace = true, default-features = true } +cumulus-client-consensus-aura = { workspace = true, default-features = true } +cumulus-client-consensus-relay-chain = { workspace = true, default-features = true } +cumulus-client-consensus-common = { workspace = true, default-features = true } +cumulus-client-consensus-proposer = { workspace = true, default-features = true } +cumulus-client-parachain-inherent = { workspace = true, default-features = true } +cumulus-client-service = { workspace = true, default-features = true } +cumulus-primitives-aura = { workspace = true, default-features = true } +cumulus-primitives-core = { workspace = true, default-features = true } +cumulus-relay-chain-interface = { workspace = true, default-features = true } + +[dev-dependencies] +assert_cmd = { workspace = true } +nix = { features = ["signal"], workspace = true } +tokio = { version = "1.32.0", features = ["macros", "parking_lot", "time"] } +wait-timeout = { workspace = true } + +[features] +default = [] +rococo-native = [ + "polkadot-cli/rococo-native", +] +westend-native = [ + "polkadot-cli/westend-native", +] +runtime-benchmarks = [ + "cumulus-primitives-core/runtime-benchmarks", + "frame-benchmarking-cli/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-cli/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", + "sc-client-db/runtime-benchmarks", + "sc-service/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-transaction-payment/try-runtime", + "polkadot-cli/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs new file mode 100644 index 000000000000..15d21235d1a1 --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs @@ -0,0 +1,396 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::{ + chain_spec::DiskChainSpecLoader, + common::{ + chain_spec::{Extensions, LoadSpec}, + NodeExtraArgs, + }, +}; +use clap::{Command, CommandFactory, FromArgMatches}; +use sc_chain_spec::ChainSpec; +use sc_cli::{ + CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, NetworkParams, + RpcEndpoint, SharedParams, SubstrateCli, +}; +use sc_service::{config::PrometheusConfig, BasePath}; +use std::{fmt::Debug, marker::PhantomData, path::PathBuf}; + +/// Trait that can be used to customize some of the customer-facing info related to the node binary +/// that is being built using this library. +/// +/// The related info is shown to the customer as part of logs or help messages. +/// It does not impact functionality. +pub trait CliConfig { + /// The version of the resulting node binary. + fn impl_version() -> String; + + /// The description of the resulting node binary. + fn description(executable_name: String) -> String { + format!( + "The command-line arguments provided first will be passed to the parachain node, \n\ + and the arguments provided after -- will be passed to the relay chain node. \n\ + \n\ + Example: \n\ + \n\ + {} [parachain-args] -- [relay-chain-args]", + executable_name + ) + } + + /// The author of the resulting node binary. + fn author() -> String; + + /// The support URL for the resulting node binary. + fn support_url() -> String; + + /// The starting copyright year of the resulting node binary. + fn copyright_start_year() -> u16; +} + +/// Sub-commands supported by the collator. +#[derive(Debug, clap::Subcommand)] +pub enum Subcommand { + /// Key management CLI utilities + #[command(subcommand)] + Key(sc_cli::KeySubcommand), + + /// Build a chain specification. + BuildSpec(sc_cli::BuildSpecCmd), + + /// Validate blocks. + CheckBlock(sc_cli::CheckBlockCmd), + + /// Export blocks. + ExportBlocks(sc_cli::ExportBlocksCmd), + + /// Export the state of a given block into a chain spec. + ExportState(sc_cli::ExportStateCmd), + + /// Import blocks. + ImportBlocks(sc_cli::ImportBlocksCmd), + + /// Revert the chain to a previous state. + Revert(sc_cli::RevertCmd), + + /// Remove the whole chain. + PurgeChain(cumulus_client_cli::PurgeChainCmd), + + /// Export the genesis state of the parachain. + #[command(alias = "export-genesis-state")] + ExportGenesisHead(cumulus_client_cli::ExportGenesisHeadCommand), + + /// Export the genesis wasm of the parachain. + ExportGenesisWasm(cumulus_client_cli::ExportGenesisWasmCommand), + + /// Sub-commands concerned with benchmarking. + /// The pallet benchmarking moved to the `pallet` sub-command. + #[command(subcommand)] + Benchmark(frame_benchmarking_cli::BenchmarkCmd), +} + +#[derive(clap::Parser)] +#[command( + propagate_version = true, + args_conflicts_with_subcommands = true, + subcommand_negates_reqs = true +)] +pub struct Cli { + #[arg(skip)] + pub(crate) chain_spec_loader: Option>, + + #[command(subcommand)] + pub subcommand: Option, + + #[command(flatten)] + pub run: cumulus_client_cli::RunCmd, + + /// EXPERIMENTAL: Use slot-based collator which can handle elastic scaling. + /// + /// Use with care, this flag is unstable and subject to change. + #[arg(long)] + pub experimental_use_slot_based: bool, + + /// Disable automatic hardware benchmarks. + /// + /// By default these benchmarks are automatically ran at startup and measure + /// the CPU speed, the memory bandwidth and the disk speed. + /// + /// The results are then printed out in the logs, and also sent as part of + /// telemetry, if telemetry is enabled. + #[arg(long)] + pub no_hardware_benchmarks: bool, + + /// Export all `PoVs` build by this collator to the given folder. + /// + /// This is useful for debugging issues that are occurring while validating these `PoVs` on the + /// relay chain. + #[arg(long)] + pub export_pov_to_path: Option, + + /// Relay chain arguments + #[arg(raw = true)] + pub relay_chain_args: Vec, + + #[arg(skip)] + pub(crate) _phantom: PhantomData, +} + +impl Cli { + pub(crate) fn node_extra_args(&self) -> NodeExtraArgs { + NodeExtraArgs { + use_slot_based_consensus: self.experimental_use_slot_based, + export_pov: self.export_pov_to_path.clone(), + } + } +} + +impl SubstrateCli for Cli { + fn impl_name() -> String { + Self::executable_name() + } + + fn impl_version() -> String { + Config::impl_version() + } + + fn description() -> String { + Config::description(Self::executable_name()) + } + + fn author() -> String { + Config::author() + } + + fn support_url() -> String { + Config::support_url() + } + + fn copyright_start_year() -> i32 { + Config::copyright_start_year() as i32 + } + + fn load_spec(&self, id: &str) -> Result, String> { + match &self.chain_spec_loader { + Some(chain_spec_loader) => chain_spec_loader.load_spec(id), + None => DiskChainSpecLoader.load_spec(id), + } + } +} + +#[derive(Debug)] +pub struct RelayChainCli { + /// The actual relay chain cli object. + pub base: polkadot_cli::RunCmd, + + /// Optional chain id that should be passed to the relay chain. + pub chain_id: Option, + + /// The base path that should be used by the relay chain. + pub base_path: Option, + + _phantom: PhantomData, +} + +impl RelayChainCli { + fn polkadot_cmd() -> Command { + let help_template = color_print::cformat!( + "The arguments that are passed to the relay chain node. \n\ + \n\ + RELAY_CHAIN_ARGS: \n\ + {{options}}", + ); + + polkadot_cli::RunCmd::command() + .no_binary_name(true) + .help_template(help_template) + } + + /// Parse the relay chain CLI parameters using the parachain `Configuration`. + pub fn new<'a>( + para_config: &sc_service::Configuration, + relay_chain_args: impl Iterator, + ) -> Self { + let polkadot_cmd = Self::polkadot_cmd(); + let matches = polkadot_cmd.get_matches_from(relay_chain_args); + let base = FromArgMatches::from_arg_matches(&matches).unwrap_or_else(|e| e.exit()); + + let extension = Extensions::try_get(&*para_config.chain_spec); + let chain_id = extension.map(|e| e.relay_chain.clone()); + + let base_path = para_config.base_path.path().join("polkadot"); + Self { base, chain_id, base_path: Some(base_path), _phantom: Default::default() } + } +} + +impl SubstrateCli for RelayChainCli { + fn impl_name() -> String { + Cli::::impl_name() + } + + fn impl_version() -> String { + Cli::::impl_version() + } + + fn description() -> String { + Cli::::description() + } + + fn author() -> String { + Cli::::author() + } + + fn support_url() -> String { + Cli::::support_url() + } + + fn copyright_start_year() -> i32 { + Cli::::copyright_start_year() + } + + fn load_spec(&self, id: &str) -> std::result::Result, String> { + polkadot_cli::Cli::from_iter([Self::executable_name()].iter()).load_spec(id) + } +} + +impl DefaultConfigurationValues for RelayChainCli { + fn p2p_listen_port() -> u16 { + 30334 + } + + fn rpc_listen_port() -> u16 { + 9945 + } + + fn prometheus_listen_port() -> u16 { + 9616 + } +} + +impl CliConfiguration for RelayChainCli { + fn shared_params(&self) -> &SharedParams { + self.base.base.shared_params() + } + + fn import_params(&self) -> Option<&ImportParams> { + self.base.base.import_params() + } + + fn network_params(&self) -> Option<&NetworkParams> { + self.base.base.network_params() + } + + fn keystore_params(&self) -> Option<&KeystoreParams> { + self.base.base.keystore_params() + } + + fn base_path(&self) -> sc_cli::Result> { + Ok(self + .shared_params() + .base_path()? + .or_else(|| self.base_path.clone().map(Into::into))) + } + + fn rpc_addr(&self, default_listen_port: u16) -> sc_cli::Result>> { + self.base.base.rpc_addr(default_listen_port) + } + + fn prometheus_config( + &self, + default_listen_port: u16, + chain_spec: &Box, + ) -> sc_cli::Result> { + self.base.base.prometheus_config(default_listen_port, chain_spec) + } + + fn init( + &self, + _support_url: &String, + _impl_version: &String, + _logger_hook: F, + _config: &sc_service::Configuration, + ) -> sc_cli::Result<()> + where + F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration), + { + unreachable!("PolkadotCli is never initialized; qed"); + } + + fn chain_id(&self, is_dev: bool) -> sc_cli::Result { + let chain_id = self.base.base.chain_id(is_dev)?; + + Ok(if chain_id.is_empty() { self.chain_id.clone().unwrap_or_default() } else { chain_id }) + } + + fn role(&self, is_dev: bool) -> sc_cli::Result { + self.base.base.role(is_dev) + } + + fn transaction_pool( + &self, + is_dev: bool, + ) -> sc_cli::Result { + self.base.base.transaction_pool(is_dev) + } + + fn trie_cache_maximum_size(&self) -> sc_cli::Result> { + self.base.base.trie_cache_maximum_size() + } + + fn rpc_methods(&self) -> sc_cli::Result { + self.base.base.rpc_methods() + } + + fn rpc_max_connections(&self) -> sc_cli::Result { + self.base.base.rpc_max_connections() + } + + fn rpc_cors(&self, is_dev: bool) -> sc_cli::Result>> { + self.base.base.rpc_cors(is_dev) + } + + fn default_heap_pages(&self) -> sc_cli::Result> { + self.base.base.default_heap_pages() + } + + fn force_authoring(&self) -> sc_cli::Result { + self.base.base.force_authoring() + } + + fn disable_grandpa(&self) -> sc_cli::Result { + self.base.base.disable_grandpa() + } + + fn max_runtime_instances(&self) -> sc_cli::Result> { + self.base.base.max_runtime_instances() + } + + fn announce_block(&self) -> sc_cli::Result { + self.base.base.announce_block() + } + + fn telemetry_endpoints( + &self, + chain_spec: &Box, + ) -> sc_cli::Result> { + self.base.base.telemetry_endpoints(chain_spec) + } + + fn node_name(&self) -> sc_cli::Result { + self.base.base.node_name() + } +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs new file mode 100644 index 000000000000..320511ece5e5 --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs @@ -0,0 +1,315 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::{ + cli::{Cli, RelayChainCli, Subcommand}, + common::{ + chain_spec::{Extensions, LoadSpec}, + runtime::{ + AuraConsensusId, Consensus, Runtime, RuntimeResolver as RuntimeResolverT, + RuntimeResolver, + }, + spec::DynNodeSpec, + types::Block, + NodeBlock, NodeExtraArgs, + }, + fake_runtime_api, + runtime::BlockNumber, + service::ShellNode, +}; +#[cfg(feature = "runtime-benchmarks")] +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; +use sc_cli::{Result, SubstrateCli}; +use sp_runtime::traits::AccountIdConversion; +#[cfg(feature = "runtime-benchmarks")] +use sp_runtime::traits::HashingFor; +use std::panic::{RefUnwindSafe, UnwindSafe}; + +/// Structure that can be used in order to provide customizers for different functionalities of the +/// node binary that is being built using this library. +pub struct RunConfig { + /// A custom chain spec loader. + pub chain_spec_loader: Box, + /// A custom runtime resolver. + pub runtime_resolver: Box, +} + +pub fn new_aura_node_spec( + aura_id: AuraConsensusId, + extra_args: &NodeExtraArgs, +) -> Box +where + Block: NodeBlock + UnwindSafe + RefUnwindSafe, + Block::BoundedHeader: UnwindSafe + RefUnwindSafe, +{ + match aura_id { + AuraConsensusId::Sr25519 => crate::service::new_aura_node_spec::< + Block, + fake_runtime_api::aura_sr25519::RuntimeApi, + sp_consensus_aura::sr25519::AuthorityId, + >(extra_args), + AuraConsensusId::Ed25519 => crate::service::new_aura_node_spec::< + Block, + fake_runtime_api::aura_ed25519::RuntimeApi, + sp_consensus_aura::ed25519::AuthorityId, + >(extra_args), + } +} + +fn new_node_spec( + config: &sc_service::Configuration, + runtime_resolver: &Box, + extra_args: &NodeExtraArgs, +) -> std::result::Result, sc_cli::Error> { + let runtime = runtime_resolver.runtime(config.chain_spec.as_ref())?; + + Ok(match runtime { + Runtime::Shell => Box::new(ShellNode), + Runtime::Omni(block_number, consensus) => match (block_number, consensus) { + (BlockNumber::U32, Consensus::Aura(aura_id)) => + new_aura_node_spec::>(aura_id, extra_args), + (BlockNumber::U64, Consensus::Aura(aura_id)) => + new_aura_node_spec::>(aura_id, extra_args), + }, + }) +} + +/// Parse command line arguments into service configuration. +pub fn run(cmd_config: RunConfig) -> Result<()> { + let mut cli = Cli::::from_args(); + cli.chain_spec_loader = Some(cmd_config.chain_spec_loader); + + match &cli.subcommand { + Some(Subcommand::BuildSpec(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) + }, + Some(Subcommand::CheckBlock(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let node = + new_node_spec(&config, &cmd_config.runtime_resolver, &cli.node_extra_args())?; + node.prepare_check_block_cmd(config, cmd) + }) + }, + Some(Subcommand::ExportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let node = + new_node_spec(&config, &cmd_config.runtime_resolver, &cli.node_extra_args())?; + node.prepare_export_blocks_cmd(config, cmd) + }) + }, + Some(Subcommand::ExportState(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let node = + new_node_spec(&config, &cmd_config.runtime_resolver, &cli.node_extra_args())?; + node.prepare_export_state_cmd(config, cmd) + }) + }, + Some(Subcommand::ImportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let node = + new_node_spec(&config, &cmd_config.runtime_resolver, &cli.node_extra_args())?; + node.prepare_import_blocks_cmd(config, cmd) + }) + }, + Some(Subcommand::Revert(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let node = + new_node_spec(&config, &cmd_config.runtime_resolver, &cli.node_extra_args())?; + node.prepare_revert_cmd(config, cmd) + }) + }, + Some(Subcommand::PurgeChain(cmd)) => { + let runner = cli.create_runner(cmd)?; + let polkadot_cli = + RelayChainCli::::new(runner.config(), cli.relay_chain_args.iter()); + + runner.sync_run(|config| { + let polkadot_config = SubstrateCli::create_configuration( + &polkadot_cli, + &polkadot_cli, + config.tokio_handle.clone(), + ) + .map_err(|err| format!("Relay chain argument error: {}", err))?; + + cmd.run(config, polkadot_config) + }) + }, + Some(Subcommand::ExportGenesisHead(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| { + let node = + new_node_spec(&config, &cmd_config.runtime_resolver, &cli.node_extra_args())?; + node.run_export_genesis_head_cmd(config, cmd) + }) + }, + Some(Subcommand::ExportGenesisWasm(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|_config| { + let spec = cli.load_spec(&cmd.shared_params.chain.clone().unwrap_or_default())?; + cmd.run(&*spec) + }) + }, + Some(Subcommand::Benchmark(cmd)) => { + let runner = cli.create_runner(cmd)?; + + // Switch on the concrete benchmark sub-command- + match cmd { + #[cfg(feature = "runtime-benchmarks")] + BenchmarkCmd::Pallet(cmd) => runner.sync_run(|config| { + cmd.run_with_spec::>, ReclaimHostFunctions>(Some( + config.chain_spec, + )) + }), + BenchmarkCmd::Block(cmd) => runner.sync_run(|config| { + let node = new_node_spec( + &config, + &cmd_config.runtime_resolver, + &cli.node_extra_args(), + )?; + node.run_benchmark_block_cmd(config, cmd) + }), + #[cfg(feature = "runtime-benchmarks")] + BenchmarkCmd::Storage(cmd) => runner.sync_run(|config| { + let node = new_node_spec( + &config, + &cmd_config.runtime_resolver, + &cli.node_extra_args(), + )?; + node.run_benchmark_storage_cmd(config, cmd) + }), + BenchmarkCmd::Machine(cmd) => + runner.sync_run(|config| cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone())), + #[allow(unreachable_patterns)] + _ => Err("Benchmarking sub-command unsupported or compilation feature missing. \ + Make sure to compile with --features=runtime-benchmarks \ + to enable all supported benchmarks." + .into()), + } + }, + Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?), + None => { + let runner = cli.create_runner(&cli.run.normalize())?; + let polkadot_cli = + RelayChainCli::::new(runner.config(), cli.relay_chain_args.iter()); + let collator_options = cli.run.collator_options(); + + runner.run_node_until_exit(|config| async move { + // If Statemint (Statemine, Westmint, Rockmine) DB exists and we're using the + // asset-hub chain spec, then rename the base path to the new chain ID. In the case + // 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, + }; + + 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); + + 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())?; + info!( + "{} was renamed to Asset Hub. The filepath with associated data on disk \ + has been renamed from {} to {}.", + old_name, + old_path.display(), + new_path.display() + ); + } + } + + let hwbench = (!cli.no_hardware_benchmarks) + .then_some(config.database.path().map(|database_path| { + let _ = std::fs::create_dir_all(database_path); + sc_sysinfo::gather_hwbench(Some(database_path)) + })) + .flatten(); + + let para_id = Extensions::try_get(&*config.chain_spec) + .map(|e| e.para_id) + .ok_or("Could not find parachain extension in chain-spec.")?; + + let id = ParaId::from(para_id); + + let parachain_account = + AccountIdConversion::::into_account_truncating( + &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))?; + + info!("🪪 Parachain id: {:?}", id); + info!("🧾 Parachain Account: {}", parachain_account); + info!("✍️ Is collating: {}", if config.role.is_authority() { "yes" } else { "no" }); + + start_node( + config, + &cmd_config.runtime_resolver, + polkadot_config, + collator_options, + id, + cli.node_extra_args(), + hwbench, + ) + .await + }) + }, + } +} + +#[sc_tracing::logging::prefix_logs_with("Parachain")] +async fn start_node( + config: sc_service::Configuration, + runtime_resolver: &Box, + polkadot_config: sc_service::Configuration, + collator_options: cumulus_client_cli::CollatorOptions, + id: ParaId, + extra_args: NodeExtraArgs, + hwbench: Option, +) -> Result { + let node_spec = new_node_spec(&config, runtime_resolver, &extra_args)?; + node_spec + .start_node(config, polkadot_config, collator_options, id, hwbench, extra_args) + .await + .map_err(Into::into) +} diff --git a/cumulus/polkadot-parachain/src/common/aura.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/aura.rs similarity index 95% rename from cumulus/polkadot-parachain/src/common/aura.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/aura.rs index 9f72d847926f..9e8837de7f87 100644 --- a/cumulus/polkadot-parachain/src/common/aura.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/aura.rs @@ -18,9 +18,11 @@ use codec::Codec; use cumulus_primitives_aura::AuraUnincludedSegmentApi; -use cumulus_primitives_core::BlockT; use sp_consensus_aura::AuraApi; -use sp_runtime::app_crypto::{AppCrypto, AppPair, AppSignature, Pair}; +use sp_runtime::{ + app_crypto::{AppCrypto, AppPair, AppSignature, Pair}, + traits::Block as BlockT, +}; /// Convenience trait for defining the basic bounds of an `AuraId`. pub trait AuraIdT: AppCrypto + Codec + Send { diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/chain_spec.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/chain_spec.rs new file mode 100644 index 000000000000..974d6ef2b611 --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/chain_spec.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 . + +//! Chain spec primitives. + +pub use sc_chain_spec::ChainSpec; +use sc_chain_spec::ChainSpecExtension; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; + +/// Helper trait used for loading/building a chain spec starting from the chain ID. +pub trait LoadSpec { + /// Load/Build a chain spec starting from the chain ID. + fn load_spec(&self, id: &str) -> Result, String>; +} + +/// Default implementation for `LoadSpec` that just reads a chain spec from the disk. +pub struct DiskChainSpecLoader; + +impl LoadSpec for DiskChainSpecLoader { + fn load_spec(&self, path: &str) -> Result, String> { + Ok(Box::new(GenericChainSpec::from_json_file(path.into())?)) + } +} + +/// Generic extensions for Parachain ChainSpecs. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecExtension)] +pub struct Extensions { + /// The relay chain of the Parachain. + #[serde(alias = "relayChain", alias = "RelayChain")] + pub relay_chain: String, + /// The id of the Parachain. + #[serde(alias = "paraId", alias = "ParaId")] + 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()) + } +} + +/// Generic chain spec for all polkadot-parachain runtimes +pub type GenericChainSpec = sc_service::GenericChainSpec; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_decode_extension_camel_and_snake_case() { + let camel_case = r#"{"relayChain":"relay","paraId":1}"#; + let snake_case = r#"{"relay_chain":"relay","para_id":1}"#; + let pascal_case = r#"{"RelayChain":"relay","ParaId":1}"#; + + let camel_case_extension: Extensions = serde_json::from_str(camel_case).unwrap(); + let snake_case_extension: Extensions = serde_json::from_str(snake_case).unwrap(); + let pascal_case_extension: Extensions = serde_json::from_str(pascal_case).unwrap(); + + assert_eq!(camel_case_extension, snake_case_extension); + assert_eq!(snake_case_extension, pascal_case_extension); + } +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs new file mode 100644 index 000000000000..e2826826d40e --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs @@ -0,0 +1,161 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::common::spec::NodeSpec; +use cumulus_client_cli::ExportGenesisHeadCommand; +use frame_benchmarking_cli::BlockCmd; +#[cfg(any(feature = "runtime-benchmarks"))] +use frame_benchmarking_cli::StorageCmd; +use sc_cli::{CheckBlockCmd, ExportBlocksCmd, ExportStateCmd, ImportBlocksCmd, RevertCmd}; +use sc_service::{Configuration, TaskManager}; +use std::{future::Future, pin::Pin}; + +type SyncCmdResult = sc_cli::Result<()>; + +type AsyncCmdResult<'a> = + sc_cli::Result<(Pin + 'a>>, TaskManager)>; + +pub trait NodeCommandRunner { + fn prepare_check_block_cmd( + self: Box, + config: Configuration, + cmd: &CheckBlockCmd, + ) -> AsyncCmdResult<'_>; + + fn prepare_export_blocks_cmd( + self: Box, + config: Configuration, + cmd: &ExportBlocksCmd, + ) -> AsyncCmdResult<'_>; + + fn prepare_export_state_cmd( + self: Box, + config: Configuration, + cmd: &ExportStateCmd, + ) -> AsyncCmdResult<'_>; + + fn prepare_import_blocks_cmd( + self: Box, + config: Configuration, + cmd: &ImportBlocksCmd, + ) -> AsyncCmdResult<'_>; + + fn prepare_revert_cmd( + self: Box, + config: Configuration, + cmd: &RevertCmd, + ) -> AsyncCmdResult<'_>; + + fn run_export_genesis_head_cmd( + self: Box, + config: Configuration, + cmd: &ExportGenesisHeadCommand, + ) -> SyncCmdResult; + + fn run_benchmark_block_cmd( + self: Box, + config: Configuration, + cmd: &BlockCmd, + ) -> SyncCmdResult; + + #[cfg(any(feature = "runtime-benchmarks"))] + fn run_benchmark_storage_cmd( + self: Box, + config: Configuration, + cmd: &StorageCmd, + ) -> SyncCmdResult; +} + +impl NodeCommandRunner for T +where + T: NodeSpec, +{ + fn prepare_check_block_cmd( + self: Box, + config: Configuration, + cmd: &CheckBlockCmd, + ) -> AsyncCmdResult<'_> { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + Ok((Box::pin(cmd.run(partial.client, partial.import_queue)), partial.task_manager)) + } + + fn prepare_export_blocks_cmd( + self: Box, + config: Configuration, + cmd: &ExportBlocksCmd, + ) -> AsyncCmdResult<'_> { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + Ok((Box::pin(cmd.run(partial.client, config.database)), partial.task_manager)) + } + + fn prepare_export_state_cmd( + self: Box, + config: Configuration, + cmd: &ExportStateCmd, + ) -> AsyncCmdResult<'_> { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + Ok((Box::pin(cmd.run(partial.client, config.chain_spec)), partial.task_manager)) + } + + fn prepare_import_blocks_cmd( + self: Box, + config: Configuration, + cmd: &ImportBlocksCmd, + ) -> AsyncCmdResult<'_> { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + Ok((Box::pin(cmd.run(partial.client, partial.import_queue)), partial.task_manager)) + } + + fn prepare_revert_cmd( + self: Box, + config: Configuration, + cmd: &RevertCmd, + ) -> AsyncCmdResult<'_> { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + Ok((Box::pin(cmd.run(partial.client, partial.backend, None)), partial.task_manager)) + } + + fn run_export_genesis_head_cmd( + self: Box, + config: Configuration, + cmd: &ExportGenesisHeadCommand, + ) -> SyncCmdResult { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + cmd.run(partial.client) + } + + fn run_benchmark_block_cmd( + self: Box, + config: Configuration, + cmd: &BlockCmd, + ) -> SyncCmdResult { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + cmd.run(partial.client) + } + + #[cfg(any(feature = "runtime-benchmarks"))] + fn run_benchmark_storage_cmd( + self: Box, + config: Configuration, + cmd: &StorageCmd, + ) -> SyncCmdResult { + let partial = T::new_partial(&config).map_err(sc_cli::Error::Service)?; + let db = partial.backend.expose_db(); + let storage = partial.backend.expose_storage(); + + cmd.run(config, partial.client, db, storage) + } +} diff --git a/cumulus/polkadot-parachain/src/common/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs similarity index 71% rename from cumulus/polkadot-parachain/src/common/mod.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs index d7718931b872..907f09263fc1 100644 --- a/cumulus/polkadot-parachain/src/common/mod.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs @@ -18,15 +18,45 @@ #![warn(missing_docs)] -pub mod aura; +pub(crate) mod aura; +pub mod chain_spec; +pub mod command; +pub mod rpc; +pub mod runtime; +pub mod spec; +pub mod types; use cumulus_primitives_core::CollectCollationInfo; +use sc_client_db::DbHash; use sp_api::{ApiExt, CallApiAt, ConstructRuntimeApi, Metadata}; use sp_block_builder::BlockBuilder; -use sp_runtime::traits::Block as BlockT; +use sp_runtime::{ + traits::{Block as BlockT, BlockNumber, Header as HeaderT, NumberFor}, + OpaqueExtrinsic, +}; use sp_session::SessionKeys; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; -use std::path::PathBuf; +use std::{fmt::Debug, path::PathBuf, str::FromStr}; + +pub trait NodeBlock: + BlockT + + for<'de> serde::Deserialize<'de> +{ + type BoundedFromStrErr: Debug; + type BoundedNumber: FromStr + BlockNumber; + type BoundedHeader: HeaderT + Unpin; +} + +impl NodeBlock for T +where + T: BlockT + for<'de> serde::Deserialize<'de>, + ::Header: Unpin, + as FromStr>::Err: Debug, +{ + type BoundedFromStrErr = as FromStr>::Err; + type BoundedNumber = NumberFor; + type BoundedHeader = ::Header; +} /// Convenience trait that defines the basic bounds for the `RuntimeApi` of a parachain node. pub trait NodeRuntimeApi: diff --git a/cumulus/polkadot-parachain/src/rpc.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs similarity index 59% rename from cumulus/polkadot-parachain/src/rpc.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs index 283a73d931d7..a4e157e87216 100644 --- a/cumulus/polkadot-parachain/src/rpc.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs @@ -18,16 +18,13 @@ #![warn(missing_docs)] -use crate::{ - common::ConstructNodeRuntimeApi, - service::{ParachainBackend, ParachainClient}, +use crate::common::{ + types::{AccountId, Balance, Nonce, ParachainBackend, ParachainClient}, + ConstructNodeRuntimeApi, }; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; -use parachains_common::{AccountId, Balance, Block, Nonce}; -use sc_rpc::{ - dev::{Dev, DevApiServer}, - DenyUnsafe, -}; +use sc_rpc::dev::{Dev, DevApiServer}; +use sp_runtime::traits::Block as BlockT; use std::{marker::PhantomData, sync::Arc}; use substrate_frame_rpc_system::{System, SystemApiServer}; use substrate_state_trie_migration_rpc::{StateMigration, StateMigrationApiServer}; @@ -37,60 +34,59 @@ pub type RpcExtension = jsonrpsee::RpcModule<()>; pub(crate) trait BuildRpcExtensions { fn build_rpc_extensions( - deny_unsafe: DenyUnsafe, client: Arc, backend: Arc, pool: Arc, ) -> sc_service::error::Result; } -pub(crate) struct BuildEmptyRpcExtensions(PhantomData); +pub(crate) struct BuildEmptyRpcExtensions(PhantomData<(Block, RuntimeApi)>); -impl +impl BuildRpcExtensions< - ParachainClient, - ParachainBackend, - sc_transaction_pool::FullPool>, - > for BuildEmptyRpcExtensions + ParachainClient, + ParachainBackend, + sc_transaction_pool::FullPool>, + > for BuildEmptyRpcExtensions where - RuntimeApi: ConstructNodeRuntimeApi> + Send + Sync + 'static, + RuntimeApi: + ConstructNodeRuntimeApi> + Send + Sync + 'static, { fn build_rpc_extensions( - _deny_unsafe: DenyUnsafe, - _client: Arc>, - _backend: Arc, - _pool: Arc>>, + _client: Arc>, + _backend: Arc>, + _pool: Arc>>, ) -> sc_service::error::Result { Ok(RpcExtension::new(())) } } -pub(crate) struct BuildParachainRpcExtensions(PhantomData); +pub(crate) struct BuildParachainRpcExtensions(PhantomData<(Block, RuntimeApi)>); -impl +impl BuildRpcExtensions< - ParachainClient, - ParachainBackend, - sc_transaction_pool::FullPool>, - > for BuildParachainRpcExtensions + ParachainClient, + ParachainBackend, + sc_transaction_pool::FullPool>, + > for BuildParachainRpcExtensions where - RuntimeApi: ConstructNodeRuntimeApi> + Send + Sync + 'static, + RuntimeApi: + ConstructNodeRuntimeApi> + Send + Sync + 'static, RuntimeApi::RuntimeApi: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + substrate_frame_rpc_system::AccountNonceApi, { fn build_rpc_extensions( - deny_unsafe: DenyUnsafe, - client: Arc>, - backend: Arc, - pool: Arc>>, + client: Arc>, + backend: Arc>, + pool: Arc>>, ) -> sc_service::error::Result { let build = || -> Result> { let mut module = RpcExtension::new(()); - module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; + module.merge(System::new(client.clone(), pool).into_rpc())?; module.merge(TransactionPayment::new(client.clone()).into_rpc())?; - module.merge(StateMigration::new(client.clone(), backend, deny_unsafe).into_rpc())?; - module.merge(Dev::new(client, deny_unsafe).into_rpc())?; + module.merge(StateMigration::new(client.clone(), backend).into_rpc())?; + module.merge(Dev::new(client).into_rpc())?; Ok(module) }; diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs new file mode 100644 index 000000000000..bddbb0a85d03 --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs @@ -0,0 +1,70 @@ +// 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 . + +//! Runtime parameters. + +use sc_chain_spec::ChainSpec; + +/// The Aura ID used by the Aura consensus +#[derive(PartialEq)] +pub enum AuraConsensusId { + /// Ed25519 + Ed25519, + /// Sr25519 + Sr25519, +} + +/// The choice of consensus for the parachain omni-node. +#[derive(PartialEq)] +pub enum Consensus { + /// Aura consensus. + Aura(AuraConsensusId), +} + +/// The choice of block number for the parachain omni-node. +#[derive(PartialEq)] +pub enum BlockNumber { + /// u32 + U32, + /// u64 + U64, +} + +/// Helper enum listing the supported Runtime types +#[derive(PartialEq)] +pub enum Runtime { + /// None of the system-chain runtimes, rather the node will act agnostic to the runtime ie. be + /// an omni-node, and simply run a node with the given consensus algorithm. + Omni(BlockNumber, Consensus), + /// Shell + Shell, +} + +/// Helper trait used for extracting the Runtime variant from the chain spec ID. +pub trait RuntimeResolver { + /// Extract the Runtime variant from the chain spec ID. + fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result; +} + +/// Default implementation for `RuntimeResolver` that just returns +/// `Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519))`. +pub struct DefaultRuntimeResolver; + +impl RuntimeResolver for DefaultRuntimeResolver { + fn runtime(&self, _chain_spec: &dyn ChainSpec) -> sc_cli::Result { + Ok(Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519))) + } +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs new file mode 100644 index 000000000000..55e042aed87e --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs @@ -0,0 +1,392 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::common::{ + command::NodeCommandRunner, + rpc::BuildRpcExtensions, + types::{ + ParachainBackend, ParachainBlockImport, ParachainClient, ParachainHostFunctions, + ParachainService, + }, + ConstructNodeRuntimeApi, NodeBlock, NodeExtraArgs, +}; +use cumulus_client_cli::CollatorOptions; +use cumulus_client_service::{ + build_network, build_relay_chain_interface, prepare_node_config, start_relay_chain_tasks, + BuildNetworkParams, CollatorSybilResistance, DARecoveryProfile, StartRelayChainTasksParams, +}; +use cumulus_primitives_core::{BlockT, ParaId}; +use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; +use parachains_common::Hash; +use polkadot_primitives::CollatorPair; +use prometheus_endpoint::Registry; +use sc_consensus::DefaultImportQueue; +use sc_executor::{HeapAllocStrategy, DEFAULT_HEAP_ALLOC_STRATEGY}; +use sc_network::{config::FullNetworkConfiguration, NetworkBackend, NetworkBlock}; +use sc_service::{Configuration, ImportQueue, PartialComponents, TaskManager}; +use sc_sysinfo::HwBench; +use sc_telemetry::{TelemetryHandle, TelemetryWorker}; +use sc_transaction_pool::FullPool; +use sp_keystore::KeystorePtr; +use std::{future::Future, pin::Pin, sync::Arc, time::Duration}; + +pub(crate) trait BuildImportQueue { + fn build_import_queue( + client: Arc>, + block_import: ParachainBlockImport, + config: &Configuration, + telemetry_handle: Option, + task_manager: &TaskManager, + ) -> sc_service::error::Result>; +} + +pub(crate) trait StartConsensus +where + RuntimeApi: ConstructNodeRuntimeApi>, +{ + fn start_consensus( + client: Arc>, + block_import: ParachainBlockImport, + prometheus_registry: Option<&Registry>, + telemetry: Option, + task_manager: &TaskManager, + relay_chain_interface: Arc, + transaction_pool: Arc>>, + keystore: KeystorePtr, + relay_chain_slot_duration: Duration, + para_id: ParaId, + collator_key: CollatorPair, + overseer_handle: OverseerHandle, + announce_block: Arc>) + Send + Sync>, + backend: Arc>, + node_extra_args: NodeExtraArgs, + ) -> Result<(), sc_service::Error>; +} + +/// 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 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", + err + ); + } +} + +pub(crate) trait NodeSpec { + type Block: NodeBlock; + + type RuntimeApi: ConstructNodeRuntimeApi< + Self::Block, + ParachainClient, + >; + + type BuildImportQueue: BuildImportQueue; + + type BuildRpcExtensions: BuildRpcExtensions< + ParachainClient, + ParachainBackend, + FullPool>, + >; + + type StartConsensus: StartConsensus; + + const SYBIL_RESISTANCE: CollatorSybilResistance; + + /// 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. + fn new_partial( + config: &Configuration, + ) -> sc_service::error::Result> { + let telemetry = config + .telemetry_endpoints + .clone() + .filter(|x| !x.is_empty()) + .map(|endpoints| -> Result<_, sc_telemetry::Error> { + let worker = TelemetryWorker::new(16)?; + let telemetry = worker.handle().new_telemetry(endpoints); + Ok((worker, telemetry)) + }) + .transpose()?; + + let heap_pages = config.default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| { + HeapAllocStrategy::Static { extra_pages: h as _ } + }); + + let executor = sc_executor::WasmExecutor::::builder() + .with_execution_method(config.wasm_method) + .with_max_runtime_instances(config.max_runtime_instances) + .with_runtime_cache_size(config.runtime_cache_size) + .with_onchain_heap_alloc_strategy(heap_pages) + .with_offchain_heap_alloc_strategy(heap_pages) + .build(); + + let (client, backend, keystore_container, task_manager) = + sc_service::new_full_parts_record_import::( + config, + telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), + executor, + true, + )?; + let client = Arc::new(client); + + let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle()); + + let telemetry = telemetry.map(|(worker, telemetry)| { + task_manager.spawn_handle().spawn("telemetry", None, worker.run()); + telemetry + }); + + let transaction_pool = sc_transaction_pool::BasicPool::new_full( + config.transaction_pool.clone(), + config.role.is_authority().into(), + config.prometheus_registry(), + task_manager.spawn_essential_handle(), + client.clone(), + ); + + let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); + + let import_queue = Self::BuildImportQueue::build_import_queue( + client.clone(), + block_import.clone(), + config, + telemetry.as_ref().map(|telemetry| telemetry.handle()), + &task_manager, + )?; + + Ok(PartialComponents { + backend, + client, + import_queue, + keystore_container, + task_manager, + transaction_pool, + select_chain: (), + other: (block_import, telemetry, telemetry_worker_handle), + }) + } + + /// Start a node with the given parachain spec. + /// + /// This is the actual implementation that is abstract over the executor and the runtime api. + fn start_node( + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, + node_extra_args: NodeExtraArgs, + ) -> Pin>>> + where + Net: NetworkBackend, + { + Box::pin(async move { + let parachain_config = prepare_node_config(parachain_config); + + let params = Self::new_partial(¶chain_config)?; + 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::<_, _, Net>::new( + ¶chain_config.network, + prometheus_registry.clone(), + ); + + 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: Self::SYBIL_RESISTANCE, + }) + .await?; + + let rpc_builder = { + let client = client.clone(); + let transaction_pool = transaction_pool.clone(); + let backend_for_rpc = backend.clone(); + + Box::new(move |_| { + Self::BuildRpcExtensions::build_rpc_extensions( + client.clone(), + backend_for_rpc.clone(), + transaction_pool.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, + })?; + + if validator { + Self::StartConsensus::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, + 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(), + node_extra_args, + )?; + } + + start_network.start_network(); + + Ok(task_manager) + }) + } +} + +pub(crate) trait DynNodeSpec: NodeCommandRunner { + fn start_node( + self: Box, + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, + node_extra_args: NodeExtraArgs, + ) -> Pin>>>; +} + +impl DynNodeSpec for T +where + T: NodeSpec + NodeCommandRunner, +{ + fn start_node( + self: Box, + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, + node_extra_args: NodeExtraArgs, + ) -> Pin>>> { + match parachain_config.network.network_backend { + sc_network::config::NetworkBackendType::Libp2p => + ::start_node::>( + parachain_config, + polkadot_config, + collator_options, + para_id, + hwbench, + node_extra_args, + ), + sc_network::config::NetworkBackendType::Litep2p => + ::start_node::( + parachain_config, + polkadot_config, + collator_options, + para_id, + hwbench, + node_extra_args, + ), + } + } +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/types.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/types.rs new file mode 100644 index 000000000000..9cfdcb22451c --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/types.rs @@ -0,0 +1,56 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use cumulus_client_consensus_common::ParachainBlockImport as TParachainBlockImport; +use cumulus_primitives_core::relay_chain::UncheckedExtrinsic; +use sc_consensus::DefaultImportQueue; +use sc_executor::WasmExecutor; +use sc_service::{PartialComponents, TFullBackend, TFullClient}; +use sc_telemetry::{Telemetry, TelemetryWorkerHandle}; +use sc_transaction_pool::FullPool; +use sp_runtime::{generic, traits::BlakeTwo256}; +use std::sync::Arc; + +pub use parachains_common::{AccountId, Balance, Hash, Nonce}; + +type Header = generic::Header; +pub type Block = generic::Block, UncheckedExtrinsic>; + +#[cfg(not(feature = "runtime-benchmarks"))] +pub type ParachainHostFunctions = cumulus_client_service::ParachainHostFunctions; +#[cfg(feature = "runtime-benchmarks")] +pub type ParachainHostFunctions = ( + cumulus_client_service::ParachainHostFunctions, + frame_benchmarking::benchmarking::HostFunctions, +); + +pub type ParachainClient = + TFullClient>; + +pub type ParachainBackend = TFullBackend; + +pub type ParachainBlockImport = + TParachainBlockImport>, ParachainBackend>; + +/// Assembly of PartialComponents (enough to run chain ops subcommands) +pub type ParachainService = PartialComponents< + ParachainClient, + ParachainBackend, + (), + DefaultImportQueue, + FullPool>, + (ParachainBlockImport, Option, Option), +>; diff --git a/cumulus/polkadot-parachain/src/fake_runtime_api/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs similarity index 67% rename from cumulus/polkadot-parachain/src/fake_runtime_api/mod.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs index 29e2736b06ff..02aa867d70fe 100644 --- a/cumulus/polkadot-parachain/src/fake_runtime_api/mod.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs @@ -17,5 +17,19 @@ //! 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; +mod utils; + +use utils::{impl_node_runtime_apis, imports::*}; + +type CustomBlock = crate::common::types::Block; +pub mod aura_sr25519 { + use super::*; + struct FakeRuntime; + impl_node_runtime_apis!(FakeRuntime, CustomBlock, sp_consensus_aura::sr25519::AuthorityId); +} + +pub mod aura_ed25519 { + use super::*; + struct FakeRuntime; + impl_node_runtime_apis!(FakeRuntime, CustomBlock, sp_consensus_aura::ed25519::AuthorityId); +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs new file mode 100644 index 000000000000..442b87b5d775 --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs @@ -0,0 +1,220 @@ +// 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(crate) mod imports { + pub use parachains_common::{AccountId, Balance, Nonce}; + pub use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; + pub use sp_runtime::{ + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, + }; + pub use sp_weights::Weight; +} + +macro_rules! impl_node_runtime_apis { + ($runtime: ty, $block: tt, $aura_id: ty) => { + sp_api::impl_runtime_apis! { + impl sp_api::Core<$block> for $runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + + fn execute_block(_: $block) { + unimplemented!() + } + + fn initialize_block( + _: &<$block as BlockT>::Header + ) -> sp_runtime::ExtrinsicInclusionMode { + unimplemented!() + } + } + + impl sp_api::Metadata<$block> for $runtime { + fn metadata() -> OpaqueMetadata { + unimplemented!() + } + + fn metadata_at_version(_: u32) -> Option { + unimplemented!() + } + + fn metadata_versions() -> Vec { + unimplemented!() + } + } + + impl sp_consensus_aura::AuraApi<$block, $aura_id> for $runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + unimplemented!() + } + + fn authorities() -> Vec<$aura_id> { + unimplemented!() + } + } + + impl cumulus_primitives_aura::AuraUnincludedSegmentApi<$block> for $runtime { + fn can_build_upon( + _: <$block as BlockT>::Hash, + _: cumulus_primitives_aura::Slot, + ) -> bool { + unimplemented!() + } + } + + impl sp_block_builder::BlockBuilder<$block> for $runtime { + fn apply_extrinsic(_: <$block as BlockT>::Extrinsic) -> ApplyExtrinsicResult { + unimplemented!() + } + + fn finalize_block() -> <$block as BlockT>::Header { + unimplemented!() + } + + fn inherent_extrinsics( + _: sp_inherents::InherentData + ) -> Vec<<$block as BlockT>::Extrinsic> { + unimplemented!() + } + + fn check_inherents( + _: $block, + _: sp_inherents::InherentData + ) -> sp_inherents::CheckInherentsResult { + unimplemented!() + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<$block> for $runtime { + fn validate_transaction( + _: TransactionSource, + _: <$block as BlockT>::Extrinsic, + _: <$block as BlockT>::Hash, + ) -> TransactionValidity { + unimplemented!() + } + } + + impl sp_session::SessionKeys<$block> 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< + $block, + Balance, + > for $runtime + { + fn query_info( + _: <$block as BlockT>::Extrinsic, + _: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + unimplemented!() + } + fn query_fee_details( + _: <$block as BlockT>::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<$block> for $runtime { + fn collect_collation_info( + _: &<$block as BlockT>::Header + ) -> cumulus_primitives_core::CollationInfo { + unimplemented!() + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime<$block> 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< + $block, + AccountId, + Nonce + > for $runtime { + fn account_nonce(_: AccountId) -> Nonce { + unimplemented!() + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark<$block> 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<$block> for $runtime { + fn build_state(_: Vec) -> sp_genesis_builder::Result { + unimplemented!() + } + + fn get_preset(_id: &Option) -> Option> { + unimplemented!() + } + + fn preset_names() -> Vec { + unimplemented!() + } + } + } + }; +} + +pub(crate) use impl_node_runtime_apis; diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs new file mode 100644 index 000000000000..6aa2f656a48b --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs @@ -0,0 +1,52 @@ +// 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 . + +//! Helper library that can be used to run a parachain node. +//! +//! ## Overview +//! +//! This library can be used to run a parachain node while also customizing the chain specs +//! that are supported by default by the `--chain-spec` argument of the node's `CLI` +//! and the parameters of the runtime that is associated with each of these chain specs. +//! +//! ## API +//! +//! The library exposes the possibility to provide a [`RunConfig`]. Through this structure +//! 2 optional configurations can be provided: +//! - a chain spec loader (an implementation of [`chain_spec::LoadSpec`]): this can be used for +//! providing the chain specs that are supported by default by the `--chain-spec` argument of the +//! node's `CLI` and the actual chain config associated with each one. +//! - a runtime resolver (an implementation of [`runtime::RuntimeResolver`]): this can be used for +//! providing the parameters of the runtime that is associated with each of the chain specs +//! +//! Apart from this, a [`CliConfig`] can also be provided, that can be used to customize some +//! user-facing binary author, support url, etc. +//! +//! ## Examples +//! +//! For an example, see the `polkadot-parachain-bin` crate. + +#![deny(missing_docs)] + +mod cli; +mod command; +mod common; +mod fake_runtime_api; +mod service; + +pub use cli::CliConfig; +pub use command::{run, RunConfig}; +pub use common::{chain_spec, runtime}; diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs new file mode 100644 index 000000000000..303ec1e3b298 --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs @@ -0,0 +1,552 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::{ + common::{ + aura::{AuraIdT, AuraRuntimeApi}, + rpc::{BuildEmptyRpcExtensions, BuildParachainRpcExtensions}, + spec::{BuildImportQueue, DynNodeSpec, NodeSpec, StartConsensus}, + types::{ + AccountId, Balance, Block, Hash, Nonce, ParachainBackend, ParachainBlockImport, + ParachainClient, + }, + ConstructNodeRuntimeApi, NodeBlock, NodeExtraArgs, + }, + fake_runtime_api::aura_sr25519::RuntimeApi as FakeRuntimeApi, +}; +use cumulus_client_collator::service::{ + CollatorService, ServiceInterface as CollatorServiceInterface, +}; +use cumulus_client_consensus_aura::collators::lookahead::{self as aura, Params as AuraParams}; +#[docify::export(slot_based_colator_import)] +use cumulus_client_consensus_aura::collators::slot_based::{ + self as slot_based, Params as SlotBasedParams, +}; +use cumulus_client_consensus_proposer::{Proposer, ProposerInterface}; +use cumulus_client_consensus_relay_chain::Verifier as RelayChainVerifier; +#[allow(deprecated)] +use cumulus_client_service::old_consensus; +use cumulus_client_service::CollatorSybilResistance; +use cumulus_primitives_core::{relay_chain::ValidationCode, ParaId}; +use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; +use futures::prelude::*; +use polkadot_primitives::CollatorPair; +use prometheus_endpoint::Registry; +use sc_client_api::BlockchainEvents; +use sc_client_db::DbHash; +use sc_consensus::{ + import_queue::{BasicQueue, Verifier as VerifierT}, + BlockImportParams, DefaultImportQueue, +}; +use sc_service::{Configuration, Error, TaskManager}; +use sc_telemetry::TelemetryHandle; +use sc_transaction_pool::FullPool; +use sp_api::ProvideRuntimeApi; +use sp_inherents::CreateInherentDataProviders; +use sp_keystore::KeystorePtr; +use sp_runtime::{ + app_crypto::AppCrypto, + traits::{Block as BlockT, Header as HeaderT}, +}; +use std::{marker::PhantomData, sync::Arc, time::Duration}; + +/// Build the import queue for the shell runtime. +pub(crate) struct BuildShellImportQueue; + +impl BuildImportQueue, FakeRuntimeApi> for BuildShellImportQueue { + fn build_import_queue( + client: Arc, FakeRuntimeApi>>, + block_import: ParachainBlockImport, FakeRuntimeApi>, + config: &Configuration, + _telemetry_handle: Option, + task_manager: &TaskManager, + ) -> sc_service::error::Result>> { + cumulus_client_consensus_relay_chain::import_queue( + client, + block_import, + |_, _| async { Ok(()) }, + &task_manager.spawn_essential_handle(), + config.prometheus_registry(), + ) + .map_err(Into::into) + } +} + +pub(crate) struct ShellNode; + +impl NodeSpec for ShellNode { + type Block = Block; + type RuntimeApi = FakeRuntimeApi; + type BuildImportQueue = BuildShellImportQueue; + type BuildRpcExtensions = BuildEmptyRpcExtensions, Self::RuntimeApi>; + type StartConsensus = StartRelayChainConsensus; + + const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Unresistant; +} + +struct Verifier { + client: Arc, + aura_verifier: Box>, + relay_chain_verifier: Box>, + _phantom: PhantomData, +} + +#[async_trait::async_trait] +impl VerifierT for Verifier +where + Client: ProvideRuntimeApi + Send + Sync, + Client::Api: AuraRuntimeApi, + AuraId: AuraIdT + Sync, +{ + async fn verify( + &self, + block_import: BlockImportParams, + ) -> Result, String> { + if self.client.runtime_api().has_aura_api(*block_import.header.parent_hash()) { + self.aura_verifier.verify(block_import).await + } else { + self.relay_chain_verifier.verify(block_import).await + } + } +} + +/// Build the import queue for parachain runtimes that started with relay chain consensus and +/// switched to aura. +pub(crate) struct BuildRelayToAuraImportQueue( + PhantomData<(Block, RuntimeApi, AuraId)>, +); + +impl BuildImportQueue + for BuildRelayToAuraImportQueue +where + RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi::RuntimeApi: AuraRuntimeApi, + AuraId: AuraIdT + Sync, +{ + fn build_import_queue( + client: Arc>, + block_import: ParachainBlockImport, + config: &Configuration, + telemetry_handle: Option, + task_manager: &TaskManager, + ) -> sc_service::error::Result> { + let verifier_client = client.clone(); + + let aura_verifier = cumulus_client_consensus_aura::build_verifier::< + ::Pair, + _, + _, + _, + >(cumulus_client_consensus_aura::BuildVerifierParams { + 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, + }); + + let relay_chain_verifier = + Box::new(RelayChainVerifier::new(client.clone(), |_, _| async { Ok(()) })); + + let verifier = Verifier { + client, + relay_chain_verifier, + aura_verifier: Box::new(aura_verifier), + _phantom: PhantomData, + }; + + let registry = config.prometheus_registry(); + let spawner = task_manager.spawn_essential_handle(); + + Ok(BasicQueue::new(verifier, Box::new(block_import), None, &spawner, registry)) + } +} + +/// Uses the lookahead collator to support async backing. +/// +/// Start an aura powered parachain node. Some system chains use this. +pub(crate) struct AuraNode( + pub PhantomData<(Block, RuntimeApi, AuraId, StartConsensus)>, +); + +impl Default + for AuraNode +{ + fn default() -> Self { + Self(Default::default()) + } +} + +impl NodeSpec + for AuraNode +where + Block: NodeBlock, + RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi::RuntimeApi: AuraRuntimeApi + + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + + substrate_frame_rpc_system::AccountNonceApi, + AuraId: AuraIdT + Sync, + StartConsensus: self::StartConsensus + 'static, +{ + type Block = Block; + type RuntimeApi = RuntimeApi; + type BuildImportQueue = BuildRelayToAuraImportQueue; + type BuildRpcExtensions = BuildParachainRpcExtensions; + type StartConsensus = StartConsensus; + const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Resistant; +} + +pub fn new_aura_node_spec( + extra_args: &NodeExtraArgs, +) -> Box +where + Block: NodeBlock, + RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi::RuntimeApi: AuraRuntimeApi + + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + + substrate_frame_rpc_system::AccountNonceApi, + AuraId: AuraIdT + Sync, +{ + if extra_args.use_slot_based_consensus { + Box::new(AuraNode::< + Block, + RuntimeApi, + AuraId, + StartSlotBasedAuraConsensus, + >::default()) + } else { + Box::new(AuraNode::< + Block, + RuntimeApi, + AuraId, + StartLookaheadAuraConsensus, + >::default()) + } +} + +/// Start relay-chain consensus that is free for all. Everyone can submit a block, the relay-chain +/// decides what is backed and included. +pub(crate) struct StartRelayChainConsensus; + +impl StartConsensus, FakeRuntimeApi> for StartRelayChainConsensus { + fn start_consensus( + client: Arc, FakeRuntimeApi>>, + block_import: ParachainBlockImport, FakeRuntimeApi>, + prometheus_registry: Option<&Registry>, + telemetry: Option, + task_manager: &TaskManager, + relay_chain_interface: Arc, + transaction_pool: Arc, ParachainClient, FakeRuntimeApi>>>, + _keystore: KeystorePtr, + _relay_chain_slot_duration: Duration, + para_id: ParaId, + collator_key: CollatorPair, + overseer_handle: OverseerHandle, + announce_block: Arc>) + Send + Sync>, + _backend: Arc>>, + _node_extra_args: NodeExtraArgs, + ) -> Result<(), 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. +pub(crate) struct StartSlotBasedAuraConsensus( + PhantomData<(Block, RuntimeApi, AuraId)>, +); + +impl, RuntimeApi, AuraId> + StartSlotBasedAuraConsensus +where + RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi::RuntimeApi: AuraRuntimeApi, + AuraId: AuraIdT + Sync, +{ + #[docify::export_content] + fn launch_slot_based_collator( + params: SlotBasedParams< + ParachainBlockImport, + CIDP, + ParachainClient, + ParachainBackend, + Arc, + CHP, + Proposer, + CS, + >, + task_manager: &TaskManager, + ) where + CIDP: CreateInherentDataProviders + 'static, + CIDP::InherentDataProviders: Send, + CHP: cumulus_client_consensus_common::ValidationCodeHashProvider + Send + 'static, + Proposer: ProposerInterface + Send + Sync + 'static, + CS: CollatorServiceInterface + Send + Sync + Clone + 'static, + { + let (collation_future, block_builder_future) = + slot_based::run::::Pair, _, _, _, _, _, _, _, _>(params); + + task_manager.spawn_essential_handle().spawn( + "collation-task", + Some("parachain-block-authoring"), + collation_future, + ); + task_manager.spawn_essential_handle().spawn( + "block-builder-task", + Some("parachain-block-authoring"), + block_builder_future, + ); + } +} + +impl, RuntimeApi, AuraId> StartConsensus + for StartSlotBasedAuraConsensus +where + RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi::RuntimeApi: AuraRuntimeApi, + AuraId: AuraIdT + Sync, +{ + fn start_consensus( + client: Arc>, + block_import: ParachainBlockImport, + prometheus_registry: Option<&Registry>, + telemetry: Option, + task_manager: &TaskManager, + relay_chain_interface: Arc, + transaction_pool: Arc>>, + keystore: KeystorePtr, + relay_chain_slot_duration: Duration, + para_id: ParaId, + collator_key: CollatorPair, + _overseer_handle: OverseerHandle, + announce_block: Arc>) + Send + Sync>, + backend: Arc>, + _node_extra_args: NodeExtraArgs, + ) -> Result<(), Error> { + 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 client_for_aura = client.clone(); + let params = SlotBasedParams { + create_inherent_data_providers: move |_, ()| async move { Ok(()) }, + block_import, + para_client: client.clone(), + para_backend: backend.clone(), + relay_client: relay_chain_interface, + code_hash_provider: move |block_hash| { + client_for_aura.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) + }, + keystore, + collator_key, + para_id, + relay_chain_slot_duration, + proposer, + collator_service, + authoring_duration: Duration::from_millis(2000), + reinitialize: false, + slot_drift: Duration::from_secs(1), + }; + + // We have a separate function only to be able to use `docify::export` on this piece of + // code. + Self::launch_slot_based_collator(params, task_manager); + + Ok(()) + } +} + +/// Wait for the Aura runtime API to appear on chain. +/// This is useful for chains that started out without Aura. Components that +/// are depending on Aura functionality will wait until Aura appears in the runtime. +async fn wait_for_aura( + client: Arc>, +) where + RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi::RuntimeApi: AuraRuntimeApi, + AuraId: AuraIdT + Sync, +{ + let finalized_hash = client.chain_info().finalized_hash; + if client.runtime_api().has_aura_api(finalized_hash) { + return; + }; + + let mut stream = client.finality_notification_stream(); + while let Some(notification) = stream.next().await { + if client.runtime_api().has_aura_api(notification.hash) { + return; + } + } +} + +/// Start consensus using the lookahead aura collator. +pub(crate) struct StartLookaheadAuraConsensus( + PhantomData<(Block, RuntimeApi, AuraId)>, +); + +impl, RuntimeApi, AuraId> StartConsensus + for StartLookaheadAuraConsensus +where + RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi::RuntimeApi: AuraRuntimeApi, + AuraId: AuraIdT + Sync, +{ + fn start_consensus( + client: Arc>, + block_import: ParachainBlockImport, + prometheus_registry: Option<&Registry>, + telemetry: Option, + task_manager: &TaskManager, + relay_chain_interface: Arc, + transaction_pool: Arc>>, + keystore: KeystorePtr, + relay_chain_slot_duration: Duration, + para_id: ParaId, + collator_key: CollatorPair, + overseer_handle: OverseerHandle, + announce_block: Arc>) + Send + Sync>, + backend: Arc>, + node_extra_args: NodeExtraArgs, + ) -> Result<(), 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 = aura::ParamsWithExport { + export_pov: node_extra_args.export_pov, + 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: { + let client = client.clone(); + move |block_hash| { + client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) + } + }, + keystore, + collator_key, + para_id, + overseer_handle, + relay_chain_slot_duration, + proposer: Proposer::new(proposer_factory), + collator_service, + authoring_duration: Duration::from_millis(2000), + reinitialize: false, + }, + }; + + let fut = async move { + wait_for_aura(client).await; + aura::run_with_export::::Pair, _, _, _, _, _, _, _, _>( + params, + ) + .await; + }; + task_manager.spawn_essential_handle().spawn("aura", None, fut); + + Ok(()) + } +} diff --git a/cumulus/polkadot-parachain/tests/benchmark_storage_works.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/benchmark_storage_works.rs similarity index 100% rename from cumulus/polkadot-parachain/tests/benchmark_storage_works.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/benchmark_storage_works.rs diff --git a/cumulus/polkadot-parachain/tests/common.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/common.rs similarity index 100% rename from cumulus/polkadot-parachain/tests/common.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/common.rs diff --git a/cumulus/polkadot-parachain/tests/polkadot_argument_parsing.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_argument_parsing.rs similarity index 100% rename from cumulus/polkadot-parachain/tests/polkadot_argument_parsing.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_argument_parsing.rs diff --git a/cumulus/polkadot-parachain/tests/polkadot_mdns_issue.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_mdns_issue.rs similarity index 100% rename from cumulus/polkadot-parachain/tests/polkadot_mdns_issue.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_mdns_issue.rs diff --git a/cumulus/polkadot-parachain/tests/purge_chain_works.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/purge_chain_works.rs similarity index 100% rename from cumulus/polkadot-parachain/tests/purge_chain_works.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/purge_chain_works.rs diff --git a/cumulus/polkadot-parachain/tests/running_the_node_and_interrupt.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/running_the_node_and_interrupt.rs similarity index 100% rename from cumulus/polkadot-parachain/tests/running_the_node_and_interrupt.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/running_the_node_and_interrupt.rs diff --git a/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs index af5bccdc416f..f6bf6375a353 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs @@ -14,13 +14,11 @@ // 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, Extensions, GenericChainSpec, - SAFE_XCM_VERSION, -}; +use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION}; use cumulus_primitives_core::ParaId; use hex_literal::hex; use parachains_common::{AccountId, AuraId, Balance as AssetHubBalance}; +use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; use sp_core::{crypto::UncheckedInto, sr25519}; diff --git a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs index 3b7376d045b8..754bd851b40a 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -14,9 +14,10 @@ // 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, GenericChainSpec}; +use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed}; use cumulus_primitives_core::ParaId; use parachains_common::Balance as BridgeHubBalance; +use polkadot_parachain_lib::chain_spec::GenericChainSpec; use sc_chain_spec::ChainSpec; use sp_core::sr25519; use std::str::FromStr; @@ -129,8 +130,9 @@ 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, GenericChainSpec, SAFE_XCM_VERSION}; + use crate::chain_spec::SAFE_XCM_VERSION; use parachains_common::{AccountId, AuraId}; + use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; use super::BridgeHubBalance; @@ -254,8 +256,9 @@ pub mod kusama { /// 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 crate::chain_spec::SAFE_XCM_VERSION; use parachains_common::{AccountId, AuraId}; + use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; use super::BridgeHubBalance; diff --git a/cumulus/polkadot-parachain/src/chain_spec/collectives.rs b/cumulus/polkadot-parachain/src/chain_spec/collectives.rs index c0a9f195d89b..865a2a917086 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/collectives.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/collectives.rs @@ -14,12 +14,10 @@ // 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, Extensions, GenericChainSpec, - SAFE_XCM_VERSION, -}; +use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION}; use cumulus_primitives_core::ParaId; use parachains_common::{AccountId, AuraId, Balance as CollectivesBalance}; +use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; use sp_core::sr25519; diff --git a/cumulus/polkadot-parachain/src/chain_spec/contracts.rs b/cumulus/polkadot-parachain/src/chain_spec/contracts.rs deleted file mode 100644 index 4e89b81d1be4..000000000000 --- a/cumulus/polkadot-parachain/src/chain_spec/contracts.rs +++ /dev/null @@ -1,241 +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 . - -use crate::chain_spec::{ - 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, AuraId}; -use sc_service::ChainType; -use sp_core::{crypto::UncheckedInto, sr25519}; - -/// 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 = - testnet_parachains_constants::rococo::currency::EXISTENTIAL_DEPOSIT; - -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()); - - 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() -> GenericChainSpec { - let mut properties = sc_chain_spec::Properties::new(); - properties.insert("tokenSymbol".into(), "ROC".into()); - properties.insert("tokenDecimals".into(), 12.into()); - - 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() -> 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()); - - 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"), - "/dns/contracts-collator-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWPEXYrz8tHU3nDtPoPw4V7ou5dzMEWSTuUj7vaWiYVAVh" - .parse() - .expect("MultiaddrWithPeerId"), - "/dns/contracts-collator-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWEVU8AFNary4nP4qEnEcwJaRuy59Wefekzdu9pKbnVEhk" - .parse() - .expect("MultiaddrWithPeerId"), - "/dns/contracts-collator-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP6pV3ZmcXzGDjv8ZMgA6nZxfAKDxSz4VNiLx6vVCQgJX" - .parse() - .expect("MultiaddrWithPeerId"), - ]) - .with_properties(properties) - .build() -} - -fn contracts_rococo_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, 1u64 << 60)).collect::>(), - }, - "parachainInfo": { - "parachainId": id, - }, - "collatorSelection": { - "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), - "candidacyBond": CONTRACTS_ROCOCO_ED * 16, - }, - "session": { - "keys": invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - contracts_rococo_runtime::SessionKeys { 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. - "polkadotXcm": { - "safeXcmVersion": Some(SAFE_XCM_VERSION), - }, - "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 index fe60b09fd8b2..fec3f56e6d35 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/coretime.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs @@ -14,8 +14,8 @@ // 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 polkadot_parachain_lib::chain_spec::GenericChainSpec; use sc_chain_spec::{ChainSpec, ChainType}; use std::{borrow::Cow, str::FromStr}; @@ -107,8 +107,9 @@ impl CoretimeRuntimeType { CoretimeRuntimeType::Kusama => Ok(Box::new(GenericChainSpec::from_json_bytes( &include_bytes!("../../chain-specs/coretime-kusama.json")[..], )?)), - CoretimeRuntimeType::Polkadot => - todo!("Generate chain-spec: ../../chain-specs/coretime-polkadot.json"), + CoretimeRuntimeType::Polkadot => Ok(Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/coretime-polkadot.json")[..], + )?)), CoretimeRuntimeType::Rococo => Ok(Box::new(GenericChainSpec::from_json_bytes( &include_bytes!("../../chain-specs/coretime-rococo.json")[..], )?)), @@ -144,11 +145,12 @@ pub fn chain_type_name(chain_type: &ChainType) -> Cow { /// Sub-module for Rococo setup. pub mod rococo { - use super::{chain_type_name, CoretimeRuntimeType, GenericChainSpec, ParaId}; + use super::{chain_type_name, CoretimeRuntimeType, ParaId}; 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, SAFE_XCM_VERSION, }; use parachains_common::{AccountId, AuraId, Balance}; + use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; use sp_core::sr25519; @@ -243,9 +245,10 @@ pub mod rococo { 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, + get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, }; use parachains_common::{AccountId, AuraId, Balance}; + use polkadot_parachain_lib::chain_spec::Extensions; use sp_core::sr25519; pub(crate) const CORETIME_WESTEND: &str = "coretime-westend"; diff --git a/cumulus/polkadot-parachain/src/chain_spec/glutton.rs b/cumulus/polkadot-parachain/src/chain_spec/glutton.rs index 77a4123b13ee..136411b93e8b 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/glutton.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/glutton.rs @@ -14,9 +14,10 @@ // 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, GenericChainSpec}; +use crate::chain_spec::get_account_id_from_seed; use cumulus_primitives_core::ParaId; use parachains_common::AuraId; +use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; use sp_core::sr25519; diff --git a/cumulus/polkadot-parachain/src/chain_spec/mod.rs b/cumulus/polkadot-parachain/src/chain_spec/mod.rs index 19047b073b05..82aec951704f 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/mod.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/mod.rs @@ -14,16 +14,21 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . +use cumulus_primitives_core::ParaId; use parachains_common::{AccountId, Signature}; -use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; -use serde::{Deserialize, Serialize}; +use polkadot_parachain_lib::{ + chain_spec::{GenericChainSpec, LoadSpec}, + runtime::{ + AuraConsensusId, BlockNumber, Consensus, Runtime, RuntimeResolver as RuntimeResolverT, + }, +}; +use sc_chain_spec::ChainSpec; use sp_core::{Pair, Public}; use sp_runtime::traits::{IdentifyAccount, Verify}; pub mod asset_hubs; pub mod bridge_hubs; pub mod collectives; -pub mod contracts; pub mod coretime; pub mod glutton; pub mod penpal; @@ -35,27 +40,6 @@ pub mod shell; /// The default XCM version to set in genesis config. const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; -/// Generic extensions for Parachain ChainSpecs. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)] -pub struct Extensions { - /// The relay chain of the Parachain. - #[serde(alias = "relayChain", alias = "RelayChain")] - pub relay_chain: String, - /// The id of the Parachain. - #[serde(alias = "paraId", alias = "ParaId")] - 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()) - } -} - -/// Generic chain spec for all polkadot-parachain runtimes -pub type GenericChainSpec = sc_service::GenericChainSpec; - /// Helper function to generate a crypto pair from seed pub fn get_from_seed(seed: &str) -> ::Public { TPublic::Pair::from_string(&format!("//{}", seed), None) @@ -80,21 +64,316 @@ pub fn get_collator_keys_from_seed(seed: &str) -> (seed) } +/// Extracts the normalized chain id and parachain id from the input chain id. +/// (H/T to Phala for the idea) +/// E.g. "penpal-kusama-2004" yields ("penpal-kusama", Some(2004)) +fn extract_parachain_id<'a>( + id: &'a str, + para_prefixes: &[&str], +) -> (&'a str, &'a str, Option) { + for para_prefix in para_prefixes { + if let Some(suffix) = id.strip_prefix(para_prefix) { + let para_id: u32 = suffix.parse().expect("Invalid parachain-id suffix"); + return (&id[..para_prefix.len() - 1], id, Some(para_id.into())); + } + } + + (id, id, None) +} + +#[derive(Debug)] +pub(crate) struct ChainSpecLoader; + +impl LoadSpec for ChainSpecLoader { + fn load_spec(&self, id: &str) -> Result, String> { + Ok(match id { + // - Default-like + "staging" => Box::new(rococo_parachain::staging_rococo_parachain_local_config()), + "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(shell::get_shell_chain_spec()), + "seedling" => Box::new(seedling::get_seedling_chain_spec()), + + // -- Asset Hub Polkadot + "asset-hub-polkadot" | "statemint" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/asset-hub-polkadot.json")[..], + )?), + + // -- Asset Hub Kusama + "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" => Box::new(asset_hubs::asset_hub_rococo_development_config()), + "asset-hub-rococo-local" => Box::new(asset_hubs::asset_hub_rococo_local_config()), + // the chain spec as used for generating the upgrade genesis values + "asset-hub-rococo-genesis" => Box::new(asset_hubs::asset_hub_rococo_genesis_config()), + "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" => + Box::new(asset_hubs::asset_hub_westend_development_config()), + "asset-hub-westend-local" | "westmint-local" => + Box::new(asset_hubs::asset_hub_westend_local_config()), + // the chain spec as used for generating the upgrade genesis values + "asset-hub-westend-genesis" | "westmint-genesis" => + Box::new(asset_hubs::asset_hub_westend_config()), + // the shell-based chain spec as used for syncing + "asset-hub-westend" | "westmint" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/asset-hub-westend.json")[..], + )?), + + // -- Polkadot Collectives + "collectives-polkadot" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/collectives-polkadot.json")[..], + )?), + + // -- Westend Collectives + "collectives-westend-dev" => + Box::new(collectives::collectives_westend_development_config()), + "collectives-westend-local" => + Box::new(collectives::collectives_westend_local_config()), + "collectives-westend" => Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/collectives-westend.json")[..], + )?), + + // -- BridgeHub + bridge_like_id + if bridge_like_id.starts_with(bridge_hubs::BridgeHubRuntimeType::ID_PREFIX) => + bridge_like_id + .parse::() + .expect("invalid value") + .load_config()?, + + // -- Coretime + coretime_like_id + if coretime_like_id.starts_with(coretime::CoretimeRuntimeType::ID_PREFIX) => + coretime_like_id + .parse::() + .expect("invalid value") + .load_config()?, + + // -- Penpal + id if id.starts_with("penpal-rococo") => { + let (_, _, para_id) = extract_parachain_id(&id, &["penpal-rococo-"]); + Box::new(penpal::get_penpal_chain_spec( + para_id.expect("Must specify parachain id"), + "rococo-local", + )) + }, + id if id.starts_with("penpal-westend") => { + let (_, _, para_id) = extract_parachain_id(&id, &["penpal-westend-"]); + Box::new(penpal::get_penpal_chain_spec( + para_id.expect("Must specify parachain id"), + "westend-local", + )) + }, + + // -- Glutton Westend + id if id.starts_with("glutton-westend-dev") => { + let (_, _, para_id) = extract_parachain_id(&id, &["glutton-westend-dev-"]); + Box::new(glutton::glutton_westend_development_config( + para_id.expect("Must specify parachain id"), + )) + }, + id if id.starts_with("glutton-westend-local") => { + let (_, _, para_id) = extract_parachain_id(&id, &["glutton-westend-local-"]); + Box::new(glutton::glutton_westend_local_config( + para_id.expect("Must specify parachain id"), + )) + }, + // the chain spec as used for generating the upgrade genesis values + id if id.starts_with("glutton-westend-genesis") => { + let (_, _, para_id) = extract_parachain_id(&id, &["glutton-westend-genesis-"]); + Box::new(glutton::glutton_westend_config( + para_id.expect("Must specify parachain id"), + )) + }, + + // -- People + people_like_id if people_like_id.starts_with(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"); + Box::new(rococo_parachain::rococo_parachain_local_config()) + }, + + // -- Loading a specific spec from disk + path => Box::new(GenericChainSpec::from_json_file(path.into())?), + }) + } +} + +/// Helper enum that is used for better distinction of different parachain/runtime configuration +/// (it is based/calculated on ChainSpec's ID attribute) +#[derive(Debug, PartialEq)] +enum LegacyRuntime { + Omni, + Shell, + Seedling, + AssetHubPolkadot, + AssetHub, + Penpal, + Collectives, + Glutton, + BridgeHub(bridge_hubs::BridgeHubRuntimeType), + Coretime(coretime::CoretimeRuntimeType), + People(people::PeopleRuntimeType), +} + +impl LegacyRuntime { + fn from_id(id: &str) -> LegacyRuntime { + let id = id.replace('_', "-"); + + if id.starts_with("shell") { + LegacyRuntime::Shell + } else if id.starts_with("seedling") { + LegacyRuntime::Seedling + } else if id.starts_with("asset-hub-polkadot") | id.starts_with("statemint") { + LegacyRuntime::AssetHubPolkadot + } else if id.starts_with("asset-hub-kusama") | + id.starts_with("statemine") | + id.starts_with("asset-hub-rococo") | + id.starts_with("rockmine") | + id.starts_with("asset-hub-westend") | + id.starts_with("westmint") + { + LegacyRuntime::AssetHub + } else if id.starts_with("penpal") { + LegacyRuntime::Penpal + } else if id.starts_with("collectives-polkadot") || id.starts_with("collectives-westend") { + LegacyRuntime::Collectives + } else if id.starts_with(bridge_hubs::BridgeHubRuntimeType::ID_PREFIX) { + LegacyRuntime::BridgeHub( + id.parse::().expect("Invalid value"), + ) + } else if id.starts_with(coretime::CoretimeRuntimeType::ID_PREFIX) { + LegacyRuntime::Coretime( + id.parse::().expect("Invalid value"), + ) + } else if id.starts_with("glutton") { + LegacyRuntime::Glutton + } else if id.starts_with(people::PeopleRuntimeType::ID_PREFIX) { + LegacyRuntime::People(id.parse::().expect("Invalid value")) + } else { + log::warn!( + "No specific runtime was recognized for ChainSpec's id: '{}', \ + so Runtime::Omni(Consensus::Aura) will be used", + id + ); + LegacyRuntime::Omni + } + } +} + +#[derive(Debug)] +pub(crate) struct RuntimeResolver; + +impl RuntimeResolverT for RuntimeResolver { + fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result { + let legacy_runtime = LegacyRuntime::from_id(chain_spec.id()); + Ok(match legacy_runtime { + LegacyRuntime::AssetHubPolkadot => + Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Ed25519)), + LegacyRuntime::AssetHub | + LegacyRuntime::BridgeHub(_) | + LegacyRuntime::Collectives | + LegacyRuntime::Coretime(_) | + LegacyRuntime::People(_) | + LegacyRuntime::Glutton | + LegacyRuntime::Penpal | + LegacyRuntime::Omni => + Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519)), + LegacyRuntime::Shell | LegacyRuntime::Seedling => Runtime::Shell, + }) + } +} + #[cfg(test)] mod tests { use super::*; + use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup, ChainType, Extension}; + use serde::{Deserialize, Serialize}; + use sp_core::sr25519; + + #[derive( + Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension, Default, + )] + #[serde(deny_unknown_fields)] + pub struct Extensions1 { + pub attribute1: String, + pub attribute2: u32, + } + + #[derive( + Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension, Default, + )] + #[serde(deny_unknown_fields)] + pub struct Extensions2 { + pub attribute_x: String, + pub attribute_y: String, + pub attribute_z: u32, + } + + pub type DummyChainSpec = sc_service::GenericChainSpec; + + pub fn create_default_with_extensions( + id: &str, + extension: E, + ) -> DummyChainSpec { + 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] - fn can_decode_extension_camel_and_snake_case() { - let camel_case = r#"{"relayChain":"relay","paraId":1}"#; - let snake_case = r#"{"relay_chain":"relay","para_id":1}"#; - let pascal_case = r#"{"RelayChain":"relay","ParaId":1}"#; + fn test_legacy_runtime_for_different_chain_specs() { + let chain_spec = create_default_with_extensions("shell-1", Extensions1::default()); + assert_eq!(LegacyRuntime::Shell, LegacyRuntime::from_id(chain_spec.id())); + + let chain_spec = create_default_with_extensions("shell-2", Extensions2::default()); + assert_eq!(LegacyRuntime::Shell, LegacyRuntime::from_id(chain_spec.id())); + + let chain_spec = create_default_with_extensions("seedling", Extensions2::default()); + assert_eq!(LegacyRuntime::Seedling, LegacyRuntime::from_id(chain_spec.id())); - let camel_case_extension: Extensions = serde_json::from_str(camel_case).unwrap(); - let snake_case_extension: Extensions = serde_json::from_str(snake_case).unwrap(); - let pascal_case_extension: Extensions = serde_json::from_str(pascal_case).unwrap(); + let chain_spec = + create_default_with_extensions("penpal-rococo-1000", Extensions2::default()); + assert_eq!(LegacyRuntime::Penpal, LegacyRuntime::from_id(chain_spec.id())); - assert_eq!(camel_case_extension, snake_case_extension); - assert_eq!(snake_case_extension, pascal_case_extension); + let chain_spec = crate::chain_spec::rococo_parachain::rococo_parachain_local_config(); + assert_eq!(LegacyRuntime::Omni, LegacyRuntime::from_id(chain_spec.id())); } } diff --git a/cumulus/polkadot-parachain/src/chain_spec/penpal.rs b/cumulus/polkadot-parachain/src/chain_spec/penpal.rs index cb1cb632d638..5645bf06b67b 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/penpal.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/penpal.rs @@ -14,12 +14,10 @@ // 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, Extensions, GenericChainSpec, - SAFE_XCM_VERSION, -}; +use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION}; use cumulus_primitives_core::ParaId; use parachains_common::{AccountId, AuraId}; +use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; use sp_core::sr25519; diff --git a/cumulus/polkadot-parachain/src/chain_spec/people.rs b/cumulus/polkadot-parachain/src/chain_spec/people.rs index 6100a15018cb..3c1150d95422 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/people.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/people.rs @@ -14,9 +14,9 @@ // 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 polkadot_parachain_lib::chain_spec::GenericChainSpec; use sc_chain_spec::ChainSpec; use std::str::FromStr; @@ -121,10 +121,10 @@ fn ensure_id(id: &str) -> Result<&str, String> { 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, + get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, }; use parachains_common::{AccountId, AuraId}; + use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; use sp_core::sr25519; @@ -231,10 +231,10 @@ pub mod rococo { 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, + get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, }; use parachains_common::{AccountId, AuraId}; + use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; use sp_core::sr25519; diff --git a/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs b/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs index 0434e5f7be8f..9f4a162e67f8 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs @@ -16,10 +16,11 @@ //! ChainSpecs dedicated to Rococo parachain setups (for testing and example purposes) -use crate::chain_spec::{get_from_seed, Extensions, GenericChainSpec, SAFE_XCM_VERSION}; +use crate::chain_spec::{get_from_seed, SAFE_XCM_VERSION}; use cumulus_primitives_core::ParaId; use hex_literal::hex; use parachains_common::AccountId; +use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use polkadot_service::chain_spec::get_account_id_from_seed; use rococo_parachain_runtime::AuraId; use sc_chain_spec::ChainType; diff --git a/cumulus/polkadot-parachain/src/chain_spec/seedling.rs b/cumulus/polkadot-parachain/src/chain_spec/seedling.rs index 32d516220545..a104b58db5d2 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/seedling.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/seedling.rs @@ -14,9 +14,10 @@ // 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, GenericChainSpec}; +use crate::chain_spec::get_account_id_from_seed; use cumulus_primitives_core::ParaId; use parachains_common::{AccountId, AuraId}; +use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; use sp_core::sr25519; diff --git a/cumulus/polkadot-parachain/src/chain_spec/shell.rs b/cumulus/polkadot-parachain/src/chain_spec/shell.rs index e0a9875fb96f..0a7816ab3193 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/shell.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/shell.rs @@ -14,9 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::chain_spec::{Extensions, GenericChainSpec}; use cumulus_primitives_core::ParaId; use parachains_common::AuraId; +use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; use super::get_collator_keys_from_seed; diff --git a/cumulus/polkadot-parachain/src/cli.rs b/cumulus/polkadot-parachain/src/cli.rs deleted file mode 100644 index a5fe33dffc96..000000000000 --- a/cumulus/polkadot-parachain/src/cli.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 . - -use crate::common::NodeExtraArgs; -use clap::{Command, CommandFactory, FromArgMatches}; -use sc_cli::SubstrateCli; -use std::path::PathBuf; - -/// Sub-commands supported by the collator. -#[derive(Debug, clap::Subcommand)] -pub enum Subcommand { - /// Key management CLI utilities - #[command(subcommand)] - Key(sc_cli::KeySubcommand), - - /// Build a chain specification. - BuildSpec(sc_cli::BuildSpecCmd), - - /// Validate blocks. - CheckBlock(sc_cli::CheckBlockCmd), - - /// Export blocks. - ExportBlocks(sc_cli::ExportBlocksCmd), - - /// Export the state of a given block into a chain spec. - ExportState(sc_cli::ExportStateCmd), - - /// Import blocks. - ImportBlocks(sc_cli::ImportBlocksCmd), - - /// Revert the chain to a previous state. - Revert(sc_cli::RevertCmd), - - /// Remove the whole chain. - PurgeChain(cumulus_client_cli::PurgeChainCmd), - - /// Export the genesis state of the parachain. - #[command(alias = "export-genesis-state")] - ExportGenesisHead(cumulus_client_cli::ExportGenesisHeadCommand), - - /// Export the genesis wasm of the parachain. - ExportGenesisWasm(cumulus_client_cli::ExportGenesisWasmCommand), - - /// Sub-commands concerned with benchmarking. - /// The pallet benchmarking moved to the `pallet` sub-command. - #[command(subcommand)] - Benchmark(frame_benchmarking_cli::BenchmarkCmd), -} - -#[derive(Debug, clap::Parser)] -#[command( - propagate_version = true, - args_conflicts_with_subcommands = true, - subcommand_negates_reqs = true, - after_help = crate::examples(Self::executable_name()) -)] -pub struct Cli { - #[command(subcommand)] - pub subcommand: Option, - - #[command(flatten)] - pub run: cumulus_client_cli::RunCmd, - - /// EXPERIMENTAL: Use slot-based collator which can handle elastic scaling. - /// - /// Use with care, this flag is unstable and subject to change. - #[arg(long)] - pub experimental_use_slot_based: bool, - - /// Disable automatic hardware benchmarks. - /// - /// By default these benchmarks are automatically ran at startup and measure - /// the CPU speed, the memory bandwidth and the disk speed. - /// - /// The results are then printed out in the logs, and also sent as part of - /// telemetry, if telemetry is enabled. - #[arg(long)] - pub no_hardware_benchmarks: bool, - - /// Export all `PoVs` build by this collator to the given folder. - /// - /// This is useful for debugging issues that are occurring while validating these `PoVs` on the - /// relay chain. - #[arg(long)] - pub export_pov_to_path: Option, - - /// Relay chain arguments - #[arg(raw = true)] - pub relay_chain_args: Vec, -} - -impl Cli { - pub(crate) fn node_extra_args(&self) -> NodeExtraArgs { - NodeExtraArgs { - use_slot_based_consensus: self.experimental_use_slot_based, - export_pov: self.export_pov_to_path.clone(), - } - } -} - -#[derive(Debug)] -pub struct RelayChainCli { - /// The actual relay chain cli object. - pub base: polkadot_cli::RunCmd, - - /// Optional chain id that should be passed to the relay chain. - pub chain_id: Option, - - /// The base path that should be used by the relay chain. - pub base_path: Option, -} - -impl RelayChainCli { - fn polkadot_cmd() -> Command { - let help_template = color_print::cformat!( - "The arguments that are passed to the relay chain node. \n\ - \n\ - RELAY_CHAIN_ARGS: \n\ - {{options}}", - ); - - polkadot_cli::RunCmd::command() - .no_binary_name(true) - .help_template(help_template) - } - - /// Parse the relay chain CLI parameters using the parachain `Configuration`. - pub fn new<'a>( - para_config: &sc_service::Configuration, - relay_chain_args: impl Iterator, - ) -> Self { - let polkadot_cmd = Self::polkadot_cmd(); - let matches = polkadot_cmd.get_matches_from(relay_chain_args); - let base = FromArgMatches::from_arg_matches(&matches).unwrap_or_else(|e| e.exit()); - - let extension = crate::chain_spec::Extensions::try_get(&*para_config.chain_spec); - let chain_id = extension.map(|e| e.relay_chain.clone()); - - let base_path = para_config.base_path.path().join("polkadot"); - Self { base, chain_id, base_path: Some(base_path) } - } -} diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs deleted file mode 100644 index 3df90c889e8e..000000000000 --- a/cumulus/polkadot-parachain/src/command.rs +++ /dev/null @@ -1,856 +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 . - -#[cfg(feature = "runtime-benchmarks")] -use crate::service::Block; -use crate::{ - chain_spec::{self, GenericChainSpec}, - cli::{Cli, RelayChainCli, Subcommand}, - common::NodeExtraArgs, - fake_runtime_api::{ - asset_hub_polkadot_aura::RuntimeApi as AssetHubPolkadotRuntimeApi, - aura::RuntimeApi as AuraRuntimeApi, - }, - service::{new_aura_node_spec, DynNodeSpec, ShellNode}, -}; -#[cfg(feature = "runtime-benchmarks")] -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; -use parachains_common::{AssetHubPolkadotAuraId, AuraId}; -use sc_cli::{ - ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, - NetworkParams, Result, SharedParams, SubstrateCli, -}; -use sc_service::config::{BasePath, PrometheusConfig}; -use sp_runtime::traits::AccountIdConversion; -#[cfg(feature = "runtime-benchmarks")] -use sp_runtime::traits::HashingFor; -use std::{net::SocketAddr, path::PathBuf}; - -/// The choice of consensus for the parachain omni-node. -#[derive(PartialEq, Eq, Debug, Default)] -pub enum Consensus { - /// Aura consensus. - #[default] - Aura, - /// Use the relay chain consensus. - // TODO: atm this is just a demonstration, not really reach-able. We can add it to the CLI, - // env, or the chain spec. Or, just don't, and when we properly refactor this mess we will - // re-introduce it. - #[allow(unused)] - Relay, -} - -/// Helper enum that is used for better distinction of different parachain/runtime configuration -/// (it is based/calculated on ChainSpec's ID attribute) -#[derive(Debug, PartialEq)] -enum Runtime { - /// None of the system-chain runtimes, rather the node will act agnostic to the runtime ie. be - /// an omni-node, and simply run a node with the given consensus algorithm. - Omni(Consensus), - Shell, - Seedling, - AssetHubPolkadot, - AssetHub, - Penpal(ParaId), - ContractsRococo, - Collectives, - Glutton, - BridgeHub(chain_spec::bridge_hubs::BridgeHubRuntimeType), - Coretime(chain_spec::coretime::CoretimeRuntimeType), - People(chain_spec::people::PeopleRuntimeType), -} - -trait RuntimeResolver { - fn runtime(&self) -> Result; -} - -impl RuntimeResolver for dyn ChainSpec { - 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) -> Result { - #[derive(Debug, serde::Deserialize)] - struct EmptyChainSpecWithId { - id: String, - } - - let file = std::fs::File::open(self)?; - let reader = std::io::BufReader::new(file); - let chain_spec: EmptyChainSpecWithId = - serde_json::from_reader(reader).map_err(|e| sc_cli::Error::Application(Box::new(e)))?; - - Ok(runtime(&chain_spec.id)) - } -} - -fn runtime(id: &str) -> Runtime { - let id = id.replace('_', "-"); - let (_, id, para_id) = extract_parachain_id(&id); - - if id.starts_with("shell") { - Runtime::Shell - } else if id.starts_with("seedling") { - Runtime::Seedling - } else if id.starts_with("asset-hub-polkadot") | id.starts_with("statemint") { - Runtime::AssetHubPolkadot - } else if id.starts_with("asset-hub-kusama") | - id.starts_with("statemine") | - id.starts_with("asset-hub-rococo") | - id.starts_with("rockmine") | - id.starts_with("asset-hub-westend") | - id.starts_with("westmint") - { - Runtime::AssetHub - } else if id.starts_with("penpal") { - Runtime::Penpal(para_id.unwrap_or(ParaId::new(0))) - } else if id.starts_with("contracts-rococo") { - Runtime::ContractsRococo - } else if id.starts_with("collectives-polkadot") || id.starts_with("collectives-westend") { - Runtime::Collectives - } else if id.starts_with(chain_spec::bridge_hubs::BridgeHubRuntimeType::ID_PREFIX) { - Runtime::BridgeHub( - 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") { - 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::Omni(Consensus::Aura) will be used", - id - ); - Runtime::Omni(Consensus::Aura) - } -} - -fn load_spec(id: &str) -> std::result::Result, String> { - let (id, _, para_id) = extract_parachain_id(id); - Ok(match id { - // - Default-like - "staging" => - Box::new(chain_spec::rococo_parachain::staging_rococo_parachain_local_config()), - "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" | "statemint" => Box::new(GenericChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/asset-hub-polkadot.json")[..], - )?), - - // -- Asset Hub Kusama - "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" => - Box::new(chain_spec::asset_hubs::asset_hub_rococo_development_config()), - "asset-hub-rococo-local" => - Box::new(chain_spec::asset_hubs::asset_hub_rococo_local_config()), - // 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(GenericChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/asset-hub-rococo.json")[..], - )?), - - // -- Asset Hub Westend - "asset-hub-westend-dev" | "westmint-dev" => - Box::new(chain_spec::asset_hubs::asset_hub_westend_development_config()), - "asset-hub-westend-local" | "westmint-local" => - Box::new(chain_spec::asset_hubs::asset_hub_westend_local_config()), - // the chain spec as used for generating the upgrade genesis values - "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(GenericChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/asset-hub-westend.json")[..], - )?), - - // -- Polkadot Collectives - "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" => - Box::new(chain_spec::contracts::contracts_rococo_development_config()), - "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(GenericChainSpec::from_json_bytes( - &include_bytes!("../chain-specs/contracts-rococo.json")[..], - )?), - - // -- BridgeHub - bridge_like_id - if bridge_like_id - .starts_with(chain_spec::bridge_hubs::BridgeHubRuntimeType::ID_PREFIX) => - bridge_like_id - .parse::() - .expect("invalid value") - .load_config()?, - - // -- Coretime - coretime_like_id - if coretime_like_id - .starts_with(chain_spec::coretime::CoretimeRuntimeType::ID_PREFIX) => - coretime_like_id - .parse::() - .expect("invalid value") - .load_config()?, - - // -- Penpal - "penpal-rococo" => Box::new(chain_spec::penpal::get_penpal_chain_spec( - para_id.expect("Must specify parachain id"), - "rococo-local", - )), - "penpal-westend" => Box::new(chain_spec::penpal::get_penpal_chain_spec( - para_id.expect("Must specify parachain id"), - "westend-local", - )), - - // -- Glutton Westend - "glutton-westend-dev" => Box::new(chain_spec::glutton::glutton_westend_development_config( - para_id.expect("Must specify parachain id"), - )), - "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-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"); - Box::new(chain_spec::rococo_parachain::rococo_parachain_local_config()) - }, - - // -- Loading a specific spec from disk - path => Box::new(GenericChainSpec::from_json_file(path.into())?), - }) -} - -/// Extracts the normalized chain id and parachain id from the input chain id. -/// (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) { - let para_prefixes = [ - // Penpal - "penpal-rococo-", - "penpal-westend-", - "penpal-kusama-", - "penpal-polkadot-", - // Glutton Kusama - "glutton-kusama-dev-", - "glutton-kusama-local-", - "glutton-kusama-genesis-", - // Glutton Westend - "glutton-westend-dev-", - "glutton-westend-local-", - "glutton-westend-genesis-", - ]; - - for para_prefix in para_prefixes { - if let Some(suffix) = id.strip_prefix(para_prefix) { - let para_id: u32 = suffix.parse().expect("Invalid parachain-id suffix"); - return (&id[..para_prefix.len() - 1], id, Some(para_id.into())) - } - } - - (id, id, None) -} - -impl SubstrateCli for Cli { - fn impl_name() -> String { - Self::executable_name() - } - - fn impl_version() -> String { - env!("SUBSTRATE_CLI_IMPL_VERSION").into() - } - - fn description() -> String { - format!( - "The command-line arguments provided first will be passed to the parachain node, \n\ - and the arguments provided after -- will be passed to the relay chain node. \n\ - \n\ - Example: \n\ - \n\ - {} [parachain-args] -- [relay-chain-args]", - Self::executable_name() - ) - } - - fn author() -> String { - env!("CARGO_PKG_AUTHORS").into() - } - - fn support_url() -> String { - "https://github.com/paritytech/polkadot-sdk/issues/new".into() - } - - fn copyright_start_year() -> i32 { - 2017 - } - - fn load_spec(&self, id: &str) -> std::result::Result, String> { - load_spec(id) - } -} - -impl SubstrateCli for RelayChainCli { - fn impl_name() -> String { - Cli::impl_name() - } - - fn impl_version() -> String { - Cli::impl_version() - } - - fn description() -> String { - Cli::description() - } - - fn author() -> String { - Cli::author() - } - - fn support_url() -> String { - Cli::support_url() - } - - fn copyright_start_year() -> i32 { - Cli::copyright_start_year() - } - - fn load_spec(&self, id: &str) -> std::result::Result, String> { - polkadot_cli::Cli::from_iter([RelayChainCli::executable_name()].iter()).load_spec(id) - } -} - -fn new_node_spec( - config: &sc_service::Configuration, - extra_args: &NodeExtraArgs, -) -> std::result::Result, sc_cli::Error> { - Ok(match config.chain_spec.runtime()? { - Runtime::AssetHubPolkadot => - new_aura_node_spec::(extra_args), - Runtime::AssetHub | - Runtime::BridgeHub(_) | - Runtime::Collectives | - Runtime::Coretime(_) | - Runtime::People(_) | - Runtime::ContractsRococo | - Runtime::Glutton | - Runtime::Penpal(_) => new_aura_node_spec::(extra_args), - Runtime::Shell | Runtime::Seedling => Box::new(ShellNode), - Runtime::Omni(consensus) => match consensus { - Consensus::Aura => new_aura_node_spec::(extra_args), - Consensus::Relay => Box::new(ShellNode), - }, - }) -} - -/// Parse command line arguments into service configuration. -pub fn run() -> Result<()> { - let cli = Cli::from_args(); - - match &cli.subcommand { - Some(Subcommand::BuildSpec(cmd)) => { - let runner = cli.create_runner(cmd)?; - runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) - }, - Some(Subcommand::CheckBlock(cmd)) => { - let runner = cli.create_runner(cmd)?; - runner.async_run(|config| { - let node = new_node_spec(&config, &cli.node_extra_args())?; - node.prepare_check_block_cmd(config, cmd) - }) - }, - Some(Subcommand::ExportBlocks(cmd)) => { - let runner = cli.create_runner(cmd)?; - runner.async_run(|config| { - let node = new_node_spec(&config, &cli.node_extra_args())?; - node.prepare_export_blocks_cmd(config, cmd) - }) - }, - Some(Subcommand::ExportState(cmd)) => { - let runner = cli.create_runner(cmd)?; - runner.async_run(|config| { - let node = new_node_spec(&config, &cli.node_extra_args())?; - node.prepare_export_state_cmd(config, cmd) - }) - }, - Some(Subcommand::ImportBlocks(cmd)) => { - let runner = cli.create_runner(cmd)?; - runner.async_run(|config| { - let node = new_node_spec(&config, &cli.node_extra_args())?; - node.prepare_import_blocks_cmd(config, cmd) - }) - }, - Some(Subcommand::Revert(cmd)) => { - let runner = cli.create_runner(cmd)?; - runner.async_run(|config| { - let node = new_node_spec(&config, &cli.node_extra_args())?; - node.prepare_revert_cmd(config, cmd) - }) - }, - Some(Subcommand::PurgeChain(cmd)) => { - let runner = cli.create_runner(cmd)?; - let polkadot_cli = RelayChainCli::new(runner.config(), cli.relay_chain_args.iter()); - - runner.sync_run(|config| { - let polkadot_config = SubstrateCli::create_configuration( - &polkadot_cli, - &polkadot_cli, - config.tokio_handle.clone(), - ) - .map_err(|err| format!("Relay chain argument error: {}", err))?; - - cmd.run(config, polkadot_config) - }) - }, - Some(Subcommand::ExportGenesisHead(cmd)) => { - let runner = cli.create_runner(cmd)?; - runner.sync_run(|config| { - let node = new_node_spec(&config, &cli.node_extra_args())?; - node.run_export_genesis_head_cmd(config, cmd) - }) - }, - Some(Subcommand::ExportGenesisWasm(cmd)) => { - let runner = cli.create_runner(cmd)?; - runner.sync_run(|_config| { - let spec = cli.load_spec(&cmd.shared_params.chain.clone().unwrap_or_default())?; - cmd.run(&*spec) - }) - }, - Some(Subcommand::Benchmark(cmd)) => { - let runner = cli.create_runner(cmd)?; - - // Switch on the concrete benchmark sub-command- - match cmd { - #[cfg(feature = "runtime-benchmarks")] - BenchmarkCmd::Pallet(cmd) => runner.sync_run(|config| { - cmd.run_with_spec::, ReclaimHostFunctions>(Some( - config.chain_spec, - )) - }), - BenchmarkCmd::Block(cmd) => runner.sync_run(|config| { - let node = new_node_spec(&config, &cli.node_extra_args())?; - node.run_benchmark_block_cmd(config, cmd) - }), - #[cfg(feature = "runtime-benchmarks")] - BenchmarkCmd::Storage(cmd) => runner.sync_run(|config| { - let node = new_node_spec(&config, &cli.node_extra_args())?; - node.run_benchmark_storage_cmd(config, cmd) - }), - BenchmarkCmd::Machine(cmd) => - runner.sync_run(|config| cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone())), - #[allow(unreachable_patterns)] - _ => Err("Benchmarking sub-command unsupported or compilation feature missing. \ - Make sure to compile with --features=runtime-benchmarks \ - to enable all supported benchmarks." - .into()), - } - }, - Some(Subcommand::Key(cmd)) => Ok(cmd.run(&cli)?), - None => { - let runner = cli.create_runner(&cli.run.normalize())?; - let polkadot_cli = RelayChainCli::new(runner.config(), cli.relay_chain_args.iter()); - let collator_options = cli.run.collator_options(); - - runner.run_node_until_exit(|config| async move { - // If Statemint (Statemine, Westmint, Rockmine) DB exists and we're using the - // asset-hub chain spec, then rename the base path to the new chain ID. In the case - // 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, - }; - - 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); - - 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())?; - info!( - "{} was renamed to Asset Hub. The filepath with associated data on disk \ - has been renamed from {} to {}.", - old_name, - old_path.display(), - new_path.display() - ); - } - } - - let hwbench = (!cli.no_hardware_benchmarks) - .then_some(config.database.path().map(|database_path| { - let _ = std::fs::create_dir_all(database_path); - sc_sysinfo::gather_hwbench(Some(database_path)) - })) - .flatten(); - - let para_id = chain_spec::Extensions::try_get(&*config.chain_spec) - .map(|e| e.para_id) - .ok_or("Could not find parachain extension in chain-spec.")?; - - let id = ParaId::from(para_id); - - let parachain_account = - AccountIdConversion::::into_account_truncating( - &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))?; - - info!("🪪 Parachain id: {:?}", id); - info!("🧾 Parachain Account: {}", parachain_account); - info!("✍️ Is collating: {}", if config.role.is_authority() { "yes" } else { "no" }); - - start_node( - config, - polkadot_config, - collator_options, - id, - cli.node_extra_args(), - hwbench, - ) - .await - }) - }, - } -} - -#[sc_tracing::logging::prefix_logs_with("Parachain")] -async fn start_node( - config: sc_service::Configuration, - polkadot_config: sc_service::Configuration, - collator_options: cumulus_client_cli::CollatorOptions, - id: ParaId, - extra_args: NodeExtraArgs, - hwbench: Option, -) -> Result { - let node_spec = new_node_spec(&config, &extra_args)?; - node_spec - .start_node(config, polkadot_config, collator_options, id, hwbench, extra_args) - .await - .map_err(Into::into) -} - -impl DefaultConfigurationValues for RelayChainCli { - fn p2p_listen_port() -> u16 { - 30334 - } - - fn rpc_listen_port() -> u16 { - 9945 - } - - fn prometheus_listen_port() -> u16 { - 9616 - } -} - -impl CliConfiguration for RelayChainCli { - fn shared_params(&self) -> &SharedParams { - self.base.base.shared_params() - } - - fn import_params(&self) -> Option<&ImportParams> { - self.base.base.import_params() - } - - fn network_params(&self) -> Option<&NetworkParams> { - self.base.base.network_params() - } - - fn keystore_params(&self) -> Option<&KeystoreParams> { - self.base.base.keystore_params() - } - - fn base_path(&self) -> Result> { - Ok(self - .shared_params() - .base_path()? - .or_else(|| self.base_path.clone().map(Into::into))) - } - - fn rpc_addr(&self, default_listen_port: u16) -> Result> { - self.base.base.rpc_addr(default_listen_port) - } - - fn prometheus_config( - &self, - default_listen_port: u16, - chain_spec: &Box, - ) -> Result> { - self.base.base.prometheus_config(default_listen_port, chain_spec) - } - - fn init( - &self, - _support_url: &String, - _impl_version: &String, - _logger_hook: F, - _config: &sc_service::Configuration, - ) -> Result<()> - where - F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration), - { - unreachable!("PolkadotCli is never initialized; qed"); - } - - fn chain_id(&self, is_dev: bool) -> Result { - let chain_id = self.base.base.chain_id(is_dev)?; - - Ok(if chain_id.is_empty() { self.chain_id.clone().unwrap_or_default() } else { chain_id }) - } - - fn role(&self, is_dev: bool) -> Result { - self.base.base.role(is_dev) - } - - fn transaction_pool(&self, is_dev: bool) -> Result { - self.base.base.transaction_pool(is_dev) - } - - fn trie_cache_maximum_size(&self) -> Result> { - self.base.base.trie_cache_maximum_size() - } - - fn rpc_methods(&self) -> Result { - self.base.base.rpc_methods() - } - - fn rpc_max_connections(&self) -> Result { - self.base.base.rpc_max_connections() - } - - fn rpc_cors(&self, is_dev: bool) -> Result>> { - self.base.base.rpc_cors(is_dev) - } - - fn default_heap_pages(&self) -> Result> { - self.base.base.default_heap_pages() - } - - fn force_authoring(&self) -> Result { - self.base.base.force_authoring() - } - - fn disable_grandpa(&self) -> Result { - self.base.base.disable_grandpa() - } - - fn max_runtime_instances(&self) -> Result> { - self.base.base.max_runtime_instances() - } - - fn announce_block(&self) -> Result { - self.base.base.announce_block() - } - - fn telemetry_endpoints( - &self, - chain_spec: &Box, - ) -> Result> { - self.base.base.telemetry_endpoints(chain_spec) - } - - fn node_name(&self) -> Result { - self.base.base.node_name() - } -} - -#[cfg(test)] -mod tests { - use crate::{ - chain_spec::{get_account_id_from_seed, get_from_seed}, - command::{Consensus, Runtime, RuntimeResolver}, - }; - use sc_chain_spec::{ChainSpec, ChainSpecExtension, ChainSpecGroup, ChainType, Extension}; - use serde::{Deserialize, Serialize}; - use sp_core::sr25519; - use std::path::PathBuf; - use tempfile::TempDir; - - #[derive( - Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension, Default, - )] - #[serde(deny_unknown_fields)] - pub struct Extensions1 { - pub attribute1: String, - pub attribute2: u32, - } - - #[derive( - Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension, Default, - )] - #[serde(deny_unknown_fields)] - pub struct Extensions2 { - pub attribute_x: String, - pub attribute_y: String, - pub attribute_z: u32, - } - - fn store_configuration(dir: &TempDir, spec: &dyn ChainSpec) -> PathBuf { - let raw_output = true; - let json = sc_service::chain_ops::build_spec(spec, raw_output) - .expect("Failed to build json string"); - let mut cfg_file_path = dir.path().to_path_buf(); - cfg_file_path.push(spec.id()); - cfg_file_path.set_extension("json"); - std::fs::write(&cfg_file_path, json).expect("Failed to write to json file"); - cfg_file_path - } - - pub type DummyChainSpec = sc_service::GenericChainSpec; - - pub fn create_default_with_extensions( - id: &str, - extension: E, - ) -> DummyChainSpec { - 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] - fn test_resolve_runtime_for_different_configuration_files() { - let temp_dir = tempfile::tempdir().expect("Failed to access tempdir"); - - let path = store_configuration( - &temp_dir, - &create_default_with_extensions("shell-1", Extensions1::default()), - ); - assert_eq!(Runtime::Shell, path.runtime().unwrap()); - - let path = store_configuration( - &temp_dir, - &create_default_with_extensions("shell-2", Extensions2::default()), - ); - assert_eq!(Runtime::Shell, path.runtime().unwrap()); - - let path = store_configuration( - &temp_dir, - &create_default_with_extensions("seedling", Extensions2::default()), - ); - assert_eq!(Runtime::Seedling, path.runtime().unwrap()); - - let path = store_configuration( - &temp_dir, - &create_default_with_extensions("penpal-rococo-1000", Extensions2::default()), - ); - assert_eq!(Runtime::Penpal(1000.into()), path.runtime().unwrap()); - - let path = store_configuration( - &temp_dir, - &create_default_with_extensions("penpal-polkadot-2000", Extensions2::default()), - ); - assert_eq!(Runtime::Penpal(2000.into()), path.runtime().unwrap()); - - let path = store_configuration( - &temp_dir, - &crate::chain_spec::contracts::contracts_rococo_local_config(), - ); - assert_eq!(Runtime::ContractsRococo, path.runtime().unwrap()); - - let path = store_configuration( - &temp_dir, - &crate::chain_spec::rococo_parachain::rococo_parachain_local_config(), - ); - assert_eq!(Runtime::Omni(Consensus::Aura), 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 deleted file mode 100644 index 7d54e9b4be04..000000000000 --- a/cumulus/polkadot-parachain/src/fake_runtime_api/asset_hub_polkadot_aura.rs +++ /dev/null @@ -1,198 +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 . - -//! 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() -> 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_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 build_state(_: Vec) -> sp_genesis_builder::Result { - unimplemented!() - } - - fn get_preset(_id: &Option) -> Option> { - unimplemented!() - } - - fn preset_names() -> Vec { - unimplemented!() - } - } -} diff --git a/cumulus/polkadot-parachain/src/fake_runtime_api/aura.rs b/cumulus/polkadot-parachain/src/fake_runtime_api/aura.rs deleted file mode 100644 index ca5fc8bdf119..000000000000 --- a/cumulus/polkadot-parachain/src/fake_runtime_api/aura.rs +++ /dev/null @@ -1,198 +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 . - -//! 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() -> 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_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 build_state(_: Vec) -> sp_genesis_builder::Result { - unimplemented!() - } - - fn get_preset(_id: &Option) -> Option> { - unimplemented!() - } - - fn preset_names() -> Vec { - unimplemented!() - } - } -} diff --git a/cumulus/polkadot-parachain/src/main.rs b/cumulus/polkadot-parachain/src/main.rs index 84e41fc347d9..f2dce552c51a 100644 --- a/cumulus/polkadot-parachain/src/main.rs +++ b/cumulus/polkadot-parachain/src/main.rs @@ -19,38 +19,36 @@ #![warn(missing_docs)] #![warn(unused_extern_crates)] -pub(crate) fn examples(executable_name: String) -> String { - color_print::cformat!( - r#"Examples: +mod chain_spec; - {0} --chain para.json --sync warp -- --chain relay.json --sync warp - Launch a warp-syncing full node of a given para's chain-spec, and a given relay's chain-spec. +use polkadot_parachain_lib::{run, CliConfig as CliConfigT, RunConfig}; - The above approach is the most flexible, and the most forward-compatible way to spawn an omni-node. +struct CliConfig; - You can find the chain-spec of some networks in: - https://paritytech.github.io/chainspecs +impl CliConfigT for CliConfig { + fn impl_version() -> String { + env!("SUBSTRATE_CLI_IMPL_VERSION").into() + } - {0} --chain asset-hub-polkadot --sync warp -- --chain polkadot --sync warp - Launch a warp-syncing full node of the Asset Hub parachain on the Polkadot Relay Chain. + fn author() -> String { + env!("CARGO_PKG_AUTHORS").into() + } - {0} --chain asset-hub-kusama --sync warp --relay-chain-rpc-url ws://rpc.example.com -- --chain kusama - Launch a warp-syncing full node of the Asset Hub parachain on the Kusama Relay Chain. - Uses ws://rpc.example.com as remote relay chain node. - "#, - executable_name, - ) -} + fn support_url() -> String { + "https://github.com/paritytech/polkadot-sdk/issues/new".into() + } -mod chain_spec; -mod cli; -mod command; -mod common; -mod fake_runtime_api; -mod rpc; -mod service; + fn copyright_start_year() -> u16 { + 2017 + } +} fn main() -> color_eyre::eyre::Result<()> { color_eyre::install()?; - Ok(command::run()?) + + let config = RunConfig { + chain_spec_loader: Box::new(chain_spec::ChainSpecLoader), + runtime_resolver: Box::new(chain_spec::RuntimeResolver), + }; + Ok(run::(config)?) } diff --git a/cumulus/polkadot-parachain/src/service.rs b/cumulus/polkadot-parachain/src/service.rs deleted file mode 100644 index 80698a2d7115..000000000000 --- a/cumulus/polkadot-parachain/src/service.rs +++ /dev/null @@ -1,1040 +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 . - -use cumulus_client_cli::{CollatorOptions, ExportGenesisHeadCommand}; -use cumulus_client_collator::service::{ - CollatorService, ServiceInterface as CollatorServiceInterface, -}; -use cumulus_client_consensus_aura::collators::lookahead::{self as aura, Params as AuraParams}; -#[docify::export(slot_based_colator_import)] -use cumulus_client_consensus_aura::collators::slot_based::{ - self as slot_based, Params as SlotBasedParams, -}; -use cumulus_client_consensus_common::ParachainBlockImport as TParachainBlockImport; -use cumulus_client_consensus_proposer::{Proposer, ProposerInterface}; -use cumulus_client_consensus_relay_chain::Verifier as RelayChainVerifier; -#[allow(deprecated)] -use cumulus_client_service::old_consensus; -use cumulus_client_service::{ - build_network, build_relay_chain_interface, prepare_node_config, start_relay_chain_tasks, - BuildNetworkParams, CollatorSybilResistance, DARecoveryProfile, StartRelayChainTasksParams, -}; -use cumulus_primitives_core::{relay_chain::ValidationCode, ParaId}; -use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; - -use crate::{ - common::{ - aura::{AuraIdT, AuraRuntimeApi}, - ConstructNodeRuntimeApi, NodeExtraArgs, - }, - fake_runtime_api::aura::RuntimeApi as FakeRuntimeApi, - rpc::BuildRpcExtensions, -}; -pub use parachains_common::{AccountId, Balance, Block, Hash, Nonce}; - -use crate::rpc::{BuildEmptyRpcExtensions, BuildParachainRpcExtensions}; -use frame_benchmarking_cli::BlockCmd; -#[cfg(any(feature = "runtime-benchmarks"))] -use frame_benchmarking_cli::StorageCmd; -use futures::prelude::*; -use polkadot_primitives::CollatorPair; -use prometheus_endpoint::Registry; -use sc_cli::{CheckBlockCmd, ExportBlocksCmd, ExportStateCmd, ImportBlocksCmd, RevertCmd}; -use sc_client_api::BlockchainEvents; -use sc_consensus::{ - import_queue::{BasicQueue, Verifier as VerifierT}, - BlockImportParams, DefaultImportQueue, ImportQueue, -}; -use sc_executor::{HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY}; -use sc_network::{config::FullNetworkConfiguration, service::traits::NetworkBackend, NetworkBlock}; -use sc_service::{Configuration, Error, PartialComponents, TFullBackend, TFullClient, TaskManager}; -use sc_sysinfo::HwBench; -use sc_telemetry::{Telemetry, TelemetryHandle, TelemetryWorker, TelemetryWorkerHandle}; -use sc_transaction_pool::FullPool; -use sp_api::ProvideRuntimeApi; -use sp_inherents::CreateInherentDataProviders; -use sp_keystore::KeystorePtr; -use sp_runtime::{app_crypto::AppCrypto, traits::Header as HeaderT}; -use std::{marker::PhantomData, pin::Pin, sync::Arc, time::Duration}; - -#[cfg(not(feature = "runtime-benchmarks"))] -type HostFunctions = cumulus_client_service::ParachainHostFunctions; - -#[cfg(feature = "runtime-benchmarks")] -type HostFunctions = ( - cumulus_client_service::ParachainHostFunctions, - frame_benchmarking::benchmarking::HostFunctions, -); - -pub type ParachainClient = TFullClient>; - -pub type ParachainBackend = TFullBackend; - -type ParachainBlockImport = - TParachainBlockImport>, ParachainBackend>; - -/// Assembly of PartialComponents (enough to run chain ops subcommands) -pub type Service = PartialComponents< - ParachainClient, - ParachainBackend, - (), - sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool>, - (ParachainBlockImport, Option, Option), ->; - -pub(crate) trait BuildImportQueue { - fn build_import_queue( - client: Arc>, - block_import: ParachainBlockImport, - config: &Configuration, - telemetry_handle: Option, - task_manager: &TaskManager, - ) -> sc_service::error::Result>; -} - -pub(crate) trait StartConsensus -where - RuntimeApi: ConstructNodeRuntimeApi>, -{ - fn start_consensus( - client: Arc>, - block_import: ParachainBlockImport, - prometheus_registry: Option<&Registry>, - telemetry: Option, - task_manager: &TaskManager, - relay_chain_interface: Arc, - transaction_pool: Arc>>, - keystore: KeystorePtr, - relay_chain_slot_duration: Duration, - para_id: ParaId, - collator_key: CollatorPair, - overseer_handle: OverseerHandle, - announce_block: Arc>) + Send + Sync>, - backend: Arc, - node_extra_args: NodeExtraArgs, - ) -> Result<(), sc_service::Error>; -} - -pub(crate) trait NodeSpec { - type RuntimeApi: ConstructNodeRuntimeApi>; - - type BuildImportQueue: BuildImportQueue + 'static; - - type BuildRpcExtensions: BuildRpcExtensions< - ParachainClient, - ParachainBackend, - sc_transaction_pool::FullPool>, - > + 'static; - - type StartConsensus: StartConsensus + 'static; - - const SYBIL_RESISTANCE: CollatorSybilResistance; - - /// 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. - fn new_partial(config: &Configuration) -> sc_service::error::Result> { - let telemetry = config - .telemetry_endpoints - .clone() - .filter(|x| !x.is_empty()) - .map(|endpoints| -> Result<_, sc_telemetry::Error> { - let worker = TelemetryWorker::new(16)?; - let telemetry = worker.handle().new_telemetry(endpoints); - Ok((worker, telemetry)) - }) - .transpose()?; - - let heap_pages = config.default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| { - HeapAllocStrategy::Static { extra_pages: h as _ } - }); - - let executor = sc_executor::WasmExecutor::::builder() - .with_execution_method(config.wasm_method) - .with_max_runtime_instances(config.max_runtime_instances) - .with_runtime_cache_size(config.runtime_cache_size) - .with_onchain_heap_alloc_strategy(heap_pages) - .with_offchain_heap_alloc_strategy(heap_pages) - .build(); - - let (client, backend, keystore_container, task_manager) = - sc_service::new_full_parts_record_import::( - config, - telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), - executor, - true, - )?; - let client = Arc::new(client); - - let telemetry_worker_handle = telemetry.as_ref().map(|(worker, _)| worker.handle()); - - let telemetry = telemetry.map(|(worker, telemetry)| { - task_manager.spawn_handle().spawn("telemetry", None, worker.run()); - telemetry - }); - - let transaction_pool = sc_transaction_pool::BasicPool::new_full( - config.transaction_pool.clone(), - config.role.is_authority().into(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), - ); - - let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); - - let import_queue = Self::BuildImportQueue::build_import_queue( - client.clone(), - block_import.clone(), - config, - telemetry.as_ref().map(|telemetry| telemetry.handle()), - &task_manager, - )?; - - Ok(PartialComponents { - backend, - client, - import_queue, - keystore_container, - task_manager, - transaction_pool, - select_chain: (), - other: (block_import, telemetry, telemetry_worker_handle), - }) - } - - /// Start a node with the given parachain spec. - /// - /// This is the actual implementation that is abstract over the executor and the runtime api. - fn start_node( - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, - node_extra_args: NodeExtraArgs, - ) -> Pin>>> - where - Net: NetworkBackend, - { - Box::pin(async move { - let parachain_config = prepare_node_config(parachain_config); - - let params = Self::new_partial(¶chain_config)?; - 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::<_, _, Net>::new( - ¶chain_config.network, - prometheus_registry.clone(), - ); - - 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: Self::SYBIL_RESISTANCE, - }) - .await?; - - let rpc_builder = { - let client = client.clone(); - let transaction_pool = transaction_pool.clone(); - let backend_for_rpc = backend.clone(); - - Box::new(move |deny_unsafe, _| { - Self::BuildRpcExtensions::build_rpc_extensions( - deny_unsafe, - client.clone(), - backend_for_rpc.clone(), - transaction_pool.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, - })?; - - if validator { - Self::StartConsensus::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, - 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(), - node_extra_args, - )?; - } - - start_network.start_network(); - - Ok(task_manager) - }) - } -} - -/// Build the import queue for the shell runtime. -pub(crate) struct BuildShellImportQueue(PhantomData); - -impl BuildImportQueue for BuildShellImportQueue { - fn build_import_queue( - client: Arc>, - block_import: ParachainBlockImport, - config: &Configuration, - _telemetry_handle: Option, - task_manager: &TaskManager, - ) -> sc_service::error::Result> { - cumulus_client_consensus_relay_chain::import_queue( - client, - block_import, - |_, _| async { Ok(()) }, - &task_manager.spawn_essential_handle(), - config.prometheus_registry(), - ) - .map_err(Into::into) - } -} - -pub(crate) struct ShellNode; - -impl NodeSpec for ShellNode { - type RuntimeApi = FakeRuntimeApi; - type BuildImportQueue = BuildShellImportQueue; - type BuildRpcExtensions = BuildEmptyRpcExtensions; - type StartConsensus = StartRelayChainConsensus; - - const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Unresistant; -} - -struct Verifier { - client: Arc, - aura_verifier: Box>, - relay_chain_verifier: Box>, - _phantom: PhantomData, -} - -#[async_trait::async_trait] -impl VerifierT for Verifier -where - Client: ProvideRuntimeApi + Send + Sync, - Client::Api: AuraRuntimeApi, - AuraId: AuraIdT + Sync, -{ - async fn verify( - &self, - block_import: BlockImportParams, - ) -> Result, String> { - if self.client.runtime_api().has_aura_api(*block_import.header.parent_hash()) { - self.aura_verifier.verify(block_import).await - } else { - self.relay_chain_verifier.verify(block_import).await - } - } -} - -/// Build the import queue for parachain runtimes that started with relay chain consensus and -/// switched to aura. -pub(crate) struct BuildRelayToAuraImportQueue( - PhantomData<(RuntimeApi, AuraId)>, -); - -impl BuildImportQueue - for BuildRelayToAuraImportQueue -where - RuntimeApi: ConstructNodeRuntimeApi>, - RuntimeApi::RuntimeApi: AuraRuntimeApi, - AuraId: AuraIdT + Sync, -{ - fn build_import_queue( - client: Arc>, - block_import: ParachainBlockImport, - config: &Configuration, - telemetry_handle: Option, - task_manager: &TaskManager, - ) -> sc_service::error::Result> { - let verifier_client = client.clone(); - - let aura_verifier = - cumulus_client_consensus_aura::build_verifier::<::Pair, _, _, _>( - cumulus_client_consensus_aura::BuildVerifierParams { - 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, - }, - ); - - let relay_chain_verifier = - Box::new(RelayChainVerifier::new(client.clone(), |_, _| async { Ok(()) })); - - let verifier = Verifier { - client, - relay_chain_verifier, - aura_verifier: Box::new(aura_verifier), - _phantom: PhantomData, - }; - - let registry = config.prometheus_registry(); - let spawner = task_manager.spawn_essential_handle(); - - Ok(BasicQueue::new(verifier, Box::new(block_import), None, &spawner, registry)) - } -} - -/// Uses the lookahead collator to support async backing. -/// -/// Start an aura powered parachain node. Some system chains use this. -pub(crate) struct AuraNode( - pub PhantomData<(RuntimeApi, AuraId, StartConsensus)>, -); - -impl Default for AuraNode { - fn default() -> Self { - Self(Default::default()) - } -} - -impl NodeSpec for AuraNode -where - RuntimeApi: ConstructNodeRuntimeApi>, - RuntimeApi::RuntimeApi: AuraRuntimeApi - + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi - + substrate_frame_rpc_system::AccountNonceApi, - AuraId: AuraIdT + Sync, - StartConsensus: self::StartConsensus + 'static, -{ - type RuntimeApi = RuntimeApi; - type BuildImportQueue = BuildRelayToAuraImportQueue; - type BuildRpcExtensions = BuildParachainRpcExtensions; - type StartConsensus = StartConsensus; - const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Resistant; -} - -pub fn new_aura_node_spec(extra_args: &NodeExtraArgs) -> Box -where - RuntimeApi: ConstructNodeRuntimeApi>, - RuntimeApi::RuntimeApi: AuraRuntimeApi - + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi - + substrate_frame_rpc_system::AccountNonceApi, - AuraId: AuraIdT + Sync, -{ - if extra_args.use_slot_based_consensus { - Box::new(AuraNode::< - RuntimeApi, - AuraId, - StartSlotBasedAuraConsensus, - >::default()) - } else { - Box::new(AuraNode::< - RuntimeApi, - AuraId, - StartLookaheadAuraConsensus, - >::default()) - } -} - -/// Start relay-chain consensus that is free for all. Everyone can submit a block, the relay-chain -/// decides what is backed and included. -pub(crate) struct StartRelayChainConsensus; - -impl StartConsensus for StartRelayChainConsensus { - fn start_consensus( - client: Arc>, - block_import: ParachainBlockImport, - prometheus_registry: Option<&Registry>, - telemetry: Option, - task_manager: &TaskManager, - relay_chain_interface: Arc, - transaction_pool: Arc>>, - _keystore: KeystorePtr, - _relay_chain_slot_duration: Duration, - para_id: ParaId, - collator_key: CollatorPair, - overseer_handle: OverseerHandle, - announce_block: Arc>) + Send + Sync>, - _backend: Arc, - _node_extra_args: NodeExtraArgs, - ) -> Result<(), 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. -pub(crate) struct StartSlotBasedAuraConsensus( - PhantomData<(RuntimeApi, AuraId)>, -); - -impl StartSlotBasedAuraConsensus -where - RuntimeApi: ConstructNodeRuntimeApi>, - RuntimeApi::RuntimeApi: AuraRuntimeApi, - AuraId: AuraIdT + Sync, -{ - #[docify::export_content] - fn launch_slot_based_collator( - params: SlotBasedParams< - ParachainBlockImport, - CIDP, - ParachainClient, - ParachainBackend, - Arc, - CHP, - Proposer, - CS, - >, - task_manager: &TaskManager, - ) where - CIDP: CreateInherentDataProviders + 'static, - CIDP::InherentDataProviders: Send, - CHP: cumulus_client_consensus_common::ValidationCodeHashProvider + Send + 'static, - Proposer: ProposerInterface + Send + Sync + 'static, - CS: CollatorServiceInterface + Send + Sync + Clone + 'static, - { - let (collation_future, block_builder_future) = - slot_based::run::::Pair, _, _, _, _, _, _, _, _>(params); - - task_manager.spawn_essential_handle().spawn( - "collation-task", - Some("parachain-block-authoring"), - collation_future, - ); - task_manager.spawn_essential_handle().spawn( - "block-builder-task", - Some("parachain-block-authoring"), - block_builder_future, - ); - } -} - -impl StartConsensus - for StartSlotBasedAuraConsensus -where - RuntimeApi: ConstructNodeRuntimeApi>, - RuntimeApi::RuntimeApi: AuraRuntimeApi, - AuraId: AuraIdT + Sync, -{ - fn start_consensus( - client: Arc>, - block_import: ParachainBlockImport, - prometheus_registry: Option<&Registry>, - telemetry: Option, - task_manager: &TaskManager, - relay_chain_interface: Arc, - transaction_pool: Arc>>, - keystore: KeystorePtr, - relay_chain_slot_duration: Duration, - para_id: ParaId, - collator_key: CollatorPair, - _overseer_handle: OverseerHandle, - announce_block: Arc>) + Send + Sync>, - backend: Arc, - _node_extra_args: NodeExtraArgs, - ) -> Result<(), Error> { - 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 client_for_aura = client.clone(); - let params = SlotBasedParams { - create_inherent_data_providers: move |_, ()| async move { Ok(()) }, - block_import, - para_client: client.clone(), - para_backend: backend.clone(), - relay_client: relay_chain_interface, - code_hash_provider: move |block_hash| { - client_for_aura.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) - }, - keystore, - collator_key, - para_id, - relay_chain_slot_duration, - proposer, - collator_service, - authoring_duration: Duration::from_millis(2000), - reinitialize: false, - slot_drift: Duration::from_secs(1), - }; - - // We have a separate function only to be able to use `docify::export` on this piece of - // code. - Self::launch_slot_based_collator(params, task_manager); - - Ok(()) - } -} - -/// Wait for the Aura runtime API to appear on chain. -/// This is useful for chains that started out without Aura. Components that -/// are depending on Aura functionality will wait until Aura appears in the runtime. -async fn wait_for_aura(client: Arc>) -where - RuntimeApi: ConstructNodeRuntimeApi>, - RuntimeApi::RuntimeApi: AuraRuntimeApi, - AuraId: AuraIdT + Sync, -{ - let finalized_hash = client.chain_info().finalized_hash; - if client.runtime_api().has_aura_api(finalized_hash) { - return; - }; - - let mut stream = client.finality_notification_stream(); - while let Some(notification) = stream.next().await { - if client.runtime_api().has_aura_api(notification.hash) { - return; - } - } -} - -/// Start consensus using the lookahead aura collator. -pub(crate) struct StartLookaheadAuraConsensus( - PhantomData<(RuntimeApi, AuraId)>, -); - -impl StartConsensus - for StartLookaheadAuraConsensus -where - RuntimeApi: ConstructNodeRuntimeApi>, - RuntimeApi::RuntimeApi: AuraRuntimeApi, - AuraId: AuraIdT + Sync, -{ - fn start_consensus( - client: Arc>, - block_import: ParachainBlockImport, - prometheus_registry: Option<&Registry>, - telemetry: Option, - task_manager: &TaskManager, - relay_chain_interface: Arc, - transaction_pool: Arc>>, - keystore: KeystorePtr, - relay_chain_slot_duration: Duration, - para_id: ParaId, - collator_key: CollatorPair, - overseer_handle: OverseerHandle, - announce_block: Arc>) + Send + Sync>, - backend: Arc, - node_extra_args: NodeExtraArgs, - ) -> Result<(), 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 = aura::ParamsWithExport { - export_pov: node_extra_args.export_pov, - 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: { - let client = client.clone(); - move |block_hash| { - client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) - } - }, - 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 = - async move { - wait_for_aura(client).await; - aura::run_with_export::::Pair, _, _, _, _, _, _, _, _>(params).await; - }; - task_manager.spawn_essential_handle().spawn("aura", None, fut); - - Ok(()) - } -} - -/// 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 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", - err - ); - } -} - -type SyncCmdResult = sc_cli::Result<()>; - -type AsyncCmdResult<'a> = - sc_cli::Result<(Pin + 'a>>, TaskManager)>; - -pub(crate) trait DynNodeSpec { - fn prepare_check_block_cmd( - self: Box, - config: Configuration, - cmd: &CheckBlockCmd, - ) -> AsyncCmdResult<'_>; - - fn prepare_export_blocks_cmd( - self: Box, - config: Configuration, - cmd: &ExportBlocksCmd, - ) -> AsyncCmdResult<'_>; - - fn prepare_export_state_cmd( - self: Box, - config: Configuration, - cmd: &ExportStateCmd, - ) -> AsyncCmdResult<'_>; - - fn prepare_import_blocks_cmd( - self: Box, - config: Configuration, - cmd: &ImportBlocksCmd, - ) -> AsyncCmdResult<'_>; - - fn prepare_revert_cmd( - self: Box, - config: Configuration, - cmd: &RevertCmd, - ) -> AsyncCmdResult<'_>; - - fn run_export_genesis_head_cmd( - self: Box, - config: Configuration, - cmd: &ExportGenesisHeadCommand, - ) -> SyncCmdResult; - - fn run_benchmark_block_cmd( - self: Box, - config: Configuration, - cmd: &BlockCmd, - ) -> SyncCmdResult; - - #[cfg(any(feature = "runtime-benchmarks"))] - fn run_benchmark_storage_cmd( - self: Box, - config: Configuration, - cmd: &StorageCmd, - ) -> SyncCmdResult; - - fn start_node( - self: Box, - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, - node_extra_args: NodeExtraArgs, - ) -> Pin>>>; -} - -impl DynNodeSpec for T -where - T: NodeSpec, -{ - fn prepare_check_block_cmd( - self: Box, - config: Configuration, - cmd: &CheckBlockCmd, - ) -> AsyncCmdResult<'_> { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - Ok((Box::pin(cmd.run(partial.client, partial.import_queue)), partial.task_manager)) - } - - fn prepare_export_blocks_cmd( - self: Box, - config: Configuration, - cmd: &ExportBlocksCmd, - ) -> AsyncCmdResult<'_> { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - Ok((Box::pin(cmd.run(partial.client, config.database)), partial.task_manager)) - } - - fn prepare_export_state_cmd( - self: Box, - config: Configuration, - cmd: &ExportStateCmd, - ) -> AsyncCmdResult<'_> { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - Ok((Box::pin(cmd.run(partial.client, config.chain_spec)), partial.task_manager)) - } - - fn prepare_import_blocks_cmd( - self: Box, - config: Configuration, - cmd: &ImportBlocksCmd, - ) -> AsyncCmdResult<'_> { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - Ok((Box::pin(cmd.run(partial.client, partial.import_queue)), partial.task_manager)) - } - - fn prepare_revert_cmd( - self: Box, - config: Configuration, - cmd: &RevertCmd, - ) -> AsyncCmdResult<'_> { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - Ok((Box::pin(cmd.run(partial.client, partial.backend, None)), partial.task_manager)) - } - - fn run_export_genesis_head_cmd( - self: Box, - config: Configuration, - cmd: &ExportGenesisHeadCommand, - ) -> SyncCmdResult { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - cmd.run(partial.client) - } - - fn run_benchmark_block_cmd( - self: Box, - config: Configuration, - cmd: &BlockCmd, - ) -> SyncCmdResult { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - cmd.run(partial.client) - } - - #[cfg(any(feature = "runtime-benchmarks"))] - fn run_benchmark_storage_cmd( - self: Box, - config: Configuration, - cmd: &StorageCmd, - ) -> SyncCmdResult { - let partial = Self::new_partial(&config).map_err(sc_cli::Error::Service)?; - let db = partial.backend.expose_db(); - let storage = partial.backend.expose_storage(); - - cmd.run(config, partial.client, db, storage) - } - - fn start_node( - self: Box, - parachain_config: Configuration, - polkadot_config: Configuration, - collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, - node_extra_args: NodeExtraArgs, - ) -> Pin>>> { - match parachain_config.network.network_backend { - sc_network::config::NetworkBackendType::Libp2p => - ::start_node::>( - parachain_config, - polkadot_config, - collator_options, - para_id, - hwbench, - node_extra_args, - ), - sc_network::config::NetworkBackendType::Litep2p => - ::start_node::( - parachain_config, - polkadot_config, - collator_options, - para_id, - hwbench, - node_extra_args, - ), - } - } -} diff --git a/cumulus/primitives/aura/Cargo.toml b/cumulus/primitives/aura/Cargo.toml index 062b9ce736e7..185b2d40833f 100644 --- a/cumulus/primitives/aura/Cargo.toml +++ b/cumulus/primitives/aura/Cargo.toml @@ -10,24 +10,14 @@ description = "Core primitives for Aura in Cumulus" workspace = true [dependencies] -codec = { features = ["derive"], workspace = true } # Substrate sp-api = { workspace = true } sp-consensus-aura = { workspace = true } -sp-runtime = { workspace = true } - -# Polkadot -polkadot-core-primitives = { workspace = true } -polkadot-primitives = { workspace = true } [features] default = ["std"] std = [ - "codec/std", - "polkadot-core-primitives/std", - "polkadot-primitives/std", "sp-api/std", "sp-consensus-aura/std", - "sp-runtime/std", ] diff --git a/cumulus/primitives/parachain-inherent/Cargo.toml b/cumulus/primitives/parachain-inherent/Cargo.toml index 172af4b9ec63..a4271d3fd9cc 100644 --- a/cumulus/primitives/parachain-inherent/Cargo.toml +++ b/cumulus/primitives/parachain-inherent/Cargo.toml @@ -17,8 +17,6 @@ scale-info = { features = ["derive"], workspace = true } # Substrate sp-core = { workspace = true } sp-inherents = { workspace = true } -sp-runtime = { optional = true, workspace = true } -sp-state-machine = { optional = true, workspace = true } sp-trie = { workspace = true } # Cumulus @@ -33,7 +31,5 @@ std = [ "scale-info/std", "sp-core/std", "sp-inherents/std", - "sp-runtime?/std", - "sp-state-machine?/std", "sp-trie/std", ] diff --git a/cumulus/primitives/storage-weight-reclaim/src/lib.rs b/cumulus/primitives/storage-weight-reclaim/src/lib.rs index 5984fa77a2c5..a557e881e26b 100644 --- a/cumulus/primitives/storage-weight-reclaim/src/lib.rs +++ b/cumulus/primitives/storage-weight-reclaim/src/lib.rs @@ -174,6 +174,9 @@ where let storage_size_diff = benchmarked_weight.abs_diff(consumed_weight as u64); + let extrinsic_len = frame_system::AllExtrinsicsLen::::get().unwrap_or(0); + let node_side_pov_size = post_dispatch_proof_size.saturating_add(extrinsic_len.into()); + // This value will be reclaimed by [`frame_system::CheckWeight`], so we need to calculate // that in. frame_system::BlockWeight::::mutate(|current| { @@ -190,6 +193,19 @@ where ); current.reduce(Weight::from_parts(0, storage_size_diff), info.class) } + + // If we encounter a situation where the node-side proof size is already higher than + // what we have in the runtime bookkeeping, we add the difference to the `BlockWeight`. + // This prevents that the proof size grows faster than the runtime proof size. + let block_weight_proof_size = current.total().proof_size(); + let missing_from_node = node_side_pov_size.saturating_sub(block_weight_proof_size); + if missing_from_node > 0 { + log::warn!( + target: LOG_TARGET, + "Node-side PoV size higher than runtime proof size weight. node-side: {node_side_pov_size} extrinsic_len: {extrinsic_len} runtime: {block_weight_proof_size}, missing: {missing_from_node}. Setting to node-side proof size." + ); + current.accrue(Weight::from_parts(0, missing_from_node), info.class); + } }); Ok(()) } @@ -332,6 +348,82 @@ mod tests { }) } + #[test] + fn sets_to_node_storage_proof_if_higher() { + // The storage proof reported by the proof recorder is higher than what is stored on + // the runtime side. + { + let mut test_ext = setup_test_externalities(&[1000, 1005]); + + test_ext.execute_with(|| { + // Stored in BlockWeight is 5 + set_current_storage_weight(5); + + // Benchmarked storage weight: 10 + let info = DispatchInfo { weight: Weight::from_parts(0, 10), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + assert_ok!(CheckWeight::::do_pre_dispatch(&info, LEN)); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(1000)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + // We expect that the storage weight was set to the node-side proof size (1005) + + // extrinsics length (150) + assert_eq!(get_storage_weight().total().proof_size(), 1155); + }) + } + + // In this second scenario the proof size on the node side is only lower + // after reclaim happened. + { + let mut test_ext = setup_test_externalities(&[175, 180]); + test_ext.execute_with(|| { + set_current_storage_weight(85); + + // Benchmarked storage weight: 100 + let info = + DispatchInfo { weight: Weight::from_parts(0, 100), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + // After this pre_dispatch, the BlockWeight proof size will be + // 85 (initial) + 100 (benched) + 150 (tx length) = 335 + assert_ok!(CheckWeight::::do_pre_dispatch(&info, LEN)); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(175)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + + // First we will reclaim 95, which leaves us with 240 BlockWeight. This is lower + // than 180 (proof size hf) + 150 (length), so we expect it to be set to 330. + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + // We expect that the storage weight was set to the node-side proof weight + assert_eq!(get_storage_weight().total().proof_size(), 330); + }) + } + } + #[test] fn does_nothing_without_extension() { let mut test_ext = new_test_ext(); @@ -545,7 +637,7 @@ mod tests { #[test] fn test_nothing_relcaimed() { - let mut test_ext = setup_test_externalities(&[100, 200]); + let mut test_ext = setup_test_externalities(&[0, 100]); test_ext.execute_with(|| { set_current_storage_weight(0); @@ -568,7 +660,7 @@ mod tests { .pre_dispatch(&ALICE, CALL, &info, LEN) .unwrap(); // Should return `setup_test_externalities` proof recorder value: 100. - assert_eq!(pre, Some(100)); + assert_eq!(pre, Some(0)); // The `CheckWeight` extension will refund `actual_weight` from `PostDispatchInfo` // we always need to call `post_dispatch` to verify that they interoperate correctly. diff --git a/cumulus/primitives/utility/Cargo.toml b/cumulus/primitives/utility/Cargo.toml index 82d18c8c0aac..2ca8b82001d5 100644 --- a/cumulus/primitives/utility/Cargo.toml +++ b/cumulus/primitives/utility/Cargo.toml @@ -15,13 +15,11 @@ log = { workspace = true } # Substrate frame-support = { workspace = true } -sp-io = { workspace = true } sp-runtime = { workspace = true } pallet-asset-conversion = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true } -polkadot-runtime-parachains = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } xcm-builder = { workspace = true } @@ -38,8 +36,6 @@ std = [ "log/std", "pallet-asset-conversion/std", "polkadot-runtime-common/std", - "polkadot-runtime-parachains/std", - "sp-io/std", "sp-runtime/std", "xcm-builder/std", "xcm-executor/std", @@ -51,7 +47,6 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", - "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", diff --git a/cumulus/test/service/benches/validate_block_glutton.rs b/cumulus/test/service/benches/validate_block_glutton.rs index 6ec338c7f142..6fe26519a3eb 100644 --- a/cumulus/test/service/benches/validate_block_glutton.rs +++ b/cumulus/test/service/benches/validate_block_glutton.rs @@ -43,7 +43,7 @@ use sp_runtime::traits::Header as HeaderT; use cumulus_test_service::bench_utils as utils; async fn import_block( - mut client: &cumulus_test_client::Client, + client: &cumulus_test_client::Client, built: cumulus_test_runtime::Block, import_existing: bool, ) { diff --git a/cumulus/test/service/src/bench_utils.rs b/cumulus/test/service/src/bench_utils.rs index 4ace894b392a..67ffbdd1d212 100644 --- a/cumulus/test/service/src/bench_utils.rs +++ b/cumulus/test/service/src/bench_utils.rs @@ -111,7 +111,7 @@ pub fn extrinsic_set_validation_data( } /// Import block into the given client and make sure the import was successful -pub async fn import_block(mut client: &TestClient, block: &NodeBlock, import_existing: bool) { +pub async fn import_block(client: &TestClient, block: &NodeBlock, import_existing: bool) { let mut params = BlockImportParams::new(BlockOrigin::File, block.header.clone()); params.body = Some(block.extrinsics.clone()); params.state_action = StateAction::Execute; diff --git a/cumulus/test/service/src/cli.rs b/cumulus/test/service/src/cli.rs index 37ca27542cbf..739c2d4bda16 100644 --- a/cumulus/test/service/src/cli.rs +++ b/cumulus/test/service/src/cli.rs @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use std::{net::SocketAddr, path::PathBuf}; +use std::path::PathBuf; use cumulus_client_cli::{ExportGenesisHeadCommand, ExportGenesisWasmCommand}; use polkadot_service::{ChainSpec, ParaId, PrometheusConfig}; use sc_cli::{ CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, NetworkParams, - Result as CliResult, SharedParams, SubstrateCli, + Result as CliResult, RpcEndpoint, SharedParams, SubstrateCli, }; use sc_service::BasePath; @@ -122,7 +122,7 @@ impl CliConfiguration for RelayChainCli { .or_else(|| self.base_path.clone().map(Into::into))) } - fn rpc_addr(&self, default_listen_port: u16) -> CliResult> { + fn rpc_addr(&self, default_listen_port: u16) -> CliResult>> { self.base.base.rpc_addr(default_listen_port) } diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 503c2f24fd54..bc0fe9090d38 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -38,7 +38,7 @@ use sp_consensus_aura::sr25519::AuthorityPair; use std::{ collections::HashSet, future::Future, - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{Ipv4Addr, SocketAddr, SocketAddrV4}, time::Duration, }; use url::Url; @@ -79,7 +79,7 @@ use sc_network::{ use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, NetworkConfiguration, - OffchainWorkerConfig, PruningMode, RpcBatchRequestConfig, WasmExecutionMethod, + OffchainWorkerConfig, PruningMode, RpcBatchRequestConfig, RpcEndpoint, WasmExecutionMethod, }, BasePath, ChainSpec as ChainSpecService, Configuration, Error as ServiceError, PartialComponents, Role, RpcHandlers, TFullBackend, TFullClient, TaskManager, @@ -379,7 +379,7 @@ where let keystore = params.keystore_container.keystore(); let rpc_builder = { let client = client.clone(); - Box::new(move |_, _| rpc_ext_builder(client.clone())) + Box::new(move |_| rpc_ext_builder(client.clone())) }; let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { @@ -1006,7 +1006,22 @@ pub fn run_relay_chain_validator_node( ); if let Some(port) = port { - config.rpc_addr = Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port)); + config.rpc_addr = Some(vec![RpcEndpoint { + batch_config: config.rpc_batch_config, + cors: config.rpc_cors.clone(), + listen_addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)), + max_connections: config.rpc_max_connections, + max_payload_in_mb: config.rpc_max_request_size, + max_payload_out_mb: config.rpc_max_response_size, + max_subscriptions_per_connection: config.rpc_max_subs_per_conn, + max_buffer_capacity_per_connection: config.rpc_message_buffer_capacity, + rpc_methods: config.rpc_methods, + rate_limit: config.rpc_rate_limit, + rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(), + retry_random_port: true, + is_optional: false, + }]); } let mut workers_path = std::env::current_exe().unwrap(); diff --git a/docs/BACKPORT.md b/docs/BACKPORT.md new file mode 100644 index 000000000000..0b4a97e6f667 --- /dev/null +++ b/docs/BACKPORT.md @@ -0,0 +1,21 @@ +# Backporting + +This document explains how to backport a merged PR from `master` to one of the `stable*` branches. +Backports should only be used to fix bugs or security issues - never to introduce new features. + +## Steps + +1. Fix a bug through a PR that targets `master`. +2. Add label `A4-needs-backport` to the PR. +3. Merge the PR into `master`. +4. Wait for the bot to open the backport PR. +5. Ensure the change is audited or does not need audit. +6. Merge the backport PR. + +The label can also be added after the PR is merged. + +## Example + +For example here where the dev triggered the process by adding the label after merging: + +![backport](./images/backport-ex2.png) diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 653e6a2a3e92..bea367411359 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -55,9 +55,10 @@ The Westend testnet will be updated to a new runtime every two weeks with the la **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. +Backports in this direction can be anything that is audited and either a `minor` or a `patch` bump. +See [BACKPORT.md](./BACKPORT.md) for more explanation. [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`** @@ -164,5 +165,6 @@ Describes how developers should merge bug and security fixes. 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. +5. Dev adds the `A4-needs-backport` label. +6. It is automatically back-ported to `stable`. +7. The fix will be released in the next *Stable* release. In urgent cases, a release can happen earlier. diff --git a/docs/contributor/CONTRIBUTING.md b/docs/contributor/CONTRIBUTING.md index d8f956b82d2d..53f42b9ae4fb 100644 --- a/docs/contributor/CONTRIBUTING.md +++ b/docs/contributor/CONTRIBUTING.md @@ -42,13 +42,13 @@ The set of labels and their description can be found [here](https://paritytech.g 3. If you’re still working on your PR, please submit as “Draft”. Once a PR is ready for review change the status to “Open”, so that the maintainers get to review your PR. Generally PRs should sit for 48 hours in order to garner feedback. It may be merged before if all relevant parties had a look at it. -4. With respect to auditing, please see [AUDIT.md](../AUDIT.md). In general, merging to master can happen independent of +4. With respect to auditing, please see [AUDIT.md](../AUDIT.md). In general, merging to master can happen independently of audit. 5. PRs will be able to be merged once all reviewers' comments are addressed and CI is successful. **Noting breaking changes:** When breaking APIs, the PR description should mention what was changed alongside some examples on how to change the code to make it work/compile. It should also mention potential storage migrations and if -they require some special setup aside adding it to the list of migrations in the runtime. +they require some special setup aside from adding it to the list of migrations in the runtime. ## Reviewing pull requests @@ -161,11 +161,11 @@ test output there is a script * `./scripts/update-ui-tests.sh` to update the tests for a current rust version locally * `./scripts/update-ui-tests.sh 1.70` # to update the tests for a specific rust version locally -Or if you have opened PR and you're member of `paritytech` - you can use command-bot to run the tests for you in CI: -* `bot update-ui` - will run the tests for the current rust version -* `bot update-ui latest --rust_version=1.70.0` - will run the tests for the specified rust version -* `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 +Or if you have opened PR and you're member of `paritytech` - you can use [/cmd](./commands-readme.md) +to run the tests for you in CI: +* `/cmd update-ui` - will run the tests for the current rust version +* `/cmd update-ui --image docker.io/paritytech/ci-unified:bullseye-1.70.0-2023-05-23` - +will run the tests for the specified rust version and specified image ## Feature Propagation @@ -175,7 +175,7 @@ We use [zepter](https://github.com/ggwpez/zepter) to enforce features are propag If you're member of **paritytech** org - you can use command-bot to run various of common commands in CI: -Start with comment in PR: `bot help` to see the list of available commands. +Start with comment in PR: `/cmd --help` to see the list of available commands. ## Deprecating code diff --git a/docs/contributor/DOCUMENTATION_GUIDELINES.md b/docs/contributor/DOCUMENTATION_GUIDELINES.md index cc1082347d20..5ac99fff1cdb 100644 --- a/docs/contributor/DOCUMENTATION_GUIDELINES.md +++ b/docs/contributor/DOCUMENTATION_GUIDELINES.md @@ -136,7 +136,7 @@ the `macro@my_macro_name` syntax in your link. Read more about how to correctly The above five guidelines must always be reasonably respected in the documentation. -The following are a set of notes that may not necessarily hold in all circumstances: +The following is a set of notes that may not necessarily hold in all circumstances: --- @@ -205,7 +205,7 @@ properly do this. ## Pallet Crates -The guidelines so far have been general in nature, and are applicable to crates that are pallets and crates that're not +The guidelines so far have been general in nature, and are applicable to crates that are pallets and crates that are not pallets. The following is relevant to how to document parts of a crate that is a pallet. See diff --git a/docs/contributor/PULL_REQUEST_TEMPLATE.md b/docs/contributor/PULL_REQUEST_TEMPLATE.md index 083b30b4a356..99455c985076 100644 --- a/docs/contributor/PULL_REQUEST_TEMPLATE.md +++ b/docs/contributor/PULL_REQUEST_TEMPLATE.md @@ -20,7 +20,7 @@ reviewed by reviewers, if the PR does NOT have the `R0-Silent` label. In case of ## Review Notes -*In depth notes about the **implenentation** details of your PR. This should be the main guide for reviewers to +*In depth notes about the **implementation** details of your PR. This should be the main guide for reviewers to understand your approach and effectively review it. If too long, use [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)*. @@ -33,8 +33,9 @@ possibly integration.* # Checklist * [ ] My PR includes a detailed description as outlined in the "Description" and its two subsections above. -* [ ] My PR follows the [labeling requirements](CONTRIBUTING.md#Process) of this project (at minimum one label for `T` - required) +* [ ] My PR follows the [labeling requirements]( +https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process +) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [ ] I have made corresponding changes to the documentation (if applicable) * [ ] I have added tests that prove my fix is effective or that my feature works (if applicable) diff --git a/docs/contributor/commands-readme.md b/docs/contributor/commands-readme.md new file mode 100644 index 000000000000..2bb9bd7e7d58 --- /dev/null +++ b/docs/contributor/commands-readme.md @@ -0,0 +1,44 @@ +# Running Commands in PRs + +You can run commands in PRs by triggering it via comment. It will use the context of your PR and post the results back. +Note: it works only for members of the `paritytech` organization. + +## Usage + +`/cmd --help` to see all available commands and usage format + +`/cmd --help` to see the usage of a specific command + + +### Commands + +- `/cmd fmt` to format the code in the PR. It commits back with the formatted code (fmt) and configs (taplo). + +- `/cmd bench` to generate weights for a runtime. Read more about [Weight Generation](weight-generation.md) + +### Flags + +1.`--quiet` to suppress the output of the command in the comments. +By default, the Start and End/Failure of the command will be commented with the link to a pipeline. +If you want to avoid, use this flag. Go to +[Action Tab](https://github.com/paritytech/polkadot-sdk/actions/workflows/cmd.yml) to see the pipeline status. + +2.`--continue-on-fail` to continue running the command even if something inside a command +(like specific pallet weight generation) are failed. +Basically avoids interruption in the middle with `exit 1` +The pipeline logs will include what is failed (like which runtimes/pallets), then you can re-run them separately or not. + +3.`--clean` to clean up all yours and bot's comments in PR relevant to `/cmd` commands. If you run too many commands, +or they keep failing, and you're rerunning them again, it's handy to add this flag to keep a PR clean. + +### Adding new Commands +Feel free to add new commands to the workflow, however **_note_** that triggered workflows will use the actions +from `main` (default) branch, meaning they will take effect only after the PR with new changes/command is merged. +If you want to test the new command, it's better to test in your fork and local-to-fork PRs, where you control +the default branch. + +### Examples +The regex in cmd.yml is: `^(\/cmd )([-\/\s\w.=:]+)$` accepts only alphanumeric, space, "-", "/", "=", ":", "." chars. + +`/cmd bench --runtime bridge-hub-westend --pallet=pallet_name` +`/cmd update-ui --image=docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v202407161507 --clean` diff --git a/docs/contributor/weight-generation.md b/docs/contributor/weight-generation.md new file mode 100644 index 000000000000..ebfdca59cae5 --- /dev/null +++ b/docs/contributor/weight-generation.md @@ -0,0 +1,69 @@ +# Weight Generation + +To generate weights for a runtime. +Weights generation is using self-hosted runner which is provided by Parity CI, the rest commands are using standard +GitHub runners on `ubuntu-latest` or `ubuntu-20.04`. +Self-hosted runner for benchmarks (arc-runners-Polkadot-sdk-benchmark) is configured to meet requirements of reference +hardware for running validators +https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware + +In a PR run the actions through comment: + +```sh +/cmd bench --help # outputs the actual usage documentation with examples and supported runtimes + +# or + +/cmd --help # to see all available commands +``` + +To regenerate all weights (however it will take long, +so don't do it unless you really need it), run the following command: +```sh +/cmd bench +``` + +To generate weights for all pallets in a particular runtime(s), run the following command: +```sh +/cmd bench --runtime kusama polkadot +``` + +For Substrate pallets (supports sub-modules too): +```sh +/cmd bench --runtime dev --pallet pallet_asset_conversion_ops +``` + +> **📝 Note**: The action is not being run right-away, it will be queued and run in the next available runner. +So might be quick, but might also take up to 10 mins (That's in control of Github). +Once the action is run, you'll see reaction 👀 on original comment, and if you didn't pass `--quiet` - +it will also send a link to a pipeline when started, and link to whole workflow when finished. + +--- + +> **💡Hint #1** : if you run all runtimes or all pallets, it might be that some pallet in the middle is failed +to generate weights, thus it stops (fails) the whole pipeline. +> If you want, you can make it to continue running, even if some pallets are failed, add `--continue-on-fail` +flag to the command. The report will include which runtimes/pallets have failed, then you can re-run +them separately after all is done. + +This way it runs all possible runtimes for the specified pallets, if it finds them in the runtime +```sh +/cmd bench --pallet pallet_balances pallet_xcm_benchmarks::generic pallet_xcm_benchmarks::fungible +``` + +If you want to run all specific pallet(s) for specific runtime(s), you can do it like this: +```sh +/cmd bench --runtime bridge-hub-polkadot --pallet pallet_xcm_benchmarks::generic pallet_xcm_benchmarks::fungible +``` + + +> **💡Hint #2** : Sometimes when you run too many commands, or they keep failing and you're rerunning them again, +it's handy to add `--clean` flag to the command. This will clean up all yours and bot's comments in PR relevant to +/cmd commands. + +```sh +/cmd bench --runtime kusama polkadot --pallet=pallet_balances --clean --continue-on-fail +``` + +> **💡Hint #3** : If you have questions or need help, feel free to tag @paritytech/opstooling (in github comments) +or ping in [matrix](https://matrix.to/#/#command-bot:parity.io) channel. diff --git a/docs/images/Polkadot_Logo_Horizontal_Pink_BlackOnWhite.png b/docs/images/Polkadot_Logo_Horizontal_Pink_BlackOnWhite.png new file mode 100644 index 000000000000..ef2b997100ea Binary files /dev/null and b/docs/images/Polkadot_Logo_Horizontal_Pink_BlackOnWhite.png differ diff --git a/docs/images/Polkadot_Logo_Horizontal_Pink_WhiteOnBlack.png b/docs/images/Polkadot_Logo_Horizontal_Pink_WhiteOnBlack.png new file mode 100644 index 000000000000..421a38e1bdfa Binary files /dev/null and b/docs/images/Polkadot_Logo_Horizontal_Pink_WhiteOnBlack.png differ diff --git a/docs/images/backport-ex2.png b/docs/images/backport-ex2.png new file mode 100644 index 000000000000..97ccf6b00fb9 Binary files /dev/null and b/docs/images/backport-ex2.png differ diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index 2f85171bb93d..adc1c1a8efbc 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -22,6 +22,7 @@ frame = { features = [ "runtime", ], workspace = true, default-features = true } pallet-examples = { workspace = true } +pallet-contracts = { workspace = true } pallet-default-config-example = { workspace = true, default-features = true } pallet-example-offchain-worker = { workspace = true, default-features = true } @@ -30,7 +31,7 @@ simple-mermaid = "0.1.1" docify = { workspace = true } # Polkadot SDK deps, typically all should only be in scope such that we can link to their doc item. -polkadot-sdk = { features = ["runtime"], workspace = true, default-features = true } +polkadot-sdk = { features = ["runtime-full"], workspace = true, default-features = true } node-cli = { workspace = true } kitchensink-runtime = { workspace = true } chain-spec-builder = { workspace = true, default-features = true } diff --git a/docs/sdk/assets/header.html b/docs/sdk/assets/header.html index f55c31b53216..c24c10940759 100644 --- a/docs/sdk/assets/header.html +++ b/docs/sdk/assets/header.html @@ -14,12 +14,13 @@ headers.forEach(header => { let link = document.createElement("a"); link.href = "#" + header.id; - link.textContent = header.textContent; + const headerTextContent = header.textContent.replace("§", "") + link.textContent = headerTextContent; link.className = header.tagName.toLowerCase(); toc.appendChild(link); - if (header.id == "modules" && header.textContent == "Modules") { + if (header.id == "modules" && headerTextContent == "Modules") { modules.forEach(module => { let link = document.createElement("a"); link.href = module.href; diff --git a/docs/sdk/src/guides/async_backing_guide.rs b/docs/sdk/src/guides/async_backing_guide.rs index a9fda2c3aa8a..25ef3a12cbf0 100644 --- a/docs/sdk/src/guides/async_backing_guide.rs +++ b/docs/sdk/src/guides/async_backing_guide.rs @@ -174,7 +174,7 @@ //! - In the `para_client` field, pass in a cloned para client rather than the original //! - Add a `para_backend` parameter after `para_client`, passing in our para backend //! - Provide a `code_hash_provider` closure like that shown below -//! - Increase `authoring_duration` from 500 milliseconds to 1500 +//! - Increase `authoring_duration` from 500 milliseconds to 2000 //! ```ignore //! let params = AuraParams { //! .. @@ -185,7 +185,7 @@ //! client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) //! }, //! .. -//! authoring_duration: Duration::from_millis(1500), +//! authoring_duration: Duration::from_millis(2000), //! .. //! }; //! ``` diff --git a/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs b/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs index 939043b53529..38ef18b88e0d 100644 --- a/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs +++ b/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs @@ -85,7 +85,7 @@ //! This phase consists of plugging in the new slot-based collator. //! //! 1. In `node/src/service.rs` import the slot based collator instead of the lookahead collator. -#![doc = docify::embed!("../../cumulus/polkadot-parachain/src/service.rs", slot_based_colator_import)] +#![doc = docify::embed!("../../cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs", slot_based_colator_import)] //! //! 2. In `start_consensus()` //! - Remove the `overseer_handle` param (also remove the @@ -94,7 +94,7 @@ //! `slot_drift` field with a value of `Duration::from_secs(1)`. //! - Replace the single future returned by `aura::run` with the two futures returned by it and //! spawn them as separate tasks: -#![doc = docify::embed!("../../cumulus/polkadot-parachain/src/service.rs", launch_slot_based_collator)] +#![doc = docify::embed!("../../cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs", launch_slot_based_collator)] //! //! 3. In `start_parachain_node()` remove the `overseer_handle` param passed to `start_consensus`. //! diff --git a/docs/sdk/src/guides/your_first_pallet/mod.rs b/docs/sdk/src/guides/your_first_pallet/mod.rs index 3c74469e1768..006d0be7ded3 100644 --- a/docs/sdk/src/guides/your_first_pallet/mod.rs +++ b/docs/sdk/src/guides/your_first_pallet/mod.rs @@ -21,7 +21,7 @@ //! this guide, namely the crate/package names, based on which template you use. //! //! > Be aware that you can read the entire source code backing this tutorial by clicking on the -//! > [`source`](./mod.rs.html) button at the top right of the page. +//! > `source` button at the top right of the page. //! //! You should have studied the following modules as a prelude to this guide: //! @@ -45,7 +45,7 @@ //! Consider the following as a "shell pallet". We continue building the rest of this pallet based //! on this template. //! -//! [`pallet::config`] and [`pallet::pallet`](frame_support::pallet) are both mandatory parts of any +//! [`pallet::config`] and [`pallet::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)] //! @@ -319,13 +319,13 @@ //! - Learn more about the individual pallet items/macros, such as event and errors and call, in //! [`frame::pallet_macros`]. //! -//! [`pallet::storage`]: ../../../frame_support/pallet_macros/attr.config.html -//! [`pallet::call`]: ../../../frame_support/pallet_macros/attr.call.html -//! [`pallet::event`]: ../../../frame_support/pallet_macros/attr.event.html -//! [`pallet::error`]: ../../../frame_support/pallet_macros/attr.error.html -//! [`pallet::pallet`]: ../../../frame_support/pallet_macros/attr.pallet.html -//! [`pallet::config`]: ../../../frame_support/pallet_macros/attr.config.html -//! [`pallet::generate_deposit`]: ../../../frame_support/pallet_macros/attr.generate_deposit.html +//! [`pallet::storage`]: frame_support::pallet_macros::storage +//! [`pallet::call`]: frame_support::pallet_macros::call +//! [`pallet::event`]: frame_support::pallet_macros::event +//! [`pallet::error`]: frame_support::pallet_macros::error +//! [`pallet::pallet`]: frame_support::pallet +//! [`pallet::config`]: frame_support::pallet_macros::config +//! [`pallet::generate_deposit`]: frame_support::pallet_macros::generate_deposit #[docify::export] #[frame::pallet(dev_mode)] diff --git a/docs/sdk/src/lib.rs b/docs/sdk/src/lib.rs index bc0970c01f1f..6dc87858530c 100644 --- a/docs/sdk/src/lib.rs +++ b/docs/sdk/src/lib.rs @@ -25,7 +25,7 @@ #![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_favicon_url = "https://polkadot.com/favicon.ico")] #![doc( html_logo_url = "https://europe1.discourse-cdn.com/standard21/uploads/polkadot2/original/1X/eb57081e2bb7c39e5fcb1a98b443e423fa4448ae.svg" )] diff --git a/docs/sdk/src/polkadot_sdk/templates.rs b/docs/sdk/src/polkadot_sdk/templates.rs index e87eb9c2bc8a..03a7aa98198f 100644 --- a/docs/sdk/src/polkadot_sdk/templates.rs +++ b/docs/sdk/src/polkadot_sdk/templates.rs @@ -40,9 +40,14 @@ //! //! In June 2023, OpenZeppelin was awarded a grant from the [Polkadot //! treasury](https://polkadot.polkassembly.io/treasury/406) for building a number of Polkadot-sdk -//! based templates. These templates are expected to be a great starting point for developers. -//! -//! - +//! based templates. These templates are a great starting point for developers and newcomers. +//! So far OpenZeppelin has released two templates, which have been fully [audited](https://github.com/OpenZeppelin/polkadot-runtime-templates/tree/main/audits): +//! - [`generic-runtime-template`](https://github.com/OpenZeppelin/polkadot-runtime-templates?tab=readme-ov-file#generic-runtime-template): +//! A minimal template that has all the common pallets that parachains use with secure defaults. +//! - [`evm-runtime-template`](https://github.com/OpenZeppelin/polkadot-runtime-templates/tree/main?tab=readme-ov-file#evm-template): +//! This template has EVM compatibility out of the box and allows migrating your solidity contracts +//! or EVM compatible dapps easily. It also uses 20 byte addresses like Ethereum and has some +//! Account Abstraction support. //! //! ## POP-CLI //! diff --git a/docs/sdk/src/reference_docs/chain_spec_genesis.rs b/docs/sdk/src/reference_docs/chain_spec_genesis.rs index 557795cb410c..39e5993d020f 100644 --- a/docs/sdk/src/reference_docs/chain_spec_genesis.rs +++ b/docs/sdk/src/reference_docs/chain_spec_genesis.rs @@ -178,7 +178,6 @@ //! [`pallet::genesis_build`]: frame_support::pallet_macros::genesis_build //! [`pallet::genesis_config`]: frame_support::pallet_macros::genesis_config //! [`BuildGenesisConfig`]: frame_support::traits::BuildGenesisConfig -//! [`chain_spec_builder`]: ../../../staging_chain_spec_builder/index.html //! [`serde`]: https://serde.rs/field-attrs.html //! [`get_storage_for_patch`]: sc_chain_spec::GenesisConfigBuilderRuntimeCaller::get_storage_for_patch //! [`GenesisBuilder::get_preset`]: sp_genesis_builder::GenesisBuilder::get_preset diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs index c45f0126337e..195d1b124474 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/runtime.rs @@ -32,10 +32,7 @@ use frame::{ runtime, }, prelude::*, - runtime::{ - apis::{self, impl_runtime_apis, ExtrinsicInclusionMode}, - prelude::*, - }, + runtime::{apis, prelude::*}, }; use sp_genesis_builder::PresetId; diff --git a/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs b/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs index 4a0ba3ca48f5..c91b66b944c6 100644 --- a/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs +++ b/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs @@ -20,8 +20,8 @@ //! #### 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 +//! [`pallet_contracts`] for WASM-based contracts or the +//! [`pallet_evm`](https://github.com/polkadot-evm/frontier/tree/master/frame/evm) for EVM-compatible contracts. These pallets //! enable Smart Contract developers to build applications and systems on top of a Substrate-based //! blockchain. //! @@ -108,7 +108,7 @@ //! - **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) +//! upgraded, but lack of migration logic. The [`pallet_contracts`] //! 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/basics/upgradeable-contracts). //! - **Isolated Impact**: Upgrades or changes to a smart contract generally impact only that diff --git a/polkadot/README.md b/polkadot/README.md index 47af79a3aa92..fa14995e9af3 100644 --- a/polkadot/README.md +++ b/polkadot/README.md @@ -103,9 +103,8 @@ Connect to the global Polkadot Mainnet network by running: ../target/release/polkadot --chain=polkadot ``` -You can see your node on [telemetry] (set a custom name with `--name "my custom name"`). - -[telemetry](https://telemetry.polkadot.io/#list/Polkadot): https://telemetry.polkadot.io/#list/Polkadot +You can see your node on [Polkadot telemetry](https://telemetry.polkadot.io/#list/0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3) +(set a custom name with `--name "my custom name"`). ### Connect to the "Kusama" Canary Network @@ -115,9 +114,8 @@ Connect to the global Kusama canary network by running: ../target/release/polkadot --chain=kusama ``` -You can see your node on [telemetry] (set a custom name with `--name "my custom name"`). - -[telemetry](https://telemetry.polkadot.io/#list/Kusama): https://telemetry.polkadot.io/#list/Kusama +You can see your node on [Kusama telemetry](https://telemetry.polkadot.io/#list/0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe) +(set a custom name with `--name "my custom name"`). ### Connect to the Westend Testnet @@ -127,9 +125,8 @@ Connect to the global Westend testnet by running: ../target/release/polkadot --chain=westend ``` -You can see your node on [telemetry] (set a custom name with `--name "my custom name"`). - -[telemetry](https://telemetry.polkadot.io/#list/Westend): https://telemetry.polkadot.io/#list/Westend +You can see your node on [Westend telemetry](https://telemetry.polkadot.io/#list/0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e) +(set a custom name with `--name "my custom name"`). ### Obtaining DOTs @@ -147,7 +144,7 @@ Then, grab the Polkadot source code: ```bash git clone https://github.com/paritytech/polkadot-sdk.git -cd polkadot +cd polkadot-sdk ``` Then build the code. You will need to build in release mode (`--release`) to start a network. Only @@ -185,7 +182,7 @@ You can run a simple single-node development "network" on your machine by runnin cargo run --bin polkadot --release -- --dev ``` -You can muck around by heading to and choose "Local Node" from the +You can muck around by heading to and choosing "Local Node" from the Settings menu. ### Local Two-node Testnet @@ -214,11 +211,11 @@ that we currently maintain. ### Using Docker -[Using Docker](../docs/contributor/docker.md) +[Using Docker](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/docker.md) ### Shell Completion -[Shell Completion](doc/shell-completion.md) +[Shell Completion](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/doc/shell-completion.md) ## Contributing @@ -232,4 +229,4 @@ that we currently maintain. ## License -Polkadot is [GPL 3.0 licensed](LICENSE). +Polkadot is [GPL 3.0 licensed](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/LICENSE). diff --git a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs index a18e667253d0..41418bcc511f 100644 --- a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs +++ b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs @@ -74,11 +74,12 @@ fn main() -> Result<(), String> { .map_err(|e| e.to_string())?; println!("{}", average_usage); - // We expect no variance for received and sent - // but use 0.001 because we operate with floats + // We expect some small variance for received and sent because the + // test messages are generated at every benchmark run and they contain + // random data so use 0.01 as the accepted variance. messages.extend(average_usage.check_network_usage(&[ - ("Received from peers", 52941.6071, 0.001), - ("Sent to peers", 63810.1859, 0.001), + ("Received from peers", 52941.6071, 0.01), + ("Sent to peers", 63995.2200, 0.01), ])); messages.extend(average_usage.check_cpu_usage(&[ ("approval-distribution", 6.3912, 0.1), diff --git a/polkadot/node/core/dispute-coordinator/src/initialized.rs b/polkadot/node/core/dispute-coordinator/src/initialized.rs index 5f86da87f21c..5096fe5e6891 100644 --- a/polkadot/node/core/dispute-coordinator/src/initialized.rs +++ b/polkadot/node/core/dispute-coordinator/src/initialized.rs @@ -1351,6 +1351,12 @@ impl Initialized { } } for validator_index in new_state.votes().invalid.keys() { + gum::debug!( + target: LOG_TARGET, + ?candidate_hash, + ?validator_index, + "Disabled offchain for voting invalid against a valid candidate", + ); self.offchain_disabled_validators .insert_against_valid(session, *validator_index); } @@ -1375,6 +1381,13 @@ impl Initialized { } for (validator_index, (kind, _sig)) in new_state.votes().valid.raw() { let is_backer = kind.is_backing(); + gum::debug!( + target: LOG_TARGET, + ?candidate_hash, + ?validator_index, + ?is_backer, + "Disabled offchain for voting valid for an invalid candidate", + ); self.offchain_disabled_validators.insert_for_invalid( session, *validator_index, diff --git a/polkadot/node/core/dispute-coordinator/src/lib.rs b/polkadot/node/core/dispute-coordinator/src/lib.rs index daa384b36ffb..34d9ddf3a97c 100644 --- a/polkadot/node/core/dispute-coordinator/src/lib.rs +++ b/polkadot/node/core/dispute-coordinator/src/lib.rs @@ -478,6 +478,18 @@ pub fn is_potential_spam( let all_invalid_votes_disabled = vote_state.invalid_votes_all_disabled(is_disabled); let ignore_disabled = !is_confirmed && all_invalid_votes_disabled; + gum::trace!( + target: LOG_TARGET, + ?candidate_hash, + ?is_disputed, + ?is_included, + ?is_backed, + ?is_confirmed, + ?all_invalid_votes_disabled, + ?ignore_disabled, + "Checking for potential spam" + ); + (is_disputed && !is_included && !is_backed && !is_confirmed) || ignore_disabled } diff --git a/polkadot/node/core/pvf/common/Cargo.toml b/polkadot/node/core/pvf/common/Cargo.toml index 18b3f959c955..bf663b4cfcea 100644 --- a/polkadot/node/core/pvf/common/Cargo.toml +++ b/polkadot/node/core/pvf/common/Cargo.toml @@ -42,6 +42,8 @@ seccompiler = "0.4.0" [dev-dependencies] assert_matches = { workspace = true } + +[target.'cfg(target_os = "linux")'.dev-dependencies] tempfile = { workspace = true } [features] diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index 3462aaef1f69..a1bdc47e9fb2 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -161,7 +161,7 @@ impl ApprovalEntry { Self { validator_index: assignment.validator, assignment, - approvals: HashMap::with_capacity(candidates.len()), + approvals: HashMap::new(), assignment_claimed_candidates: candidates, routing_info, } @@ -2508,7 +2508,9 @@ impl ApprovalDistribution { }; - self.handle_from_orchestra(message, &mut approval_voting_sender, &mut network_sender, state, rng).await; + if self.handle_from_orchestra(message, &mut approval_voting_sender, &mut network_sender, state, rng).await { + return; + } }, } @@ -2516,6 +2518,8 @@ impl ApprovalDistribution { } /// Handles a from orchestra message received by approval distribution subystem. + /// + /// Returns `true` if the subsystem should be stopped. pub async fn handle_from_orchestra< N: overseer::SubsystemSender, A: overseer::SubsystemSender, @@ -2526,7 +2530,7 @@ impl ApprovalDistribution { network_sender: &mut N, state: &mut State, rng: &mut (impl CryptoRng + Rng), - ) { + ) -> bool { match message { FromOrchestra::Communication { msg } => Self::handle_incoming( @@ -2555,8 +2559,9 @@ impl ApprovalDistribution { gum::trace!(target: LOG_TARGET, number = %number, "finalized signal"); state.handle_block_finalized(network_sender, &self.metrics, number).await; }, - FromOrchestra::Signal(OverseerSignal::Conclude) => return, + FromOrchestra::Signal(OverseerSignal::Conclude) => return true, } + false } async fn handle_incoming< diff --git a/polkadot/node/network/approval-distribution/src/tests.rs b/polkadot/node/network/approval-distribution/src/tests.rs index 3ea722c51a92..1ca571721ea9 100644 --- a/polkadot/node/network/approval-distribution/src/tests.rs +++ b/polkadot/node/network/approval-distribution/src/tests.rs @@ -59,9 +59,13 @@ fn test_harness>( let subsystem = ApprovalDistribution::new(Default::default()); { let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(12345); - - let subsystem = - subsystem.run_inner(context, &mut state, REPUTATION_CHANGE_TEST_INTERVAL, &mut rng); + let (tx, rx) = oneshot::channel(); + let subsystem = async { + subsystem + .run_inner(context, &mut state, REPUTATION_CHANGE_TEST_INTERVAL, &mut rng) + .await; + tx.send(()).expect("Fail to notify subystem is done"); + }; let test_fut = test_fn(virtual_overseer); @@ -76,6 +80,8 @@ fn test_harness>( .timeout(TIMEOUT) .await .expect("Conclude send timeout"); + let _ = + rx.timeout(Duration::from_secs(2)).await.expect("Subsystem did not conclude"); }, subsystem, )); diff --git a/polkadot/node/network/availability-distribution/src/lib.rs b/polkadot/node/network/availability-distribution/src/lib.rs index ec2c01f99b01..d3185e0af809 100644 --- a/polkadot/node/network/availability-distribution/src/lib.rs +++ b/polkadot/node/network/availability-distribution/src/lib.rs @@ -25,7 +25,7 @@ use polkadot_node_subsystem::{ jaeger, messages::AvailabilityDistributionMessage, overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, }; -use polkadot_primitives::Hash; +use polkadot_primitives::{BlockNumber, Hash}; use std::collections::HashMap; /// Error and [`Result`] type for this subsystem. @@ -104,7 +104,7 @@ impl AvailabilityDistributionSubsystem { /// Start processing work as passed on from the Overseer. async fn run(self, mut ctx: Context) -> std::result::Result<(), FatalError> { let Self { mut runtime, recvs, metrics, req_protocol_names } = self; - let mut spans: HashMap = HashMap::new(); + let mut spans: HashMap = HashMap::new(); let IncomingRequestReceivers { pov_req_receiver, @@ -162,7 +162,7 @@ impl AvailabilityDistributionSubsystem { }; let span = jaeger::PerLeafSpan::new(cloned_leaf.span, "availability-distribution"); - spans.insert(cloned_leaf.hash, span); + spans.insert(cloned_leaf.hash, (cloned_leaf.number, span)); log_error( requester .get_mut() @@ -172,8 +172,8 @@ impl AvailabilityDistributionSubsystem { &mut warn_freq, )?; }, - FromOrchestra::Signal(OverseerSignal::BlockFinalized(hash, _)) => { - spans.remove(&hash); + FromOrchestra::Signal(OverseerSignal::BlockFinalized(_hash, finalized_number)) => { + spans.retain(|_hash, (block_number, _span)| *block_number > finalized_number); }, FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()), FromOrchestra::Communication { @@ -189,7 +189,7 @@ impl AvailabilityDistributionSubsystem { } => { let span = spans .get(&relay_parent) - .map(|span| span.child("fetch-pov")) + .map(|(_, span)| span.child("fetch-pov")) .unwrap_or_else(|| jaeger::Span::new(&relay_parent, "fetch-pov")) .with_trace_id(candidate_hash) .with_candidate(candidate_hash) diff --git a/polkadot/node/network/availability-distribution/src/requester/mod.rs b/polkadot/node/network/availability-distribution/src/requester/mod.rs index efbdceb43bdd..0175161af70d 100644 --- a/polkadot/node/network/availability-distribution/src/requester/mod.rs +++ b/polkadot/node/network/availability-distribution/src/requester/mod.rs @@ -39,7 +39,9 @@ use polkadot_node_subsystem_util::{ availability_chunks::availability_chunk_index, runtime::{get_occupied_cores, RuntimeInfo}, }; -use polkadot_primitives::{CandidateHash, CoreIndex, Hash, OccupiedCore, SessionIndex}; +use polkadot_primitives::{ + BlockNumber, CandidateHash, CoreIndex, Hash, OccupiedCore, SessionIndex, +}; use super::{FatalError, Metrics, Result, LOG_TARGET}; @@ -112,14 +114,14 @@ impl Requester { ctx: &mut Context, runtime: &mut RuntimeInfo, update: ActiveLeavesUpdate, - spans: &HashMap, + spans: &HashMap, ) -> Result<()> { gum::trace!(target: LOG_TARGET, ?update, "Update fetching heads"); let ActiveLeavesUpdate { activated, deactivated } = update; if let Some(leaf) = activated { let span = spans .get(&leaf.hash) - .map(|span| span.child("update-fetching-heads")) + .map(|(_, span)| span.child("update-fetching-heads")) .unwrap_or_else(|| jaeger::Span::new(&leaf.hash, "update-fetching-heads")) .with_string_tag("leaf", format!("{:?}", leaf.hash)) .with_stage(jaeger::Stage::AvailabilityDistribution); diff --git a/polkadot/node/network/availability-distribution/src/requester/tests.rs b/polkadot/node/network/availability-distribution/src/requester/tests.rs index 09567a8f87d3..decb3156004e 100644 --- a/polkadot/node/network/availability-distribution/src/requester/tests.rs +++ b/polkadot/node/network/availability-distribution/src/requester/tests.rs @@ -208,7 +208,7 @@ fn check_ancestry_lookup_in_same_session() { test_harness(test_state.clone(), |mut ctx| async move { let chain = &test_state.relay_chain; - let spans: HashMap = HashMap::new(); + let spans: HashMap = HashMap::new(); let block_number = 1; let update = ActiveLeavesUpdate { activated: Some(new_leaf(chain[block_number], block_number as u32)), @@ -281,7 +281,7 @@ fn check_ancestry_lookup_in_different_sessions() { test_harness(test_state.clone(), |mut ctx| async move { let chain = &test_state.relay_chain; - let spans: HashMap = HashMap::new(); + let spans: HashMap = HashMap::new(); let block_number = 3; let update = ActiveLeavesUpdate { activated: Some(new_leaf(chain[block_number], block_number as u32)), diff --git a/polkadot/node/network/bridge/src/rx/mod.rs b/polkadot/node/network/bridge/src/rx/mod.rs index 56965ce6ba40..7745c42f78a1 100644 --- a/polkadot/node/network/bridge/src/rx/mod.rs +++ b/polkadot/node/network/bridge/src/rx/mod.rs @@ -962,6 +962,21 @@ fn update_our_view( ) }; + let our_view = OurView::new( + live_heads.iter().take(MAX_VIEW_HEADS).cloned().map(|a| (a.hash, a.span)), + finalized_number, + ); + + dispatch_validation_event_to_all_unbounded( + NetworkBridgeEvent::OurViewChange(our_view.clone()), + ctx.sender(), + ); + + dispatch_collation_event_to_all_unbounded( + NetworkBridgeEvent::OurViewChange(our_view), + ctx.sender(), + ); + 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()); @@ -1007,21 +1022,6 @@ fn update_our_view( metrics, notification_sinks, ); - - let our_view = OurView::new( - live_heads.iter().take(MAX_VIEW_HEADS).cloned().map(|a| (a.hash, a.span)), - finalized_number, - ); - - dispatch_validation_event_to_all_unbounded( - NetworkBridgeEvent::OurViewChange(our_view.clone()), - ctx.sender(), - ); - - dispatch_collation_event_to_all_unbounded( - NetworkBridgeEvent::OurViewChange(our_view), - ctx.sender(), - ); } // Handle messages on a specific v1 peer-set. The peer is expected to be connected on that diff --git a/polkadot/node/network/dispute-distribution/src/receiver/mod.rs b/polkadot/node/network/dispute-distribution/src/receiver/mod.rs index 2409e6994f60..77c1e41aac05 100644 --- a/polkadot/node/network/dispute-distribution/src/receiver/mod.rs +++ b/polkadot/node/network/dispute-distribution/src/receiver/mod.rs @@ -66,9 +66,12 @@ use self::{ const COST_INVALID_REQUEST: Rep = Rep::CostMajor("Received message could not be decoded."); const COST_INVALID_SIGNATURE: Rep = Rep::Malicious("Signatures were invalid."); -const COST_INVALID_IMPORT: Rep = - Rep::Malicious("Import was deemed invalid by dispute-coordinator."); const COST_NOT_A_VALIDATOR: Rep = Rep::CostMajor("Reporting peer was not a validator."); + +/// Invalid imports can be caused by flooding, e.g. by a disabled validator. +const COST_INVALID_IMPORT: Rep = + Rep::CostMinor("Import was deemed invalid by dispute-coordinator."); + /// Mildly punish peers exceeding their rate limit. /// /// For honest peers this should rarely happen, but if it happens we would not want to disconnect diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index 24c69582731b..685a9fd337df 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -59,7 +59,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.15.0"; +pub const NODE_VERSION: &'static str = "1.15.1"; // For a 16-ary Merkle Prefix Trie, we can expect at most 16 32-byte hashes per node // plus some overhead: diff --git a/polkadot/node/service/chain-specs/kusama.json b/polkadot/node/service/chain-specs/kusama.json index 5d2413ac1e02..ff38192906da 100644 --- a/polkadot/node/service/chain-specs/kusama.json +++ b/polkadot/node/service/chain-specs/kusama.json @@ -37,7 +37,8 @@ "/dns/ibp-boot-kusama.luckyfriday.io/tcp/30333/p2p/12D3KooW9vu1GWHBuxyhm7rZgD3fhGZpNajPXFexadvhujWMgwfT", "/dns/boot-kusama.luckyfriday.io/tcp/443/wss/p2p/12D3KooWS1Lu6DmK8YHSvkErpxpcXmk14vG6y4KVEFEkd9g62PP8", "/dns/ibp-boot-kusama.luckyfriday.io/tcp/30334/wss/p2p/12D3KooW9vu1GWHBuxyhm7rZgD3fhGZpNajPXFexadvhujWMgwfT", - "/dns4/kusama-0.boot.onfinality.io/tcp/27682/ws/p2p/12D3KooWFrwFo7ry3dEuFwhehGSSN96a5Xdzxot7SWfXeSbhELAe" + "/dns4/kusama-0.boot.onfinality.io/tcp/27682/ws/p2p/12D3KooWFrwFo7ry3dEuFwhehGSSN96a5Xdzxot7SWfXeSbhELAe", + "/dns/kusama.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJHhnF64TXSmyxNkhPkXAHtYNRy86LuvGQu1LTi5vrJCL" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/chain-specs/paseo.json b/polkadot/node/service/chain-specs/paseo.json index 35ac9aa6aa93..aacfdb025786 100644 --- a/polkadot/node/service/chain-specs/paseo.json +++ b/polkadot/node/service/chain-specs/paseo.json @@ -20,7 +20,8 @@ "/dns/pso16.rotko.net/tcp/33246/p2p/12D3KooWRH8eBMhw8c7bucy6pJfy94q4dKpLkF3pmeGohHmemdRu", "/dns/pso16.rotko.net/tcp/35246/wss/p2p/12D3KooWRH8eBMhw8c7bucy6pJfy94q4dKpLkF3pmeGohHmemdRu", "/dns/paseo-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr", - "/dns/paseo-boot-ng.dwellir.com/tcp/30354/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr" + "/dns/paseo-boot-ng.dwellir.com/tcp/30354/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr", + "/dns/paseo.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWMdND5nwfCs5M2rfp5kyRo41BGDgD8V67rVRaB3acgZ53" ], "telemetryEndpoints": null, "protocolId": "pas", diff --git a/polkadot/node/service/chain-specs/polkadot.json b/polkadot/node/service/chain-specs/polkadot.json index 553ff1bb3e41..7e1e90f6a8c1 100644 --- a/polkadot/node/service/chain-specs/polkadot.json +++ b/polkadot/node/service/chain-specs/polkadot.json @@ -38,7 +38,8 @@ "/dns/ibp-boot-polkadot.luckyfriday.io/tcp/30333/p2p/12D3KooWEjk6QXrZJ26fLpaajisJGHiz6WiQsR8k7mkM9GmWKnRZ", "/dns/ibp-boot-polkadot.luckyfriday.io/tcp/30334/wss/p2p/12D3KooWEjk6QXrZJ26fLpaajisJGHiz6WiQsR8k7mkM9GmWKnRZ", "/dns/boot-polkadot.luckyfriday.io/tcp/443/wss/p2p/12D3KooWAdyiVAaeGdtBt6vn5zVetwA4z4qfm9Fi2QCSykN1wTBJ", - "/dns4/polkadot-0.boot.onfinality.io/tcp/24446/ws/p2p/12D3KooWT1PWaNdAwYrSr89dvStnoGdH3t4LNRbcVNN4JCtsotkR" + "/dns4/polkadot-0.boot.onfinality.io/tcp/24446/ws/p2p/12D3KooWT1PWaNdAwYrSr89dvStnoGdH3t4LNRbcVNN4JCtsotkR", + "/dns/polkadot.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWEymrFRHz6c17YP3FAyd8kXS5gMRLgkW4U77ZJD2ZNCLZ" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/chain-specs/westend.json b/polkadot/node/service/chain-specs/westend.json index 9059d525689d..08c5bd33b4bd 100644 --- a/polkadot/node/service/chain-specs/westend.json +++ b/polkadot/node/service/chain-specs/westend.json @@ -33,7 +33,8 @@ "/dns/wnd14.rotko.net/tcp/35234/wss/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" + "/dns/ibp-boot-westend.luckyfriday.io/tcp/30334/wss/p2p/12D3KooWDg1YEytdwFFNWroFj6gio4YFsMB3miSbHKgdpJteUMB9", + "/dns/westend.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWHaQKkJiTPqeNgqDcW7dfYgJxYwT8YqJMtTkueSu6378V" ], "telemetryEndpoints": [ [ diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index 5ca566d2c962..a907d310c105 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -486,7 +486,6 @@ fn new_partial( sc_transaction_pool::FullPool, ( impl Fn( - polkadot_rpc::DenyUnsafe, polkadot_rpc::SubscriptionTaskExecutor, ) -> Result, ( @@ -593,15 +592,13 @@ where let chain_spec = config.chain_spec.cloned_box(); let backend = backend.clone(); - move |deny_unsafe, - subscription_executor: polkadot_rpc::SubscriptionTaskExecutor| + move |subscription_executor: polkadot_rpc::SubscriptionTaskExecutor| -> Result { let deps = polkadot_rpc::FullDeps { client: client.clone(), pool: transaction_pool.clone(), select_chain: select_chain.clone(), chain_spec: chain_spec.cloned_box(), - deny_unsafe, babe: polkadot_rpc::BabeDeps { babe_worker_handle: babe_worker_handle.clone(), keystore: keystore.clone(), @@ -764,7 +761,7 @@ pub fn new_full< ) -> Result { use polkadot_availability_recovery::FETCH_CHUNKS_THRESHOLD; use polkadot_node_network_protocol::request_response::IncomingRequest; - use sc_network_sync::WarpSyncParams; + use sc_network_sync::WarpSyncConfig; let is_offchain_indexing_enabled = config.offchain_worker.indexing_enabled; let role = config.role.clone(); @@ -1037,7 +1034,7 @@ pub fn new_full< spawn_handle: task_manager.spawn_handle(), import_queue, block_announce_validator_builder: None, - warp_sync_params: Some(WarpSyncParams::WithProvider(warp_sync)), + warp_sync_config: Some(WarpSyncConfig::WithProvider(warp_sync)), block_relay: None, metrics, })?; diff --git a/polkadot/node/test/client/src/lib.rs b/polkadot/node/test/client/src/lib.rs index 6b205c09f2f3..498994d9a0a8 100644 --- a/polkadot/node/test/client/src/lib.rs +++ b/polkadot/node/test/client/src/lib.rs @@ -102,7 +102,7 @@ mod tests { #[test] fn ensure_test_client_can_build_and_import_block() { - let mut client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().build(); let block_builder = client.init_polkadot_block_builder(); let block = block_builder.build().expect("Finalizes the block").block; @@ -113,7 +113,7 @@ mod tests { #[test] fn ensure_test_client_can_push_extrinsic() { - let mut client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().build(); let transfer = construct_transfer_extrinsic( &client, diff --git a/polkadot/rpc/src/lib.rs b/polkadot/rpc/src/lib.rs index eb0133b6e8fd..0007df908e2b 100644 --- a/polkadot/rpc/src/lib.rs +++ b/polkadot/rpc/src/lib.rs @@ -27,7 +27,7 @@ use sc_consensus_beefy::communication::notification::{ BeefyBestBlockStream, BeefyVersionedFinalityProofStream, }; use sc_consensus_grandpa::FinalityProofProvider; -pub use sc_rpc::{DenyUnsafe, SubscriptionTaskExecutor}; +pub use sc_rpc::SubscriptionTaskExecutor; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_application_crypto::RuntimeAppPublic; @@ -83,8 +83,6 @@ pub struct FullDeps { pub select_chain: SC, /// A copy of the chain spec. pub chain_spec: Box, - /// Whether to deny unsafe calls - pub deny_unsafe: DenyUnsafe, /// BABE specific dependencies. pub babe: BabeDeps, /// GRANDPA specific dependencies. @@ -97,7 +95,13 @@ pub struct FullDeps { /// Instantiate all RPC extensions. pub fn create_full( - FullDeps { client, pool, select_chain, chain_spec, deny_unsafe, babe, grandpa, beefy, backend } : FullDeps, + FullDeps { client, pool, select_chain, chain_spec, babe, grandpa, beefy, backend }: FullDeps< + C, + P, + SC, + B, + AuthorityId, + >, ) -> Result> where C: ProvideRuntimeApi @@ -138,8 +142,8 @@ where finality_provider, } = grandpa; - 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(StateMigration::new(client.clone(), backend.clone()).into_rpc())?; + io.merge(System::new(client.clone(), pool.clone()).into_rpc())?; io.merge(TransactionPayment::new(client.clone()).into_rpc())?; io.merge( Mmr::new( @@ -151,8 +155,7 @@ where .into_rpc(), )?; io.merge( - Babe::new(client.clone(), babe_worker_handle.clone(), keystore, select_chain, deny_unsafe) - .into_rpc(), + Babe::new(client.clone(), babe_worker_handle.clone(), keystore, select_chain).into_rpc(), )?; io.merge( Grandpa::new( diff --git a/polkadot/runtime/parachains/src/builder.rs b/polkadot/runtime/parachains/src/builder.rs index b2a67ee8dd24..59afff359d08 100644 --- a/polkadot/runtime/parachains/src/builder.rs +++ b/polkadot/runtime/parachains/src/builder.rs @@ -221,9 +221,10 @@ impl BenchBuilder { .expect("self.block_number is u32") } - /// Maximum number of validators that may be part of a validator group. + /// Fallback for the maximum number of validators participating in parachains consensus (a.k.a. + /// active validators). pub(crate) fn fallback_max_validators() -> u32 { - configuration::ActiveConfig::::get().max_validators.unwrap_or(200) + configuration::ActiveConfig::::get().max_validators.unwrap_or(1024) } /// Maximum number of validators participating in parachains consensus (a.k.a. active @@ -285,8 +286,8 @@ impl BenchBuilder { /// 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 + pub(crate) fn fallback_min_backing_votes() -> u32 { + 2 } fn mock_head_data() -> HeadData { @@ -356,11 +357,11 @@ impl BenchBuilder { availability_votes, commitments, ); - inclusion::PendingAvailability::::mutate(para_id, |maybe_andidates| { - if let Some(candidates) = maybe_andidates { + inclusion::PendingAvailability::::mutate(para_id, |maybe_candidates| { + if let Some(candidates) = maybe_candidates { candidates.push_back(candidate_availability); } else { - *maybe_andidates = + *maybe_candidates = Some([candidate_availability].into_iter().collect::>()); } }); diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index d09962ef2b44..19df1f8c3607 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -557,7 +557,7 @@ pub mod pallet { /// The list is sorted ascending by session index. Also, this list can only contain at most /// 2 items: for the next session and for the `scheduled_session`. #[pallet::storage] - pub(crate) type PendingConfigs = + pub type PendingConfigs = StorageValue<_, Vec<(SessionIndex, HostConfiguration>)>, ValueQuery>; /// If this is set, then the configuration setters will bypass the consistency checks. This diff --git a/polkadot/runtime/parachains/src/coretime/migration.rs b/polkadot/runtime/parachains/src/coretime/migration.rs index 4e7508867559..d4be135aad65 100644 --- a/polkadot/runtime/parachains/src/coretime/migration.rs +++ b/polkadot/runtime/parachains/src/coretime/migration.rs @@ -24,7 +24,6 @@ mod v_coretime { use crate::{ assigner_coretime, configuration, coretime::{mk_coretime_call, Config, PartsOf57600, WeightInfo}, - paras, }; use alloc::{vec, vec::Vec}; #[cfg(feature = "try-runtime")] @@ -51,18 +50,24 @@ mod v_coretime { 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; + // All parachains holding a lease, no matter if there are gaps in the slots or not. + fn get_all_parachains_with_leases() -> Vec; } /// 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( + pub struct MigrateToCoretime( core::marker::PhantomData<(T, SendXcm, LegacyLease)>, ); - impl>> - MigrateToCoretime + impl< + T: Config, + SendXcm: xcm::v4::SendXcm, + LegacyLease: GetLegacyLease>, + const TIMESLICE_PERIOD: u32, + > MigrateToCoretime { fn already_migrated() -> bool { // We are using the assigner coretime because the coretime pallet doesn't has any @@ -94,7 +99,8 @@ mod v_coretime { T: Config + crate::dmp::Config, SendXcm: xcm::v4::SendXcm, LegacyLease: GetLegacyLease>, - > OnRuntimeUpgrade for MigrateToCoretime + const TIMESLICE_PERIOD: u32, + > OnRuntimeUpgrade for MigrateToCoretime { fn on_runtime_upgrade() -> Weight { if Self::already_migrated() { @@ -102,7 +108,7 @@ mod v_coretime { } log::info!("Migrating existing parachains to coretime."); - migrate_to_coretime::() + migrate_to_coretime::() } #[cfg(feature = "try-runtime")] @@ -111,7 +117,7 @@ mod v_coretime { return Ok(Vec::new()) } - let legacy_paras = paras::Parachains::::get(); + let legacy_paras = LegacyLease::get_all_parachains_with_leases(); let config = configuration::ActiveConfig::::get(); let total_core_count = config.scheduler_params.num_cores + legacy_paras.len() as u32; @@ -154,8 +160,9 @@ mod v_coretime { T: Config, SendXcm: xcm::v4::SendXcm, LegacyLease: GetLegacyLease>, + const TIMESLICE_PERIOD: u32, >() -> Weight { - let legacy_paras = paras::Parachains::::get(); + let legacy_paras = LegacyLease::get_all_parachains_with_leases(); let legacy_count = legacy_paras.len() as u32; let now = frame_system::Pallet::::block_number(); for (core, para_id) in legacy_paras.into_iter().enumerate() { @@ -175,7 +182,6 @@ mod v_coretime { } let config = configuration::ActiveConfig::::get(); - // 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( @@ -193,7 +199,9 @@ mod v_coretime { c.scheduler_params.num_cores = total_cores; }); - if let Err(err) = migrate_send_assignments_to_coretime_chain::() { + if let Err(err) = + migrate_send_assignments_to_coretime_chain::( + ) { log::error!("Sending legacy chain data to coretime chain failed: {:?}", err); } @@ -210,8 +218,9 @@ mod v_coretime { T: Config, SendXcm: xcm::v4::SendXcm, LegacyLease: GetLegacyLease>, + const TIMESLICE_PERIOD: u32, >() -> result::Result<(), SendError> { - let legacy_paras = paras::Parachains::::get(); + let legacy_paras = LegacyLease::get_all_parachains_with_leases(); let legacy_paras_count = legacy_paras.len(); let (system_chains, lease_holding): (Vec<_>, Vec<_>) = legacy_paras.into_iter().partition(IsSystem::is_system); @@ -224,7 +233,7 @@ mod v_coretime { mk_coretime_call::(crate::coretime::CoretimeCalls::Reserve(schedule)) }); - let leases = lease_holding.into_iter().filter_map(|p| { + let mut 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?!"); @@ -237,10 +246,7 @@ mod v_coretime { return None }, }; - // We assume the coretime chain set this parameter to the recommended 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; + let time_slice = (valid_until + TIMESLICE_PERIOD - 1) / TIMESLICE_PERIOD; 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))) }); @@ -269,16 +275,30 @@ mod v_coretime { }); 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 leases_content_1 = message_content + .clone() + .chain(leases.by_ref().take(legacy_paras_count / 2)) // split in two messages to avoid overweighted XCM + .collect(); + let leases_content_2 = 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), - ]; + // If `pool_content` is empty don't send a blank XCM message + let messages = if core_count as usize > legacy_paras_count { + let pool_content = message_content.clone().chain(pool).collect(); + vec![ + Xcm(reservation_content), + Xcm(pool_content), + Xcm(leases_content_1), + Xcm(leases_content_2), + Xcm(set_core_count_content), + ] + } else { + vec![ + Xcm(reservation_content), + Xcm(leases_content_1), + Xcm(leases_content_2), + Xcm(set_core_count_content), + ] + }; for message in messages { send_xcm::( diff --git a/polkadot/runtime/parachains/src/coretime/mod.rs b/polkadot/runtime/parachains/src/coretime/mod.rs index fbd8935f1990..9b9bdb86878f 100644 --- a/polkadot/runtime/parachains/src/coretime/mod.rs +++ b/polkadot/runtime/parachains/src/coretime/mod.rs @@ -358,7 +358,10 @@ fn mk_coretime_call(call: crate::coretime::CoretimeCalls) -> Instruct fn do_notify_revenue(when: BlockNumber, raw_revenue: Balance) -> Result<(), XcmError> { let dest = Junction::Parachain(T::BrokerId::get()).into_location(); - let mut message = Vec::new(); + let mut message = vec![Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }]; let asset = Asset { id: AssetId(Location::here()), fun: Fungible(raw_revenue) }; let dummy_xcm_context = XcmContext { origin: None, message_id: [0; 32], topic: None }; @@ -384,10 +387,6 @@ fn do_notify_revenue(when: BlockNumber, raw_revenue: Balance) -> Resu message.extend( [ - Instruction::UnpaidExecution { - weight_limit: WeightLimit::Unlimited, - check_origin: None, - }, ReceiveTeleportedAsset(assets_reanchored), DepositAsset { assets: Wild(AllCounted(1)), diff --git a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs index c5284ba1dd1f..88b90e792e73 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs @@ -26,6 +26,20 @@ use polkadot_primitives::v7::GroupIndex; use crate::builder::BenchBuilder; benchmarks! { + enter_empty { + let scenario = BenchBuilder::::new() + .build(); + + let mut benchmark = scenario.data.clone(); + + benchmark.bitfields.clear(); + benchmark.backed_candidates.clear(); + benchmark.disputes.clear(); + }: enter(RawOrigin::None, benchmark) + verify { + // Assert that the block was not discarded + assert!(Included::::get().is_some()); + } // Variant over `v`, the number of dispute statements in a dispute statement set. This gives the // weight of a single dispute statement set. enter_variable_disputes { @@ -92,18 +106,8 @@ benchmarks! { // Variant over `v`, the amount of validity votes for a backed candidate. This gives the weight // of a single backed candidate. enter_backed_candidates_variable { - // NOTE: the starting value must be over half of the max validators per group so the backed - // candidate is not rejected. Also, we cannot have more validity votes than validators in - // the group. - - // Do not use this range for Rococo because it only has 1 validator per backing group, - // which causes issues when trying to create slopes with the benchmarking analysis. Instead - // use v = 1 for running Rococo benchmarks - let v in (BenchBuilder::::fallback_min_validity_votes()) - ..(BenchBuilder::::fallback_max_validators()); - - // Comment in for running rococo benchmarks - // let v = 1; + let v in (BenchBuilder::::fallback_min_backing_votes()) + ..(BenchBuilder::::fallback_max_validators_per_core()); let cores_with_backed: BTreeMap<_, _> = vec![(0, v)] // The backed candidate will have `v` validity votes. @@ -119,7 +123,6 @@ benchmarks! { // There is 1 backed, assert_eq!(benchmark.backed_candidates.len(), 1); // with `v` validity votes. - // let votes = v as usize; let votes = min(scheduler::Pallet::::group_validators(GroupIndex::from(0)).unwrap().len(), v as usize); assert_eq!(benchmark.backed_candidates.get(0).unwrap().validity_votes().len(), votes); @@ -157,7 +160,7 @@ benchmarks! { let v = crate::configuration::ActiveConfig::::get().max_code_size; let cores_with_backed: BTreeMap<_, _> - = vec![(0, BenchBuilder::::fallback_min_validity_votes())] + = vec![(0, BenchBuilder::::fallback_min_backing_votes())] .into_iter() .collect(); @@ -168,8 +171,10 @@ benchmarks! { let mut benchmark = scenario.data.clone(); - // let votes = BenchBuilder::::fallback_min_validity_votes() as usize; - let votes = min(scheduler::Pallet::::group_validators(GroupIndex::from(0)).unwrap().len(), BenchBuilder::::fallback_min_validity_votes() as usize); + let votes = min( + scheduler::Pallet::::group_validators(GroupIndex::from(0)).unwrap().len(), + BenchBuilder::::fallback_min_backing_votes() as usize + ); // There is 1 backed assert_eq!(benchmark.backed_candidates.len(), 1); diff --git a/polkadot/runtime/parachains/src/paras_inherent/weights.rs b/polkadot/runtime/parachains/src/paras_inherent/weights.rs index 37809396a823..3e84c132aa24 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/weights.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/weights.rs @@ -28,6 +28,8 @@ use polkadot_primitives::{ use super::{BackedCandidate, Config, DisputeStatementSet, Weight}; pub trait WeightInfo { + /// The weight of processing an empty parachain inherent. + fn enter_empty() -> Weight; /// Variant over `v`, the count of dispute statements in a dispute statement set. This gives the /// weight of a single dispute statement set. fn enter_variable_disputes(v: u32) -> Weight; @@ -45,6 +47,9 @@ pub struct TestWeightInfo; // mock. #[cfg(not(feature = "runtime-benchmarks"))] impl WeightInfo for TestWeightInfo { + fn enter_empty() -> Weight { + Weight::zero() + } fn enter_variable_disputes(v: u32) -> Weight { // MAX Block Weight should fit 4 disputes Weight::from_parts(80_000 * v as u64 + 80_000, 0) @@ -66,6 +71,9 @@ impl WeightInfo for TestWeightInfo { // running as a test. #[cfg(feature = "runtime-benchmarks")] impl WeightInfo for TestWeightInfo { + fn enter_empty() -> Weight { + Weight::zero() + } fn enter_variable_disputes(_v: u32) -> Weight { Weight::zero() } @@ -123,7 +131,8 @@ where set_proof_size_to_tx_size( <::WeightInfo as WeightInfo>::enter_variable_disputes( statement_set.as_ref().statements.len() as u32, - ), + ) + .saturating_sub(<::WeightInfo as WeightInfo>::enter_empty()), statement_set, ) } @@ -133,6 +142,7 @@ pub fn signed_bitfields_weight( ) -> Weight { set_proof_size_to_tx_size( <::WeightInfo as WeightInfo>::enter_bitfields() + .saturating_sub(<::WeightInfo as WeightInfo>::enter_empty()) .saturating_mul(bitfields.len() as u64), bitfields, ) @@ -140,7 +150,8 @@ pub fn signed_bitfields_weight( pub fn signed_bitfield_weight(bitfield: &UncheckedSignedAvailabilityBitfield) -> Weight { set_proof_size_to_tx_size( - <::WeightInfo as WeightInfo>::enter_bitfields(), + <::WeightInfo as WeightInfo>::enter_bitfields() + .saturating_sub(<::WeightInfo as WeightInfo>::enter_empty()), bitfield, ) } @@ -155,7 +166,8 @@ pub fn backed_candidate_weight( <::WeightInfo as WeightInfo>::enter_backed_candidates_variable( candidate.validity_votes().len() as u32, ) - }, + } + .saturating_sub(<::WeightInfo as WeightInfo>::enter_empty()), candidate, ) } diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 50970965e11e..4aaaf94da586 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -339,4 +339,4 @@ runtime-metrics = [ # 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 = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] diff --git a/polkadot/runtime/rococo/src/impls.rs b/polkadot/runtime/rococo/src/impls.rs index a4440a1c6e0b..f01440ea02bc 100644 --- a/polkadot/runtime/rococo/src/impls.rs +++ b/polkadot/runtime/rococo/src/impls.rs @@ -90,7 +90,7 @@ where fn on_reap_identity(who: &AccountId, fields: u32, subs: u32) -> DispatchResult { use crate::{ impls::IdentityMigratorCalls::PokeDeposit, - weights::runtime_common_identity_migrator::WeightInfo as MigratorWeights, + weights::polkadot_runtime_common_identity_migrator::WeightInfo as MigratorWeights, }; let total_to_send = Self::calculate_remote_deposit(fields, subs); diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 2f23b889916b..dfc41b15bb1d 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -674,7 +674,7 @@ impl claims::Config for Runtime { type VestingSchedule = Vesting; type Prefix = Prefix; type MoveClaimOrigin = EnsureRoot; - type WeightInfo = weights::runtime_common_claims::WeightInfo; + type WeightInfo = weights::polkadot_runtime_common_claims::WeightInfo; } parameter_types! { @@ -940,7 +940,7 @@ impl pallet_proxy::Config for Runtime { impl parachains_origin::Config for Runtime {} impl parachains_configuration::Config for Runtime { - type WeightInfo = weights::runtime_parachains_configuration::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_configuration::WeightInfo; } impl parachains_shared::Config for Runtime { @@ -963,7 +963,7 @@ impl parachains_inclusion::Config for Runtime { type DisputesHandler = ParasDisputes; type RewardValidators = RewardValidators; type MessageQueue = MessageQueue; - type WeightInfo = weights::runtime_parachains_inclusion::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_inclusion::WeightInfo; } parameter_types! { @@ -972,7 +972,7 @@ parameter_types! { impl parachains_paras::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type WeightInfo = weights::runtime_parachains_paras::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_paras::WeightInfo; type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; @@ -1046,11 +1046,11 @@ impl parachains_hrmp::Config for Runtime { HrmpChannelSizeAndCapacityWithSystemRatio, >; type VersionWrapper = crate::XcmPallet; - type WeightInfo = weights::runtime_parachains_hrmp::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_hrmp::WeightInfo; } impl parachains_paras_inherent::Config for Runtime { - type WeightInfo = weights::runtime_parachains_paras_inherent::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_paras_inherent::WeightInfo; } impl parachains_scheduler::Config for Runtime { @@ -1079,7 +1079,7 @@ impl coretime::Config for Runtime { type Currency = Balances; type BrokerId = BrokerId; type BrokerPotLocation = BrokerPot; - type WeightInfo = weights::runtime_parachains_coretime::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_coretime::WeightInfo; type SendXcm = crate::xcm_config::XcmRouter; type AssetTransactor = crate::xcm_config::LocalAssetTransactor; type AccountToLocation = xcm_builder::AliasesIntoAccountId32< @@ -1100,7 +1100,7 @@ impl parachains_on_demand::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type TrafficDefaultValue = OnDemandTrafficDefaultValue; - type WeightInfo = weights::runtime_parachains_on_demand::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_on_demand::WeightInfo; type MaxHistoricalRevenue = MaxHistoricalRevenue; type PalletId = OnDemandPalletId; } @@ -1110,7 +1110,7 @@ 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 WeightInfo = weights::polkadot_runtime_parachains_initializer::WeightInfo; type CoretimeOnNewSession = Coretime; } @@ -1118,7 +1118,7 @@ impl parachains_disputes::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RewardValidators = (); type SlashingHandler = parachains_slashing::SlashValidatorsForDisputes; - type WeightInfo = weights::runtime_parachains_disputes::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_disputes::WeightInfo; } impl parachains_slashing::Config for Runtime { @@ -1149,7 +1149,7 @@ impl paras_registrar::Config for Runtime { type OnSwap = (Crowdloan, Slots, SwapLeases); type ParaDeposit = ParaDeposit; type DataDepositPerByte = DataDepositPerByte; - type WeightInfo = weights::runtime_common_paras_registrar::WeightInfo; + type WeightInfo = weights::polkadot_runtime_common_paras_registrar::WeightInfo; } parameter_types! { @@ -1163,7 +1163,7 @@ impl slots::Config for Runtime { type LeasePeriod = LeasePeriod; type LeaseOffset = (); type ForceOrigin = EitherOf, LeaseAdmin>; - type WeightInfo = weights::runtime_common_slots::WeightInfo; + type WeightInfo = weights::polkadot_runtime_common_slots::WeightInfo; } parameter_types! { @@ -1184,7 +1184,7 @@ impl crowdloan::Config for Runtime { type Registrar = Registrar; type Auctioneer = Auctions; type MaxMemoLength = MaxMemoLength; - type WeightInfo = weights::runtime_common_crowdloan::WeightInfo; + type WeightInfo = weights::polkadot_runtime_common_crowdloan::WeightInfo; } parameter_types! { @@ -1203,14 +1203,14 @@ impl auctions::Config for Runtime { type SampleLength = SampleLength; type Randomness = pallet_babe::RandomnessFromOneEpochAgo; type InitiateOrigin = EitherOf, AuctionAdmin>; - type WeightInfo = weights::runtime_common_auctions::WeightInfo; + type WeightInfo = weights::polkadot_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 WeightInfo = weights::polkadot_runtime_common_identity_migrator::WeightInfo; } type NisCounterpartInstance = pallet_balances::Instance2; @@ -1335,6 +1335,7 @@ impl pallet_beefy_mmr::Config for Runtime { type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum; type LeafExtra = H256; type BeefyDataProvider = ParaHeadsRootProvider; + type WeightInfo = weights::pallet_beefy_mmr::WeightInfo; } impl paras_sudo_wrapper::Config for Runtime {} @@ -1352,7 +1353,7 @@ impl assigned_slots::Config for Runtime { type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod; - type WeightInfo = weights::runtime_common_assigned_slots::WeightInfo; + type WeightInfo = weights::polkadot_runtime_common_assigned_slots::WeightInfo; } impl validator_manager::Config for Runtime { @@ -1579,6 +1580,13 @@ pub mod migrations { as Leaser>::lease_period_index(now)?; Some(index.saturating_add(lease.len() as u32).saturating_mul(LeasePeriod::get())) } + + fn get_all_parachains_with_leases() -> Vec { + slots::Leases::::iter() + .filter(|(_, lease)| !lease.is_empty()) + .map(|(para, _)| para) + .collect::>() + } } parameter_types! { @@ -1629,47 +1637,45 @@ pub mod migrations { /// Unreleased migrations. Add new ones here: pub type Unreleased = ( - pallet_society::migrations::MigrateToV2, - parachains_configuration::migration::v7::MigrateToV7, - assigned_slots::migration::v1::MigrateToV1, - parachains_scheduler::migration::MigrateV1ToV2, - parachains_configuration::migration::v8::MigrateToV8, - parachains_configuration::migration::v9::MigrateToV9, - paras_registrar::migration::MigrateToV1, - pallet_referenda::migration::v1::MigrateV0ToV1, - pallet_referenda::migration::v1::MigrateV0ToV1, - - // Unlock & unreserve Gov1 funds - - pallet_elections_phragmen::migrations::unlock_and_unreserve_all_funds::UnlockAndUnreserveAllFunds, - pallet_democracy::migrations::unlock_and_unreserve_all_funds::UnlockAndUnreserveAllFunds, - pallet_tips::migrations::unreserve_deposits::UnreserveDeposits, - - // Delete all Gov v1 pallet storage key/values. - - frame_support::migrations::RemovePallet::DbWeight>, - frame_support::migrations::RemovePallet::DbWeight>, - frame_support::migrations::RemovePallet::DbWeight>, - frame_support::migrations::RemovePallet::DbWeight>, - frame_support::migrations::RemovePallet::DbWeight>, - frame_support::migrations::RemovePallet::DbWeight>, - - pallet_grandpa::migrations::MigrateV4ToV5, - parachains_configuration::migration::v10::MigrateToV10, - - // 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, - parachains_on_demand::migration::MigrateV0ToV1, - - // permanent - pallet_xcm::migration::MigrateToLatestXcmVersion, - - parachains_inclusion::migration::MigrateToV1, - ); + pallet_society::migrations::MigrateToV2, + parachains_configuration::migration::v7::MigrateToV7, + assigned_slots::migration::v1::MigrateToV1, + parachains_scheduler::migration::MigrateV1ToV2, + parachains_configuration::migration::v8::MigrateToV8, + parachains_configuration::migration::v9::MigrateToV9, + paras_registrar::migration::MigrateToV1, + pallet_referenda::migration::v1::MigrateV0ToV1, + pallet_referenda::migration::v1::MigrateV0ToV1, + + // Unlock & unreserve Gov1 funds + + pallet_elections_phragmen::migrations::unlock_and_unreserve_all_funds::UnlockAndUnreserveAllFunds, + pallet_democracy::migrations::unlock_and_unreserve_all_funds::UnlockAndUnreserveAllFunds, + pallet_tips::migrations::unreserve_deposits::UnreserveDeposits, + + // Delete all Gov v1 pallet storage key/values. + + frame_support::migrations::RemovePallet::DbWeight>, + frame_support::migrations::RemovePallet::DbWeight>, + frame_support::migrations::RemovePallet::DbWeight>, + frame_support::migrations::RemovePallet::DbWeight>, + frame_support::migrations::RemovePallet::DbWeight>, + frame_support::migrations::RemovePallet::DbWeight>, + pallet_grandpa::migrations::MigrateV4ToV5, + parachains_configuration::migration::v10::MigrateToV10, + + // 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, + parachains_on_demand::migration::MigrateV0ToV1, + + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, + parachains_inclusion::migration::MigrateToV1, + ); } /// Executive: handles dispatch to the various modules. @@ -1735,6 +1741,7 @@ mod benches { // Substrate [pallet_balances, Balances] [pallet_balances, NisCounterpartBalances] + [pallet_beefy_mmr, MmrLeaf] [frame_benchmarking::baseline, Baseline::] [pallet_bounties, Bounties] [pallet_child_bounties, ChildBounties] diff --git a/polkadot/runtime/rococo/src/weights/mod.rs b/polkadot/runtime/rococo/src/weights/mod.rs index 0512a393a6c4..020f8e22594a 100644 --- a/polkadot/runtime/rococo/src/weights/mod.rs +++ b/polkadot/runtime/rococo/src/weights/mod.rs @@ -19,6 +19,7 @@ pub mod frame_system; pub mod pallet_asset_rate; pub mod pallet_balances_balances; pub mod pallet_balances_nis_counterpart_balances; +pub mod pallet_beefy_mmr; pub mod pallet_bounties; pub mod pallet_child_bounties; pub mod pallet_conviction_voting; @@ -43,20 +44,20 @@ pub mod pallet_utility; pub mod pallet_vesting; pub mod pallet_whitelist; pub mod pallet_xcm; -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_configuration; -pub mod runtime_parachains_coretime; -pub mod runtime_parachains_disputes; -pub mod runtime_parachains_hrmp; -pub mod runtime_parachains_inclusion; -pub mod runtime_parachains_initializer; -pub mod runtime_parachains_on_demand; -pub mod runtime_parachains_paras; -pub mod runtime_parachains_paras_inherent; +pub mod polkadot_runtime_common_assigned_slots; +pub mod polkadot_runtime_common_auctions; +pub mod polkadot_runtime_common_claims; +pub mod polkadot_runtime_common_crowdloan; +pub mod polkadot_runtime_common_identity_migrator; +pub mod polkadot_runtime_common_paras_registrar; +pub mod polkadot_runtime_common_slots; +pub mod polkadot_runtime_parachains_configuration; +pub mod polkadot_runtime_parachains_coretime; +pub mod polkadot_runtime_parachains_disputes; +pub mod polkadot_runtime_parachains_hrmp; +pub mod polkadot_runtime_parachains_inclusion; +pub mod polkadot_runtime_parachains_initializer; +pub mod polkadot_runtime_parachains_on_demand; +pub mod polkadot_runtime_parachains_paras; +pub mod polkadot_runtime_parachains_paras_inherent; pub mod xcm; diff --git a/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs b/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs new file mode 100644 index 000000000000..317c9149ec6c --- /dev/null +++ b/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs @@ -0,0 +1,89 @@ +// 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_beefy_mmr` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-696hpswk-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=pallet_beefy_mmr +// --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; + +/// Weight functions for `pallet_beefy_mmr`. +pub struct WeightInfo(PhantomData); +impl pallet_beefy_mmr::WeightInfo for WeightInfo { + /// Storage: `System::BlockHash` (r:1 w:0) + /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + fn extract_validation_context() -> Weight { + // Proof Size summary in bytes: + // Measured: `92` + // Estimated: `3509` + // Minimum execution time: 7_116_000 picoseconds. + Weight::from_parts(7_343_000, 0) + .saturating_add(Weight::from_parts(0, 3509)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `Mmr::Nodes` (r:1 w:0) + /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + fn read_peak() -> Weight { + // Proof Size summary in bytes: + // Measured: `234` + // Estimated: `3505` + // Minimum execution time: 5_652_000 picoseconds. + Weight::from_parts(5_963_000, 0) + .saturating_add(Weight::from_parts(0, 3505)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `Mmr::RootHash` (r:1 w:0) + /// Proof: `Mmr::RootHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `Mmr::NumberOfLeaves` (r:1 w:0) + /// Proof: `Mmr::NumberOfLeaves` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 512]`. + fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `226` + // Estimated: `1517` + // Minimum execution time: 11_953_000 picoseconds. + Weight::from_parts(15_978_891, 0) + .saturating_add(Weight::from_parts(0, 1517)) + // Standard Error: 1_780 + .saturating_add(Weight::from_parts(1_480_582, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2)) + } +} diff --git a/polkadot/runtime/rococo/src/weights/runtime_common_assigned_slots.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_common_assigned_slots.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_common_assigned_slots.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_common_assigned_slots.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_common_auctions.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_common_auctions.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_common_auctions.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_common_auctions.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_common_claims.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_common_claims.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_common_claims.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_common_claims.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_common_crowdloan.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_common_crowdloan.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_common_crowdloan.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_common_crowdloan.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_common_identity_migrator.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_common_identity_migrator.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_common_identity_migrator.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_common_identity_migrator.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_common_paras_registrar.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_common_paras_registrar.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_common_paras_registrar.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_common_paras_registrar.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_common_slots.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_common_slots.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_common_slots.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_common_slots.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_configuration.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_configuration.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_parachains_configuration.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_configuration.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_coretime.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_coretime.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_parachains_coretime.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_coretime.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_disputes.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_disputes.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_parachains_disputes.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_disputes.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_hrmp.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_hrmp.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_parachains_hrmp.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_hrmp.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_inclusion.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_inclusion.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_parachains_inclusion.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_inclusion.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_initializer.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_initializer.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_parachains_initializer.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_initializer.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_on_demand.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_on_demand.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_parachains_on_demand.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_on_demand.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_paras.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras.rs similarity index 100% rename from polkadot/runtime/rococo/src/weights/runtime_parachains_paras.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras.rs diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_paras_inherent.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras_inherent.rs similarity index 82% rename from polkadot/runtime/rococo/src/weights/runtime_parachains_paras_inherent.rs rename to polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras_inherent.rs index c00966fb8048..b7b3d12d4d92 100644 --- a/polkadot/runtime/rococo/src/weights/runtime_parachains_paras_inherent.rs +++ b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_paras_inherent.rs @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Autogenerated weights for `runtime_parachains::paras_inherent` +//! Autogenerated weights for `polkadot_runtime_parachains::paras_inherent` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-08-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -32,7 +32,7 @@ // --wasm-execution=compiled // --heap-pages=4096 // --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=runtime_parachains::paras_inherent +// --pallet=polkadot_runtime_parachains::paras_inherent // --chain=rococo-dev // --header=./polkadot/file_header.txt // --output=./polkadot/runtime/rococo/src/weights/ @@ -45,9 +45,49 @@ use frame_support::{traits::Get, weights::Weight}; use core::marker::PhantomData; -/// Weight functions for `runtime_parachains::paras_inherent`. +/// Weight functions for `polkadot_runtime_parachains::paras_inherent`. pub struct WeightInfo(PhantomData); impl polkadot_runtime_parachains::paras_inherent::WeightInfo for WeightInfo { + /// Storage: `ParaInherent::Included` (r:1 w:1) + /// Proof: `ParaInherent::Included` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::ParentHash` (r:1 w:0) + /// Proof: `System::ParentHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `ParasShared::AllowedRelayParents` (r:1 w:1) + /// Proof: `ParasShared::AllowedRelayParents` (`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`) + /// Storage: `ParaScheduler::AvailabilityCores` (r:1 w:1) + /// Proof: `ParaScheduler::AvailabilityCores` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::ActiveValidatorKeys` (r:1 w:0) + /// Proof: `ParasShared::ActiveValidatorKeys` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Babe::AuthorVrfRandomness` (r:1 w:0) + /// Proof: `Babe::AuthorVrfRandomness` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + /// Storage: `ParaInherent::OnChainVotes` (r:1 w:1) + /// Proof: `ParaInherent::OnChainVotes` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasDisputes::Frozen` (r:1 w:0) + /// Proof: `ParasDisputes::Frozen` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParaInclusion::V1` (r:1 w:0) + /// Proof: `ParaInclusion::V1` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ParaScheduler::SessionStartBlock` (r:1 w:0) + /// Proof: `ParaScheduler::SessionStartBlock` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParaScheduler::ValidatorGroups` (r:1 w:0) + /// Proof: `ParaScheduler::ValidatorGroups` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParaScheduler::ClaimQueue` (r:1 w:1) + /// Proof: `ParaScheduler::ClaimQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::ActiveValidatorIndices` (r:1 w:0) + /// Proof: `ParasShared::ActiveValidatorIndices` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Session::DisabledValidators` (r:1 w:0) + /// Proof: `Session::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn enter_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `8967` + // Estimated: `12432` + // Minimum execution time: 144_751_000 picoseconds. + Weight::from_parts(153_966_000, 0) + .saturating_add(Weight::from_parts(0, 12432)) + .saturating_add(T::DbWeight::get().reads(15)) + .saturating_add(T::DbWeight::get().writes(5)) + } /// Storage: `ParaInherent::Included` (r:1 w:1) /// Proof: `ParaInherent::Included` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::ParentHash` (r:1 w:0) @@ -109,13 +149,13 @@ impl polkadot_runtime_parachains::paras_inherent::Weigh /// The range of component `v` is `[10, 200]`. fn enter_variable_disputes(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `67785` - // Estimated: `73725 + v * (23 ±0)` - // Minimum execution time: 949_716_000 picoseconds. - Weight::from_parts(482_361_515, 0) - .saturating_add(Weight::from_parts(0, 73725)) - // Standard Error: 17_471 - .saturating_add(Weight::from_parts(50_100_764, 0).saturating_mul(v.into())) + // Measured: `67786` + // Estimated: `73726 + v * (23 ±0)` + // Minimum execution time: 972_311_000 picoseconds. + Weight::from_parts(645_559_304, 0) + .saturating_add(Weight::from_parts(0, 73726)) + // Standard Error: 53_320 + .saturating_add(Weight::from_parts(41_795_493, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(25)) .saturating_add(T::DbWeight::get().writes(15)) .saturating_add(Weight::from_parts(0, 23).saturating_mul(v.into())) @@ -140,18 +180,6 @@ impl polkadot_runtime_parachains::paras_inherent::Weigh /// Proof: `ParasDisputes::Frozen` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParaInclusion::V1` (r:2 w:1) /// Proof: `ParaInclusion::V1` (`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::DeliveryFeeFactor` (r:1 w:1) - /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Hrmp::HrmpChannelDigests` (r:1 w:1) - /// Proof: `Hrmp::HrmpChannelDigests` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Paras::FutureCodeUpgrades` (r:1 w:0) - /// Proof: `Paras::FutureCodeUpgrades` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Registrar::Paras` (r:1 w:0) - /// Proof: `Registrar::Paras` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ParasDisputes::Disputes` (r:1 w:0) - /// Proof: `ParasDisputes::Disputes` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParaScheduler::SessionStartBlock` (r:1 w:0) /// Proof: `ParaScheduler::SessionStartBlock` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParaScheduler::ValidatorGroups` (r:1 w:0) @@ -164,25 +192,15 @@ impl polkadot_runtime_parachains::paras_inherent::Weigh /// Proof: `ParasShared::ActiveValidatorIndices` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Session::DisabledValidators` (r:1 w:0) /// Proof: `Session::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParasDisputes::Included` (r:0 w:1) - /// Proof: `ParasDisputes::Included` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Hrmp::HrmpWatermarks` (r:0 w:1) - /// Proof: `Hrmp::HrmpWatermarks` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Paras::Heads` (r:0 w:1) - /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Paras::UpgradeGoAheadSignal` (r:0 w:1) - /// Proof: `Paras::UpgradeGoAheadSignal` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Paras::MostRecentContext` (r:0 w:1) - /// Proof: `Paras::MostRecentContext` (`max_values`: None, `max_size`: None, mode: `Measured`) fn enter_bitfields() -> Weight { // Proof Size summary in bytes: - // Measured: `42757` - // Estimated: `48697` - // Minimum execution time: 437_627_000 picoseconds. - Weight::from_parts(460_975_000, 0) - .saturating_add(Weight::from_parts(0, 48697)) - .saturating_add(T::DbWeight::get().reads(23)) - .saturating_add(T::DbWeight::get().writes(15)) + // Measured: `42374` + // Estimated: `48314` + // Minimum execution time: 361_262_000 picoseconds. + Weight::from_parts(370_617_000, 0) + .saturating_add(Weight::from_parts(0, 48314)) + .saturating_add(T::DbWeight::get().reads(17)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `ParaInherent::Included` (r:1 w:1) /// Proof: `ParaInherent::Included` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -247,13 +265,13 @@ impl polkadot_runtime_parachains::paras_inherent::Weigh /// The range of component `v` is `[101, 200]`. fn enter_backed_candidates_variable(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `42829` - // Estimated: `48769` - // Minimum execution time: 1_305_254_000 picoseconds. - Weight::from_parts(1_347_160_667, 0) - .saturating_add(Weight::from_parts(0, 48769)) - // Standard Error: 22_128 - .saturating_add(Weight::from_parts(57_229, 0).saturating_mul(v.into())) + // Measured: `42830` + // Estimated: `48770` + // Minimum execution time: 1_322_051_000 picoseconds. + Weight::from_parts(1_379_846_608, 0) + .saturating_add(Weight::from_parts(0, 48770)) + // Standard Error: 19_959 + .saturating_add(Weight::from_parts(24_630, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(26)) .saturating_add(T::DbWeight::get().writes(15)) } @@ -323,11 +341,11 @@ impl polkadot_runtime_parachains::paras_inherent::Weigh /// Proof: `Paras::MostRecentContext` (`max_values`: None, `max_size`: None, mode: `Measured`) fn enter_backed_candidate_code_upgrade() -> Weight { // Proof Size summary in bytes: - // Measured: `42842` - // Estimated: `48782` - // Minimum execution time: 38_637_547_000 picoseconds. - Weight::from_parts(41_447_412_000, 0) - .saturating_add(Weight::from_parts(0, 48782)) + // Measured: `42843` + // Estimated: `48783` + // Minimum execution time: 37_550_515_000 picoseconds. + Weight::from_parts(37_886_489_000, 0) + .saturating_add(Weight::from_parts(0, 48783)) .saturating_add(T::DbWeight::get().reads(28)) .saturating_add(T::DbWeight::get().writes(15)) } diff --git a/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 60c40429b1ac..7d743b209124 100644 --- a/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -16,10 +16,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: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-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` +//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 // Executed Command: @@ -55,8 +55,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 23_189_000 picoseconds. - Weight::from_parts(23_896_000, 3593) + // Minimum execution time: 30_672_000 picoseconds. + Weight::from_parts(31_677_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -66,8 +66,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 50_299_000 picoseconds. - Weight::from_parts(50_962_000, 6196) + // Minimum execution time: 41_132_000 picoseconds. + Weight::from_parts(41_654_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -83,10 +83,10 @@ impl WeightInfo { /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) pub(crate) fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `243` + // Measured: `281` // Estimated: `6196` - // Minimum execution time: 71_748_000 picoseconds. - Weight::from_parts(74_072_000, 6196) + // Minimum execution time: 97_174_000 picoseconds. + Weight::from_parts(99_537_000, 6196) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -105,16 +105,18 @@ impl WeightInfo { /// 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: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) pub(crate) fn initiate_reserve_withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 27_806_000 picoseconds. - Weight::from_parts(28_594_000, 3607) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `281` + // Estimated: `3746` + // Minimum execution time: 67_105_000 picoseconds. + Weight::from_parts(68_659_000, 3746) + .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`) @@ -122,8 +124,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 21_199_000 picoseconds. - Weight::from_parts(21_857_000, 3593) + // Minimum execution time: 30_780_000 picoseconds. + Weight::from_parts(31_496_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -133,27 +135,27 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 23_578_000 picoseconds. - Weight::from_parts(24_060_000, 3593) + // Minimum execution time: 23_411_000 picoseconds. + Weight::from_parts(23_891_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: `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: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) pub(crate) fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 48_522_000 picoseconds. - Weight::from_parts(49_640_000, 3607) + // Measured: `180` + // Estimated: `3645` + // Minimum execution time: 61_541_000 picoseconds. + Weight::from_parts(63_677_000, 3645) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -169,10 +171,10 @@ impl WeightInfo { /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) pub(crate) fn initiate_teleport() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 50_429_000 picoseconds. - Weight::from_parts(51_295_000, 3607) + // Measured: `180` + // Estimated: `3645` + // Minimum execution time: 48_574_000 picoseconds. + Weight::from_parts(49_469_000, 3645) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 4226595cd2ff..83c0eb037f4a 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -358,4 +358,4 @@ runtime-metrics = [ # 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 = ["metadata-hash", "sp-api/disable-logging"] +on-chain-release-build = ["metadata-hash"] diff --git a/polkadot/runtime/westend/src/impls.rs b/polkadot/runtime/westend/src/impls.rs index 11665953bd8e..ac3f9e679f8d 100644 --- a/polkadot/runtime/westend/src/impls.rs +++ b/polkadot/runtime/westend/src/impls.rs @@ -90,7 +90,7 @@ where fn on_reap_identity(who: &AccountId, fields: u32, subs: u32) -> DispatchResult { use crate::{ impls::IdentityMigratorCalls::PokeDeposit, - weights::runtime_common_identity_migrator::WeightInfo as MigratorWeights, + weights::polkadot_runtime_common_identity_migrator::WeightInfo as MigratorWeights, }; let total_to_send = Self::calculate_remote_deposit(fields, subs); diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 838ba17e5613..e8fe11615d74 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -65,7 +65,7 @@ use polkadot_runtime_common::{ VersionedLocatableAsset, VersionedLocationConverter, }, paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, - traits::{Leaser, OnSwap}, + traits::OnSwap, BalanceToU256, BlockHashCount, BlockLength, CurrencyToVote, SlowAdjustingFeeUpdate, U256ToBalance, }; @@ -99,7 +99,7 @@ use sp_runtime::{ IdentityLookup, Keccak256, OpaqueKeys, SaturatedConversion, Verify, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, FixedU128, KeyTypeId, Perbill, Percent, Permill, + ApplyExtrinsicResult, FixedU128, KeyTypeId, Percent, Permill, }; use sp_staking::SessionIndex; #[cfg(any(feature = "std", test))] @@ -461,6 +461,7 @@ impl pallet_beefy_mmr::Config for Runtime { type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum; type LeafExtra = H256; type BeefyDataProvider = ParaHeadsRootProvider; + type WeightInfo = weights::pallet_beefy_mmr::WeightInfo; } parameter_types! { @@ -601,20 +602,20 @@ impl pallet_election_provider_multi_phase::MinerConfig for Runtime { type MaxWeight = OffchainSolutionWeightLimit; type Solution = NposCompactSolution16; type MaxVotesPerVoter = < - ::DataProvider - as - frame_election_provider_support::ElectionDataProvider - >::MaxVotesPerVoter; + ::DataProvider + as + frame_election_provider_support::ElectionDataProvider + >::MaxVotesPerVoter; type MaxWinners = MaxActiveValidators; // The unsigned submissions have to respect the weight of the submit_unsigned call, thus their // weight estimate function is wired to this call's weight. fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight { < - ::WeightInfo - as - pallet_election_provider_multi_phase::WeightInfo - >::submit_unsigned(v, t, a, d) + ::WeightInfo + as + pallet_election_provider_multi_phase::WeightInfo + >::submit_unsigned(v, t, a, d) } } @@ -1177,7 +1178,7 @@ impl pallet_proxy::Config for Runtime { impl parachains_origin::Config for Runtime {} impl parachains_configuration::Config for Runtime { - type WeightInfo = weights::runtime_parachains_configuration::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_configuration::WeightInfo; } impl parachains_shared::Config for Runtime { @@ -1193,7 +1194,7 @@ impl parachains_inclusion::Config for Runtime { type DisputesHandler = ParasDisputes; type RewardValidators = parachains_reward_points::RewardValidatorsWithEraPoints; type MessageQueue = MessageQueue; - type WeightInfo = weights::runtime_parachains_inclusion::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_inclusion::WeightInfo; } parameter_types! { @@ -1202,7 +1203,7 @@ parameter_types! { impl parachains_paras::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type WeightInfo = weights::runtime_parachains_paras::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_paras::WeightInfo; type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; @@ -1276,11 +1277,11 @@ impl parachains_hrmp::Config for Runtime { HrmpChannelSizeAndCapacityWithSystemRatio, >; type VersionWrapper = crate::XcmPallet; - type WeightInfo = weights::runtime_parachains_hrmp::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_hrmp::WeightInfo; } impl parachains_paras_inherent::Config for Runtime { - type WeightInfo = weights::runtime_parachains_paras_inherent::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_paras_inherent::WeightInfo; } impl parachains_scheduler::Config for Runtime { @@ -1309,7 +1310,7 @@ impl coretime::Config for Runtime { type Currency = Balances; type BrokerId = BrokerId; type BrokerPotLocation = BrokerPot; - type WeightInfo = weights::runtime_parachains_coretime::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_coretime::WeightInfo; type SendXcm = crate::xcm_config::XcmRouter; type AssetTransactor = crate::xcm_config::LocalAssetTransactor; type AccountToLocation = xcm_builder::AliasesIntoAccountId32< @@ -1330,7 +1331,7 @@ impl parachains_on_demand::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type TrafficDefaultValue = OnDemandTrafficDefaultValue; - type WeightInfo = weights::runtime_parachains_on_demand::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_on_demand::WeightInfo; type MaxHistoricalRevenue = MaxHistoricalRevenue; type PalletId = OnDemandPalletId; } @@ -1340,7 +1341,7 @@ 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 WeightInfo = weights::polkadot_runtime_parachains_initializer::WeightInfo; type CoretimeOnNewSession = Coretime; } @@ -1359,14 +1360,14 @@ impl assigned_slots::Config for Runtime { type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod; - type WeightInfo = weights::runtime_common_assigned_slots::WeightInfo; + type WeightInfo = weights::polkadot_runtime_common_assigned_slots::WeightInfo; } impl parachains_disputes::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RewardValidators = parachains_reward_points::RewardValidatorsWithEraPoints; type SlashingHandler = parachains_slashing::SlashValidatorsForDisputes; - type WeightInfo = weights::runtime_parachains_disputes::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_disputes::WeightInfo; } impl parachains_slashing::Config for Runtime { @@ -1382,7 +1383,7 @@ impl parachains_slashing::Config for Runtime { Offences, ReportLongevity, >; - type WeightInfo = weights::runtime_parachains_disputes_slashing::WeightInfo; + type WeightInfo = weights::polkadot_runtime_parachains_disputes_slashing::WeightInfo; type BenchmarkingConfig = parachains_slashing::BenchConfig<300>; } @@ -1398,7 +1399,7 @@ impl paras_registrar::Config for Runtime { type OnSwap = (Crowdloan, Slots, SwapLeases); type ParaDeposit = ParaDeposit; type DataDepositPerByte = RegistrarDataDepositPerByte; - type WeightInfo = weights::runtime_common_paras_registrar::WeightInfo; + type WeightInfo = weights::polkadot_runtime_common_paras_registrar::WeightInfo; } parameter_types! { @@ -1412,7 +1413,7 @@ impl slots::Config for Runtime { type LeasePeriod = LeasePeriod; type LeaseOffset = (); type ForceOrigin = EitherOf, LeaseAdmin>; - type WeightInfo = weights::runtime_common_slots::WeightInfo; + type WeightInfo = weights::polkadot_runtime_common_slots::WeightInfo; } parameter_types! { @@ -1433,7 +1434,7 @@ impl crowdloan::Config for Runtime { type Registrar = Registrar; type Auctioneer = Auctions; type MaxMemoLength = MaxMemoLength; - type WeightInfo = weights::runtime_common_crowdloan::WeightInfo; + type WeightInfo = weights::polkadot_runtime_common_crowdloan::WeightInfo; } parameter_types! { @@ -1452,14 +1453,14 @@ impl auctions::Config for Runtime { type SampleLength = SampleLength; type Randomness = pallet_babe::RandomnessFromOneEpochAgo; type InitiateOrigin = EitherOf, AuctionAdmin>; - type WeightInfo = weights::runtime_common_auctions::WeightInfo; + type WeightInfo = weights::polkadot_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 WeightInfo = weights::polkadot_runtime_common_identity_migrator::WeightInfo; } parameter_types! { @@ -1759,11 +1760,8 @@ pub type SignedExtra = ( ); parameter_types! { - // This is the max pools that will be migrated in the runtime upgrade. Westend has more pools - // than this, but we want to emulate some non migrated pools. In prod runtimes, if weight is not - // a concern, it is recommended to set to (existing pools + 10) to also account for any new - // pools getting created before the migration is actually executed. - pub const MaxPoolsToMigrate: u32 = 250; + /// Bounding number of agent pot accounts to be migrated in a single block. + pub const MaxAgentsToMigrate: u32 = 300; } /// All migrations that will run on the next runtime upgrade. @@ -1777,33 +1775,13 @@ pub type Migrations = migrations::Unreleased; pub mod migrations { use super::*; - 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::Leases::::get(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())) - } - } - /// Unreleased migrations. Add new ones here: pub type Unreleased = ( - // Migrate NominationPools to `DelegateStake` adapter. This is unversioned upgrade and - // should not be applied yet in Kusama/Polkadot. - pallet_nomination_pools::migration::unversioned::DelegationStakeMigration< + // This is only needed for Westend. + pallet_delegated_staking::migration::unversioned::ProxyDelegatorMigration< Runtime, - MaxPoolsToMigrate, + MaxAgentsToMigrate, >, - pallet_staking::migrations::v15::MigrateV14ToV15, ); } @@ -1847,6 +1825,7 @@ mod benches { // Substrate [pallet_bags_list, VoterList] [pallet_balances, Balances] + [pallet_beefy_mmr, BeefyMmrLeaf] [pallet_conviction_voting, ConvictionVoting] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] [frame_election_provider_support, ElectionProviderBench::] @@ -2474,6 +2453,14 @@ sp_api::impl_runtime_apis! { fn member_needs_delegate_migration(member: AccountId) -> bool { NominationPools::api_member_needs_delegate_migration(member) } + + fn member_total_balance(member: AccountId) -> Balance { + NominationPools::api_member_total_balance(member) + } + + fn pool_balance(pool_id: pallet_nomination_pools::PoolId) -> Balance { + NominationPools::api_pool_balance(pool_id) + } } impl pallet_staking_runtime_api::StakingApi for Runtime { @@ -2774,45 +2761,6 @@ sp_api::impl_runtime_apis! { } } -#[cfg(all(test, feature = "try-runtime"))] -mod remote_tests { - use super::*; - use frame_try_runtime::{runtime_decl_for_try_runtime::TryRuntime, UpgradeCheckSelect}; - use remote_externalities::{ - Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig, Transport, - }; - use std::env::var; - - #[tokio::test] - async fn run_migrations() { - if var("RUN_MIGRATION_TESTS").is_err() { - return; - } - - sp_tracing::try_init_simple(); - let transport: Transport = - var("WS").unwrap_or("wss://westend-rpc.polkadot.io:443".to_string()).into(); - let maybe_state_snapshot: Option = var("SNAP").map(|s| s.into()).ok(); - let mut ext = Builder::::default() - .mode(if let Some(state_snapshot) = maybe_state_snapshot { - Mode::OfflineOrElseOnline( - OfflineConfig { state_snapshot: state_snapshot.clone() }, - OnlineConfig { - transport, - state_snapshot: Some(state_snapshot), - ..Default::default() - }, - ) - } else { - Mode::Online(OnlineConfig { transport, ..Default::default() }) - }) - .build() - .await - .unwrap(); - ext.execute_with(|| Runtime::on_runtime_upgrade(UpgradeCheckSelect::PreAndPost)); - } -} - mod clean_state_migration { use super::Runtime; #[cfg(feature = "try-runtime")] diff --git a/polkadot/runtime/westend/src/tests.rs b/polkadot/runtime/westend/src/tests.rs index 4d5e2e946bce..dc8103ab52c4 100644 --- a/polkadot/runtime/westend/src/tests.rs +++ b/polkadot/runtime/westend/src/tests.rs @@ -99,3 +99,140 @@ fn check_treasury_pallet_id() { westend_runtime_constants::TREASURY_PALLET_ID ); } + +#[cfg(all(test, feature = "try-runtime"))] +mod remote_tests { + use super::*; + use frame_try_runtime::{runtime_decl_for_try_runtime::TryRuntime, UpgradeCheckSelect}; + use remote_externalities::{ + Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig, Transport, + }; + use std::env::var; + + #[tokio::test] + async fn run_migrations() { + if var("RUN_MIGRATION_TESTS").is_err() { + return; + } + + sp_tracing::try_init_simple(); + let transport: Transport = + var("WS").unwrap_or("wss://westend-rpc.polkadot.io:443".to_string()).into(); + let maybe_state_snapshot: Option = var("SNAP").map(|s| s.into()).ok(); + let mut ext = Builder::::default() + .mode(if let Some(state_snapshot) = maybe_state_snapshot { + Mode::OfflineOrElseOnline( + OfflineConfig { state_snapshot: state_snapshot.clone() }, + OnlineConfig { + transport, + state_snapshot: Some(state_snapshot), + ..Default::default() + }, + ) + } else { + Mode::Online(OnlineConfig { transport, ..Default::default() }) + }) + .build() + .await + .unwrap(); + ext.execute_with(|| Runtime::on_runtime_upgrade(UpgradeCheckSelect::PreAndPost)); + } + + #[tokio::test] + async fn delegate_stake_migration() { + // Intended to be run only manually. + if var("RUN_MIGRATION_TESTS").is_err() { + return; + } + use frame_support::assert_ok; + sp_tracing::try_init_simple(); + + let transport: Transport = var("WS").unwrap_or("ws://127.0.0.1:9900".to_string()).into(); + let maybe_state_snapshot: Option = var("SNAP").map(|s| s.into()).ok(); + let mut ext = Builder::::default() + .mode(if let Some(state_snapshot) = maybe_state_snapshot { + Mode::OfflineOrElseOnline( + OfflineConfig { state_snapshot: state_snapshot.clone() }, + OnlineConfig { + transport, + state_snapshot: Some(state_snapshot), + pallets: vec![ + "staking".into(), + "system".into(), + "balances".into(), + "nomination-pools".into(), + "delegated-staking".into(), + ], + ..Default::default() + }, + ) + } else { + Mode::Online(OnlineConfig { transport, ..Default::default() }) + }) + .build() + .await + .unwrap(); + ext.execute_with(|| { + // create an account with some balance + let alice = AccountId::from([1u8; 32]); + use frame_support::traits::Currency; + let _ = Balances::deposit_creating(&alice, 100_000 * UNITS); + + // iterate over all pools + pallet_nomination_pools::BondedPools::::iter_keys().for_each(|k| { + if pallet_nomination_pools::Pallet::::api_pool_needs_delegate_migration(k) + { + assert_ok!( + pallet_nomination_pools::Pallet::::migrate_pool_to_delegate_stake( + RuntimeOrigin::signed(alice.clone()).into(), + k, + ) + ); + } + }); + + // member migration stats + let mut success = 0; + let mut direct_stakers = 0; + let mut unexpected_errors = 0; + + // iterate over all pool members + pallet_nomination_pools::PoolMembers::::iter_keys().for_each(|k| { + if pallet_nomination_pools::Pallet::::api_member_needs_delegate_migration( + k.clone(), + ) { + // reasons migrations can fail: + let is_direct_staker = pallet_staking::Bonded::::contains_key(&k); + + let migration = pallet_nomination_pools::Pallet::::migrate_delegation( + RuntimeOrigin::signed(alice.clone()).into(), + sp_runtime::MultiAddress::Id(k.clone()), + ); + + if is_direct_staker { + // if the member is a direct staker, the migration should fail until pool + // member unstakes all funds from pallet-staking. + direct_stakers += 1; + assert_eq!( + migration.unwrap_err(), + pallet_delegated_staking::Error::::AlreadyStaking.into() + ); + } else if migration.is_err() { + unexpected_errors += 1; + log::error!(target: "remote_test", "Unexpected error {:?} while migrating {:?}", migration.unwrap_err(), k); + } else { + success += 1; + } + } + }); + + log::info!( + target: "remote_test", + "Migration stats: success: {}, direct_stakers: {}, unexpected_errors: {}", + success, + direct_stakers, + unexpected_errors + ); + }); + } +} diff --git a/polkadot/runtime/westend/src/weights/mod.rs b/polkadot/runtime/westend/src/weights/mod.rs index 2248e421e639..1e7b01bc472b 100644 --- a/polkadot/runtime/westend/src/weights/mod.rs +++ b/polkadot/runtime/westend/src/weights/mod.rs @@ -20,6 +20,7 @@ pub mod frame_system; pub mod pallet_asset_rate; pub mod pallet_bags_list; pub mod pallet_balances; +pub mod pallet_beefy_mmr; pub mod pallet_conviction_voting; pub mod pallet_election_provider_multi_phase; pub mod pallet_fast_unstake; @@ -44,20 +45,20 @@ pub mod pallet_utility; pub mod pallet_vesting; pub mod pallet_whitelist; 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_configuration; -pub mod runtime_parachains_coretime; -pub mod runtime_parachains_disputes; -pub mod runtime_parachains_disputes_slashing; -pub mod runtime_parachains_hrmp; -pub mod runtime_parachains_inclusion; -pub mod runtime_parachains_initializer; -pub mod runtime_parachains_on_demand; -pub mod runtime_parachains_paras; -pub mod runtime_parachains_paras_inherent; +pub mod polkadot_runtime_common_assigned_slots; +pub mod polkadot_runtime_common_auctions; +pub mod polkadot_runtime_common_crowdloan; +pub mod polkadot_runtime_common_identity_migrator; +pub mod polkadot_runtime_common_paras_registrar; +pub mod polkadot_runtime_common_slots; +pub mod polkadot_runtime_parachains_configuration; +pub mod polkadot_runtime_parachains_coretime; +pub mod polkadot_runtime_parachains_disputes; +pub mod polkadot_runtime_parachains_disputes_slashing; +pub mod polkadot_runtime_parachains_hrmp; +pub mod polkadot_runtime_parachains_inclusion; +pub mod polkadot_runtime_parachains_initializer; +pub mod polkadot_runtime_parachains_on_demand; +pub mod polkadot_runtime_parachains_paras; +pub mod polkadot_runtime_parachains_paras_inherent; pub mod xcm; diff --git a/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs b/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs new file mode 100644 index 000000000000..5be207e3fcff --- /dev/null +++ b/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs @@ -0,0 +1,89 @@ +// 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_beefy_mmr` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-696hpswk-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=pallet_beefy_mmr +// --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 `pallet_beefy_mmr`. +pub struct WeightInfo(PhantomData); +impl pallet_beefy_mmr::WeightInfo for WeightInfo { + /// Storage: `System::BlockHash` (r:1 w:0) + /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + fn extract_validation_context() -> Weight { + // Proof Size summary in bytes: + // Measured: `92` + // Estimated: `3509` + // Minimum execution time: 7_850_000 picoseconds. + Weight::from_parts(8_169_000, 0) + .saturating_add(Weight::from_parts(0, 3509)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `Mmr::Nodes` (r:1 w:0) + /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + fn read_peak() -> Weight { + // Proof Size summary in bytes: + // Measured: `201` + // Estimated: `3505` + // Minimum execution time: 6_852_000 picoseconds. + Weight::from_parts(7_448_000, 0) + .saturating_add(Weight::from_parts(0, 3505)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `Mmr::RootHash` (r:1 w:0) + /// Proof: `Mmr::RootHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `Mmr::NumberOfLeaves` (r:1 w:0) + /// Proof: `Mmr::NumberOfLeaves` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 512]`. + fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `193` + // Estimated: `1517` + // Minimum execution time: 12_860_000 picoseconds. + Weight::from_parts(17_158_162, 0) + .saturating_add(Weight::from_parts(0, 1517)) + // Standard Error: 1_732 + .saturating_add(Weight::from_parts(1_489_410, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2)) + } +} diff --git a/polkadot/runtime/westend/src/weights/runtime_common_assigned_slots.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_common_assigned_slots.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_common_assigned_slots.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_common_assigned_slots.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_common_auctions.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_common_auctions.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_common_auctions.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_common_auctions.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_common_crowdloan.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_common_crowdloan.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_common_crowdloan.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_common_crowdloan.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_common_identity_migrator.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_common_identity_migrator.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_common_identity_migrator.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_common_identity_migrator.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_common_paras_registrar.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_common_paras_registrar.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_common_paras_registrar.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_common_paras_registrar.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_common_slots.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_common_slots.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_common_slots.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_common_slots.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_configuration.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_configuration.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_parachains_configuration.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_configuration.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_coretime.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_coretime.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_disputes.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_parachains_disputes.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_disputes_slashing.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes_slashing.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_parachains_disputes_slashing.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes_slashing.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_hrmp.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_hrmp.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_parachains_hrmp.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_hrmp.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_inclusion.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_inclusion.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_parachains_inclusion.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_inclusion.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_initializer.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_initializer.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_parachains_initializer.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_initializer.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_on_demand.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_on_demand.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_parachains_on_demand.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_on_demand.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_paras.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras.rs similarity index 100% rename from polkadot/runtime/westend/src/weights/runtime_parachains_paras.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras.rs diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_paras_inherent.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras_inherent.rs similarity index 82% rename from polkadot/runtime/westend/src/weights/runtime_parachains_paras_inherent.rs rename to polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras_inherent.rs index 74dd55cc3f2c..32f6f28f2426 100644 --- a/polkadot/runtime/westend/src/weights/runtime_parachains_paras_inherent.rs +++ b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_paras_inherent.rs @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Autogenerated weights for `runtime_parachains::paras_inherent` +//! Autogenerated weights for `polkadot_runtime_parachains::paras_inherent` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-08-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-svzsllib-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -32,7 +32,7 @@ // --wasm-execution=compiled // --heap-pages=4096 // --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=runtime_parachains::paras_inherent +// --pallet=polkadot_runtime_parachains::paras_inherent // --chain=westend-dev // --header=./polkadot/file_header.txt // --output=./polkadot/runtime/westend/src/weights/ @@ -45,9 +45,49 @@ use frame_support::{traits::Get, weights::Weight}; use core::marker::PhantomData; -/// Weight functions for `runtime_parachains::paras_inherent`. +/// Weight functions for `polkadot_runtime_parachains::paras_inherent`. pub struct WeightInfo(PhantomData); impl polkadot_runtime_parachains::paras_inherent::WeightInfo for WeightInfo { + /// Storage: `ParaInherent::Included` (r:1 w:1) + /// Proof: `ParaInherent::Included` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::ParentHash` (r:1 w:0) + /// Proof: `System::ParentHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `ParasShared::AllowedRelayParents` (r:1 w:1) + /// Proof: `ParasShared::AllowedRelayParents` (`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`) + /// Storage: `ParaScheduler::AvailabilityCores` (r:1 w:1) + /// Proof: `ParaScheduler::AvailabilityCores` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::ActiveValidatorKeys` (r:1 w:0) + /// Proof: `ParasShared::ActiveValidatorKeys` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Babe::AuthorVrfRandomness` (r:1 w:0) + /// Proof: `Babe::AuthorVrfRandomness` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + /// Storage: `ParaInherent::OnChainVotes` (r:1 w:1) + /// Proof: `ParaInherent::OnChainVotes` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasDisputes::Frozen` (r:1 w:0) + /// Proof: `ParasDisputes::Frozen` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParaInclusion::V1` (r:1 w:0) + /// Proof: `ParaInclusion::V1` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ParaScheduler::SessionStartBlock` (r:1 w:0) + /// Proof: `ParaScheduler::SessionStartBlock` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParaScheduler::ValidatorGroups` (r:1 w:0) + /// Proof: `ParaScheduler::ValidatorGroups` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParaScheduler::ClaimQueue` (r:1 w:1) + /// Proof: `ParaScheduler::ClaimQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::ActiveValidatorIndices` (r:1 w:0) + /// Proof: `ParasShared::ActiveValidatorIndices` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Session::DisabledValidators` (r:1 w:0) + /// Proof: `Session::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn enter_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `37553` + // Estimated: `41018` + // Minimum execution time: 237_414_000 picoseconds. + Weight::from_parts(245_039_000, 0) + .saturating_add(Weight::from_parts(0, 41018)) + .saturating_add(T::DbWeight::get().reads(15)) + .saturating_add(T::DbWeight::get().writes(5)) + } /// Storage: `ParaInherent::Included` (r:1 w:1) /// Proof: `ParaInherent::Included` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::ParentHash` (r:1 w:0) @@ -112,19 +152,19 @@ impl polkadot_runtime_parachains::paras_inherent::Weigh /// Proof: `Paras::UpgradeGoAheadSignal` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Paras::MostRecentContext` (r:0 w:1) /// Proof: `Paras::MostRecentContext` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `v` is `[10, 200]`. + /// The range of component `v` is `[10, 1024]`. fn enter_variable_disputes(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `67518` - // Estimated: `73458 + v * (23 ±0)` - // Minimum execution time: 844_022_000 picoseconds. - Weight::from_parts(456_682_337, 0) - .saturating_add(Weight::from_parts(0, 73458)) - // Standard Error: 16_403 - .saturating_add(Weight::from_parts(41_871_245, 0).saturating_mul(v.into())) + // Measured: `199504` + // Estimated: `205444 + v * (5 ±0)` + // Minimum execution time: 1_157_489_000 picoseconds. + Weight::from_parts(629_243_559, 0) + .saturating_add(Weight::from_parts(0, 205444)) + // Standard Error: 10_997 + .saturating_add(Weight::from_parts(50_752_930, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(28)) .saturating_add(T::DbWeight::get().writes(16)) - .saturating_add(Weight::from_parts(0, 23).saturating_mul(v.into())) + .saturating_add(Weight::from_parts(0, 5).saturating_mul(v.into())) } /// Storage: `ParaInherent::Included` (r:1 w:1) /// Proof: `ParaInherent::Included` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -146,24 +186,6 @@ impl polkadot_runtime_parachains::paras_inherent::Weigh /// Proof: `ParasDisputes::Frozen` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParaInclusion::V1` (r:2 w:1) /// Proof: `ParaInclusion::V1` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ParaSessionInfo::AccountKeys` (r:1 w:0) - /// Proof: `ParaSessionInfo::AccountKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Session::Validators` (r:1 w:0) - /// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `Staking::ActiveEra` (r:1 w:0) - /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasRewardPoints` (r:1 w:1) - /// Proof: `Staking::ErasRewardPoints` (`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::DeliveryFeeFactor` (r:1 w:1) - /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Hrmp::HrmpChannelDigests` (r:1 w:1) - /// Proof: `Hrmp::HrmpChannelDigests` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Paras::FutureCodeUpgrades` (r:1 w:0) - /// Proof: `Paras::FutureCodeUpgrades` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `ParasDisputes::Disputes` (r:1 w:0) - /// Proof: `ParasDisputes::Disputes` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParaScheduler::SessionStartBlock` (r:1 w:0) /// Proof: `ParaScheduler::SessionStartBlock` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParaScheduler::ValidatorGroups` (r:1 w:0) @@ -176,25 +198,15 @@ impl polkadot_runtime_parachains::paras_inherent::Weigh /// Proof: `ParasShared::ActiveValidatorIndices` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Session::DisabledValidators` (r:1 w:0) /// Proof: `Session::DisabledValidators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `ParasDisputes::Included` (r:0 w:1) - /// Proof: `ParasDisputes::Included` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Hrmp::HrmpWatermarks` (r:0 w:1) - /// Proof: `Hrmp::HrmpWatermarks` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Paras::Heads` (r:0 w:1) - /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Paras::UpgradeGoAheadSignal` (r:0 w:1) - /// Proof: `Paras::UpgradeGoAheadSignal` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Paras::MostRecentContext` (r:0 w:1) - /// Proof: `Paras::MostRecentContext` (`max_values`: None, `max_size`: None, mode: `Measured`) fn enter_bitfields() -> Weight { // Proof Size summary in bytes: - // Measured: `43196` - // Estimated: `49136` - // Minimum execution time: 438_637_000 picoseconds. - Weight::from_parts(458_342_000, 0) - .saturating_add(Weight::from_parts(0, 49136)) - .saturating_add(T::DbWeight::get().reads(26)) - .saturating_add(T::DbWeight::get().writes(16)) + // Measured: `75131` + // Estimated: `81071` + // Minimum execution time: 466_928_000 picoseconds. + Weight::from_parts(494_342_000, 0) + .saturating_add(Weight::from_parts(0, 81071)) + .saturating_add(T::DbWeight::get().reads(17)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `ParaInherent::Included` (r:1 w:1) /// Proof: `ParaInherent::Included` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -262,16 +274,16 @@ impl polkadot_runtime_parachains::paras_inherent::Weigh /// Proof: `Paras::UpgradeGoAheadSignal` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Paras::MostRecentContext` (r:0 w:1) /// Proof: `Paras::MostRecentContext` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `v` is `[101, 200]`. + /// The range of component `v` is `[2, 5]`. fn enter_backed_candidates_variable(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `43269` - // Estimated: `49209` - // Minimum execution time: 5_955_361_000 picoseconds. - Weight::from_parts(1_285_398_956, 0) - .saturating_add(Weight::from_parts(0, 49209)) - // Standard Error: 57_369 - .saturating_add(Weight::from_parts(47_073_853, 0).saturating_mul(v.into())) + // Measured: `76369` + // Estimated: `82309` + // Minimum execution time: 1_468_919_000 picoseconds. + Weight::from_parts(1_433_315_477, 0) + .saturating_add(Weight::from_parts(0, 82309)) + // Standard Error: 419_886 + .saturating_add(Weight::from_parts(42_880_485, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(29)) .saturating_add(T::DbWeight::get().writes(16)) } @@ -347,11 +359,11 @@ impl polkadot_runtime_parachains::paras_inherent::Weigh /// Proof: `Paras::MostRecentContext` (`max_values`: None, `max_size`: None, mode: `Measured`) fn enter_backed_candidate_code_upgrade() -> Weight { // Proof Size summary in bytes: - // Measured: `43282` - // Estimated: `49222` - // Minimum execution time: 42_128_606_000 picoseconds. - Weight::from_parts(42_822_806_000, 0) - .saturating_add(Weight::from_parts(0, 49222)) + // Measured: `76382` + // Estimated: `82322` + // Minimum execution time: 34_577_233_000 picoseconds. + Weight::from_parts(39_530_352_000, 0) + .saturating_add(Weight::from_parts(0, 82322)) .saturating_add(T::DbWeight::get().reads(31)) .saturating_add(T::DbWeight::get().writes(16)) } diff --git a/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 9939f16aa29f..e0c61c8e2bf2 100644 --- a/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -16,10 +16,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-10-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-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` +//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 // Executed Command: @@ -55,8 +55,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 24_815_000 picoseconds. - Weight::from_parts(25_098_000, 3593) + // Minimum execution time: 31_780_000 picoseconds. + Weight::from_parts(32_602_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -66,12 +66,12 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 51_268_000 picoseconds. - Weight::from_parts(51_857_000, 6196) + // Minimum execution time: 41_818_000 picoseconds. + Weight::from_parts(42_902_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: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -83,12 +83,12 @@ impl WeightInfo { /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) pub(crate) fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `210` - // Estimated: `6196` - // Minimum execution time: 74_113_000 picoseconds. - Weight::from_parts(74_721_000, 6196) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `351` + // Estimated: `8799` + // Minimum execution time: 101_949_000 picoseconds. + Weight::from_parts(104_190_000, 8799) + .saturating_add(T::DbWeight::get().reads(7)) + .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`) @@ -105,16 +105,18 @@ impl WeightInfo { /// 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: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) pub(crate) fn initiate_reserve_withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3574` - // Minimum execution time: 28_919_000 picoseconds. - Weight::from_parts(29_703_000, 3574) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `351` + // Estimated: `6196` + // Minimum execution time: 70_123_000 picoseconds. + Weight::from_parts(72_564_000, 6196) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -122,8 +124,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 21_685_000 picoseconds. - Weight::from_parts(22_528_000, 3593) + // Minimum execution time: 31_868_000 picoseconds. + Weight::from_parts(32_388_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -133,27 +135,27 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 25_192_000 picoseconds. - Weight::from_parts(25_445_000, 3593) + // Minimum execution time: 24_532_000 picoseconds. + Weight::from_parts(25_166_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: `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: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) pub(crate) fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3593` - // Minimum execution time: 49_349_000 picoseconds. - Weight::from_parts(50_476_000, 3593) + // Measured: `147` + // Estimated: `3612` + // Minimum execution time: 63_378_000 picoseconds. + Weight::from_parts(65_002_000, 3612) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -169,10 +171,10 @@ impl WeightInfo { /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) pub(crate) fn initiate_teleport() -> Weight { // Proof Size summary in bytes: - // Measured: `109` - // Estimated: `3593` - // Minimum execution time: 51_386_000 picoseconds. - Weight::from_parts(52_141_000, 3593) + // Measured: `147` + // Estimated: `3612` + // Minimum execution time: 49_174_000 picoseconds. + Weight::from_parts(50_356_000, 3612) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index e96ed6af73d9..1e90338a0f18 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -477,10 +477,7 @@ impl Table { if !context.is_member_of(&from, &votes.group_id) { let sig = match vote { ValidityVote::Valid(s) => s, - ValidityVote::Issued(_) => panic!( - "implicit issuance vote only cast from `import_candidate` after \ - checking group membership of issuer; qed" - ), + ValidityVote::Issued(s) => s, }; return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { diff --git a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs index 279d7118f8cf..7683c6025392 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs @@ -34,7 +34,7 @@ use xcm_executor::traits::WeightBounds; #[test] fn basic_buy_fees_message_executes() { sp_tracing::try_init_simple(); - let mut client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().build(); let msg = Xcm(vec![ WithdrawAsset((Parent, 100).into()), @@ -75,7 +75,7 @@ fn basic_buy_fees_message_executes() { #[test] fn transact_recursion_limit_works() { sp_tracing::try_init_simple(); - let mut client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().build(); let base_xcm = |call: polkadot_test_runtime::RuntimeCall| { Xcm(vec![ @@ -174,7 +174,7 @@ fn query_response_fires() { use polkadot_test_runtime::RuntimeEvent::TestNotifier; sp_tracing::try_init_simple(); - let mut client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().build(); let mut block_builder = client.init_polkadot_block_builder(); @@ -256,7 +256,7 @@ fn query_response_elicits_handler() { use polkadot_test_runtime::RuntimeEvent::TestNotifier; sp_tracing::try_init_simple(); - let mut client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().build(); let mut block_builder = client.init_polkadot_block_builder(); @@ -332,7 +332,7 @@ fn query_response_elicits_handler() { #[test] fn deposit_reserve_asset_works_for_any_xcm_sender() { sp_tracing::try_init_simple(); - let mut client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().build(); // Init values for the simulated origin Parachain let amount_to_send: u128 = 1_000_000_000_000; diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 1daf5ae750cf..74561e931e7e 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -809,7 +809,7 @@ impl XcmExecutor { }; let actual_weight = maybe_actual_weight.unwrap_or(weight); let surplus = weight.saturating_sub(actual_weight); - // We assume that the `Config::Weigher` will counts the `require_weight_at_most` + // We assume that the `Config::Weigher` will count the `require_weight_at_most` // for the estimate of how much weight this instruction will take. Now that we know // that it's less, we credit it. // @@ -858,14 +858,7 @@ impl XcmExecutor { 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(()) + self.deposit_assets_with_retry(&deposited, &beneficiary) }); if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() { self.holding = old_holding; @@ -890,9 +883,7 @@ impl XcmExecutor { // 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))?; - } + self.deposit_assets_with_retry(&deposited, &dest)?; // 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. @@ -1282,4 +1273,46 @@ impl XcmExecutor { }), } } + + /// Deposit `to_deposit` assets to `beneficiary`, without giving up on the first (transient) + /// error, and retrying once just in case one of the subsequently deposited assets satisfy some + /// requirement. + /// + /// Most common transient error is: `beneficiary` account does not yet exist and the first + /// asset(s) in the (sorted) list does not satisfy ED, but a subsequent one in the list does. + /// + /// This function can write into storage and also return an error at the same time, it should + /// always be called within a transactional context. + fn deposit_assets_with_retry( + &mut self, + to_deposit: &AssetsInHolding, + beneficiary: &Location, + ) -> Result<(), XcmError> { + let mut failed_deposits = Vec::with_capacity(to_deposit.len()); + + let mut deposit_result = Ok(()); + for asset in to_deposit.assets_iter() { + deposit_result = + Config::AssetTransactor::deposit_asset(&asset, &beneficiary, Some(&self.context)); + // if deposit failed for asset, mark it for retry after depositing the others. + if deposit_result.is_err() { + failed_deposits.push(asset); + } + } + if failed_deposits.len() == to_deposit.len() { + tracing::debug!( + target: "xcm::execute", + ?deposit_result, + "Deposit for each asset failed, returning the last error as there is no point in retrying any of them", + ); + return deposit_result; + } + tracing::trace!(target: "xcm::execute", ?failed_deposits, "Deposits to retry"); + + // retry previously failed deposits, this time short-circuiting on any error. + for asset in failed_deposits { + Config::AssetTransactor::deposit_asset(&asset, &beneficiary, Some(&self.context))?; + } + Ok(()) + } } diff --git a/polkadot/zombienet_tests/functional/0003-mmr-generate-and-verify-proof.js b/polkadot/zombienet_tests/functional/0003-mmr-generate-and-verify-proof.js index 6583173e40c3..20d0c2a988b1 100644 --- a/polkadot/zombienet_tests/functional/0003-mmr-generate-and-verify-proof.js +++ b/polkadot/zombienet_tests/functional/0003-mmr-generate-and-verify-proof.js @@ -3,9 +3,9 @@ const common = require('./0003-common.js'); async function run(nodeName, networkInfo, nodeNames) { const apis = await common.getApis(networkInfo, nodeNames); - const proof = await apis[nodeName].rpc.mmr.generateProof([1, 9, 20]); - - const root = await apis[nodeName].rpc.mmr.root() + let at = await apis[nodeName].rpc.chain.getBlockHash(21); + const root = await apis[nodeName].rpc.mmr.root(at); + const proof = await apis[nodeName].rpc.mmr.generateProof([1, 9, 20], 21, at); const proofVerifications = await Promise.all( Object.values(apis).map(async (api) => { diff --git a/prdoc/pr_4791.prdoc b/prdoc/1.15.1/pr_4791.prdoc similarity index 100% rename from prdoc/pr_4791.prdoc rename to prdoc/1.15.1/pr_4791.prdoc diff --git a/prdoc/pr_4937.prdoc b/prdoc/1.15.1/pr_4937.prdoc similarity index 100% rename from prdoc/pr_4937.prdoc rename to prdoc/1.15.1/pr_4937.prdoc diff --git a/prdoc/pr_5273.prdoc b/prdoc/1.15.1/pr_5273.prdoc similarity index 100% rename from prdoc/pr_5273.prdoc rename to prdoc/1.15.1/pr_5273.prdoc diff --git a/prdoc/1.15.1/pr_5281.prdoc b/prdoc/1.15.1/pr_5281.prdoc new file mode 100644 index 000000000000..60feab412aff --- /dev/null +++ b/prdoc/1.15.1/pr_5281.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: PoV-Reclaim - Set `BlockWeight` to node-side PoV size if mismatch is detected + +doc: + - audience: Runtime Dev + description: | + After this change, the `StorageWeightReclaim` `SignedExtension` will check the node-side PoV size after every + extrinsic. If we detect a case where the returned proof size is higher than the `BlockWeight` value of the + runtime, we set `BlockWeight` to the size returned from the node. + +crates: + - name: cumulus-primitives-storage-weight-reclaim + bump: patch + - name: frame-system + bump: minor diff --git a/prdoc/1.15.1/pr_5321.prdoc b/prdoc/1.15.1/pr_5321.prdoc new file mode 100644 index 000000000000..97f75d28dd52 --- /dev/null +++ b/prdoc/1.15.1/pr_5321.prdoc @@ -0,0 +1,11 @@ +title: fix availability-distribution Jaeger spans memory leak + +doc: + - audience: Node Dev + description: | + Fixes a memory leak which caused the Jaeger span storage in availability-distribution to never be pruned and therefore increasing indefinitely. + This was caused by improper handling of finalized heads. More info in https://github.com/paritytech/polkadot-sdk/issues/5258 + +crates: + - name: polkadot-availability-distribution + bump: patch diff --git a/prdoc/pr_4129.prdoc b/prdoc/pr_4129.prdoc new file mode 100644 index 000000000000..dfcc9b9ef030 --- /dev/null +++ b/prdoc/pr_4129.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: Update ForeignAssets from xcm::v3::Location to xcm::v4::Location + +doc: + - audience: + - Runtime Dev + - Runtime User + description: | + As a stepping stone for XCMv5, the foreign asset ids have been updated from v3::Location to v4::Location. + +crates: + - name: asset-hub-rococo-runtime + bump: minor + - name: asset-hub-westend-runtime + bump: minor diff --git a/prdoc/pr_4460.prdoc b/prdoc/pr_4460.prdoc new file mode 100644 index 000000000000..81636c3313fc --- /dev/null +++ b/prdoc/pr_4460.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: "xcm-executor: allow deposit of multiple assets if at least one of them satisfies ED" + +doc: + - audience: Runtime Dev + description: | + XCM programs that deposit assets to some new (empty) account will now succeed if at least + one of the deposited assets satisfies ED. Before this change, the requirement was that the + _first_ asset had to satisfy ED, but assets order can be changed during reanchoring so it + is not reliable. Now, ordering doesn't matter, any one(s) of them can satisfy ED for the + whole deposit to work. + - audience: Runtime User + description: | + XCM programs that deposit assets to some new (empty) account will now succeed if at least + one of the deposited assets satisfies ED. Before this change, the requirement was that the + _first_ asset had to satisfy ED, but assets order can be changed during reanchoring so it + is not reliable. Now, ordering doesn't matter, any one(s) of them can satisfy ED for the + whole deposit to work. + +crates: + - name: staging-xcm-executor + bump: patch diff --git a/prdoc/pr_4792.prdoc b/prdoc/pr_4792.prdoc new file mode 100644 index 000000000000..5ce4303bcf75 --- /dev/null +++ b/prdoc/pr_4792.prdoc @@ -0,0 +1,62 @@ +# 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: bind to `ipv6` if available and add `CLI --experimental-rpc-endpoint` to specify listen addr" + +doc: + - audience: Node Operator + description: | + This PR changes/adds the following: + + 1. The default setting is that substrate starts a rpc server that listens to localhost both ipv4 and ipv6 on the same port. + ipv6 is allowed to fail because some platforms may not support it + 2. A new RPC CLI option `--experimental-rpc-endpoint` is introduced which allows to configure arbitrary listen addresses including the port, + if this is enabled no other interfaces are enabled. + 3. If the local addr is not found for any of the sockets the server is not started and throws an error. + 4. Remove the deny_unsafe from the RPC implementations instead this is an extension to allow different polices for different interfaces/sockets + such one may enable unsafe on local interface and safe on only the external interface. + 5. This new `--experimental-rpc-endpoint` has several options and in the help menu all possible parameters are documented. + 6. The log emitted by jsonrpc server when it has been started has been modified to indicate all started rpc endpoints. + + So for instance it's now possible to start up three RPC endpoints as follows: + ``` + $ polkadot --experimental-rpc-endpoint "listen-addr=127.0.0.1:9944,methods=unsafe" --experimental-rpc-endpoint "listen-addr=0.0.0.0:9945,methods=safe,rate-limit=100" --experimental-rpc-endpoint "listen-addr=[::1]:9944,optional=true" + ``` + +crates: + - name: sc-rpc-server + bump: major + - name: sc-rpc + bump: major + - name: sc-cli + bump: major + - name: sc-service + bump: major + - name: sc-rpc-api + bump: patch + - name: polkadot-dispute-distribution + bump: patch + - name: polkadot-parachain-lib + bump: patch + - name: substrate-frame-rpc-system + bump: major + - name: substrate-state-trie-migration-rpc + bump: major + - name: cumulus-client-cli + bump: major + validate: false + - name: sc-consensus-beefy-rpc + bump: major + validate: false + - name: sc-consensus-grandpa-rpc + bump: major + validate: false + - name: sc-consensus-babe-rpc + bump: major + validate: false + - name: polkadot-rpc + bump: major + validate: false + - name: polkadot-service + bump: major + validate: false diff --git a/prdoc/pr_4822.prdoc b/prdoc/pr_4822.prdoc new file mode 100644 index 000000000000..44f3e41d8d5a --- /dev/null +++ b/prdoc/pr_4822.prdoc @@ -0,0 +1,25 @@ +# 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: Ensure as many as possible pool members can migrate to `DelegateStake` + +doc: + - audience: Runtime Dev + description: | + 1. Allows pool members to use their total balance while joining pool with `DelegateStake`. + 2. Gates call mutating pool or member in unmigrated state. + 3. Runtime apis for reading pool and member balance. + +crates: + - name: westend-runtime + bump: minor + - name: kitchensink-runtime + bump: patch + - name: pallet-delegated-staking + bump: patch + - name: pallet-nomination-pools + bump: minor + - name: sp-staking + bump: patch + - name: pallet-nomination-pools-runtime-api + bump: minor diff --git a/prdoc/pr_4956.prdoc b/prdoc/pr_4956.prdoc new file mode 100644 index 000000000000..a72ca303aaca --- /dev/null +++ b/prdoc/pr_4956.prdoc @@ -0,0 +1,39 @@ +title: Add build options to the srtool build step and delete `disanle-logging` feature + +doc: +- audience: Runtime Dev + description: | + This PR adds possibility to set BUILD_OPTIONS to the "Srtool Build\" step in the release pipeline while building runtimes. + And deletes the `disable-logging` feature from test runtimes to be able to build those with an activated logging. + + + +crates: +- name: people-rococo-runtime + bump: patch +- name: people-westend-runtime + bump: patch +- name: rococo-parachain-runtime + bump: patch +- name: asset-hub-rococo-runtime + bump: patch +- name: asset-hub-westend-runtime + bump: patch +- name: bridge-hub-rococo-runtime + bump: patch +- name: bridge-hub-westend-runtime + bump: patch +- name: collectives-westend-runtime + bump: patch +- name: contracts-rococo-runtime + bump: patch +- name: coretime-rococo-runtime + bump: patch +- name: coretime-westend-runtime + bump: patch +- name: glutton-westend-runtime + bump: patch +- name: rococo-runtime + bump: patch +- name: westend-runtime + bump: patch diff --git a/prdoc/pr_4962.prdoc b/prdoc/pr_4962.prdoc new file mode 100644 index 000000000000..0957f85b6628 --- /dev/null +++ b/prdoc/pr_4962.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: Removed `pallet::getter` usage from the pallet-treasury + +doc: + - audience: Runtime Dev + description: | + This PR removed `pallet::getter`s from `pallet-treasury`s storage items. + Instead use the syntax `StorageItem::::get()`. + +crates: + - name: pallet-treasury + bump: minor \ No newline at end of file diff --git a/prdoc/pr_4998.prdoc b/prdoc/pr_4998.prdoc new file mode 100644 index 000000000000..41e3886405cc --- /dev/null +++ b/prdoc/pr_4998.prdoc @@ -0,0 +1,20 @@ +# 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: Ensure members can always exit the pool gracefully + +doc: + - audience: Runtime Dev + description: | + Ensures when a member wants to withdraw all funds but the pool is not able to provide all their funds, the member + can receive as much as possible and exit pool. Also handles cases where some extra funds held in member's account + is released when they are removed. + +crates: + - name: pallet-delegated-staking + bump: patch + - name: pallet-nomination-pools + bump: major + - name: sp-staking + bump: major + diff --git a/prdoc/pr_4999.prdoc b/prdoc/pr_4999.prdoc new file mode 100644 index 000000000000..d396fcdbe8b3 --- /dev/null +++ b/prdoc/pr_4999.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: Fixes entropy for derivation of proxy delegator account. + +doc: + - audience: Runtime Dev + description: | + This fixes how ProxyDelegator accounts are derived but may cause issues in Westend since it would use the old + derivative accounts. Does not affect Polkadot/Kusama as this pallet is not deployed to them yet. + +crates: + - name: westend-runtime + bump: patch + - name: pallet-delegated-staking + bump: patch + - name: pallet-nomination-pools + bump: patch \ No newline at end of file diff --git a/prdoc/pr_5082.prdoc b/prdoc/pr_5082.prdoc new file mode 100644 index 000000000000..d309f4e7266e --- /dev/null +++ b/prdoc/pr_5082.prdoc @@ -0,0 +1,15 @@ +title: "Fix ParaInherent weight overestimation" + +doc: + - audience: Runtime Dev + description: | + This PR fixes the relay chain inherent weight overestimation allowing it + to support more cores and validators. + +crates: +- name: polkadot-runtime-parachains + bump: major +- name: westend-runtime + bump: minor +- name: rococo-runtime + bump: minor diff --git a/prdoc/pr_5155.prdoc b/prdoc/pr_5155.prdoc new file mode 100644 index 000000000000..373522eea1c3 --- /dev/null +++ b/prdoc/pr_5155.prdoc @@ -0,0 +1,27 @@ +# 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 umbrella crate for minimal template + +doc: + - audience: Runtime Dev + description: | + Minor additions to the `polkadot-sdk-frame` crate and making it ready for usage in more templates. This PR already integrates it in the minimal template. + + +crates: + - name: polkadot-sdk + bump: major + - name: polkadot-sdk-frame + bump: patch + - name: sp-wasm-interface + bump: patch + - name: pallet-revive + bump: patch + - name: pallet-revive-fixtures + bump: patch + - name: frame-support + bump: patch + - name: pallet-balances + bump: patch + diff --git a/prdoc/pr_5188.prdoc b/prdoc/pr_5188.prdoc new file mode 100644 index 000000000000..b2ab9ff6653b --- /dev/null +++ b/prdoc/pr_5188.prdoc @@ -0,0 +1,32 @@ +# 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: Added benchmarks for BEEFY fork voting + +doc: + - audience: + - Runtime Dev + - Runtime User + description: | + This PR adds benchmarks for `report_fork_voting` and `report_future_voting` extrinsics to `pallet-beefy`. + `report_future_voting` can be called now. `report_fork_voting` can't be called yet. Even though we have added + the formula for computing its weight, we still use `Weight::MAX`. We will set the proper weight in a future PR. + In order to do this we need to also check that the ancestry proof is optimal. + The PR adds a `WeightInfo` associated trait to the `pallet_beefy_mmr::Config` and defines benchmarks for + `pallet_beefy_mmr`. + +crates: + - name: pallet-mmr + bump: minor + - name: sp-mmr-primitives + bump: minor + - name: sp-consensus-beefy + bump: minor + - name: rococo-runtime + bump: minor + - name: pallet-beefy + bump: major + - name: pallet-beefy-mmr + bump: major + - name: westend-runtime + bump: minor diff --git a/prdoc/pr_5195.prdoc b/prdoc/pr_5195.prdoc new file mode 100644 index 000000000000..cfd435fa289d --- /dev/null +++ b/prdoc/pr_5195.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: Bump Aura authoring duration to 2s. + +doc: + - audience: Node Dev + description: | + This PR bumps the Aura authoring duration in the asynchronous backing + guide and the polkadot-parachain service file to 2s in order to make + better use of the provided coretime. + +crates: + - name: polkadot-parachain-bin + bump: patch diff --git a/prdoc/pr_5204.prdoc b/prdoc/pr_5204.prdoc new file mode 100644 index 000000000000..38a73b6b00ef --- /dev/null +++ b/prdoc/pr_5204.prdoc @@ -0,0 +1,13 @@ +title: "Pallet assets: fix doc: start_destroy never required asset to be frozen" + +doc: + - audience: Runtime Dev + description: | + In pallet assets calling `start_destroy` doesn't require the asset to be frozen. Doc is fixed. + + +crates: + - name: pallet-assets + bump: patch + - name: frame-support + bump: patch diff --git a/prdoc/pr_5252.prdoc b/prdoc/pr_5252.prdoc new file mode 100644 index 000000000000..fd4454ac3b9d --- /dev/null +++ b/prdoc/pr_5252.prdoc @@ -0,0 +1,11 @@ +title: Additional logging in `dispute-coordinator` subsystem + +doc: + - audience: Node Dev + description: | + Additional logging in `dispute-coordinator` subsystem tracing the list of offchain disabled + validators and the reason why an import statement is considered spam. + +crates: + - name: polkadot-node-core-dispute-coordinator + bump: patch \ No newline at end of file diff --git a/prdoc/pr_5262.prdoc b/prdoc/pr_5262.prdoc new file mode 100644 index 000000000000..828f0ffeb1bc --- /dev/null +++ b/prdoc/pr_5262.prdoc @@ -0,0 +1,25 @@ +# 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: Correct some typos in crates' descriptions + +doc: + - audience: Runtime Dev + description: | + Corrected typos and copy-paste errors in crates' descriptions. + +crates: + - name: cumulus-client-pov-recovery + bump: patch + - name: cumulus-pallet-aura-ext + bump: patch + - name: bridge-hub-rococo-runtime + bump: patch + - name: frame-try-runtime + bump: patch + - name: pallet-whitelist + bump: patch + - name: polkadot-sdk + bump: patch + - name: polkadot-runtime-parachains + bump: none diff --git a/prdoc/pr_5269.prdoc b/prdoc/pr_5269.prdoc new file mode 100644 index 000000000000..e4401f2406ce --- /dev/null +++ b/prdoc/pr_5269.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: Added the possibility to build a parachain node with block number u64 + +doc: + - audience: Node Dev + description: | + Added the possibility to build a parachain node with block number u64. + +crates: + - name: polkadot-parachain-lib + bump: minor + - name: polkadot-parachain-bin + bump: patch diff --git a/prdoc/pr_5284.prdoc b/prdoc/pr_5284.prdoc new file mode 100644 index 000000000000..a3244a82c860 --- /dev/null +++ b/prdoc/pr_5284.prdoc @@ -0,0 +1,7 @@ +title: Minor clean up +author: conr2d +topic: runtime + +crates: + - name: sp-runtime + bump: none diff --git a/prdoc/pr_5288.prdoc b/prdoc/pr_5288.prdoc new file mode 100644 index 000000000000..8241e75876f1 --- /dev/null +++ b/prdoc/pr_5288.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: Added `polkadot-parachain-lib` helper library that can be used to build a parachain node + +doc: + - audience: Node Dev + description: | + This PR adds the `polkadot-parachain-lib` helper library that can be used to build a parachain node. + +crates: + - name: polkadot-parachain-bin + bump: patch + - name: polkadot-parachain-lib + bump: patch + - name: polkadot-sdk + bump: patch diff --git a/prdoc/pr_5293.prdoc b/prdoc/pr_5293.prdoc new file mode 100644 index 000000000000..90528a224e8d --- /dev/null +++ b/prdoc/pr_5293.prdoc @@ -0,0 +1,22 @@ +title: Add initial version of pallet_revive + +doc: + - audience: Runtime Dev + description: | + Adds initial **experimental** version of the new pallet_revive. It will run PolkaVM + contracts which were recompiled from YUL using the revive compiler. Do not use the + pallet in production, yet. It is work in progress. + +crates: + - name: polkadot-sdk + bump: minor + - name: pallet-revive + bump: minor + - name: pallet-revive-fixtures + bump: minor + - name: pallet-revive-proc-macro + bump: minor + - name: pallet-revive-uapi + bump: minor + - name: pallet-revive-mock-network + bump: minor diff --git a/prdoc/pr_5326.prdoc b/prdoc/pr_5326.prdoc new file mode 100644 index 000000000000..0301b8c17a30 --- /dev/null +++ b/prdoc/pr_5326.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: Update Readme of the `polkadot` crate + +doc: + - audience: Node Operator + description: | + Updated Readme of the `polkadot` crate. + +crates: + - name: polkadot + bump: patch diff --git a/prdoc/pr_5339.prdoc b/prdoc/pr_5339.prdoc new file mode 100644 index 000000000000..850ba903e126 --- /dev/null +++ b/prdoc/pr_5339.prdoc @@ -0,0 +1,45 @@ +title: Replace unnecessary `&mut self` with `&self` in `BlockImport::import_block()` + +doc: + - audience: Node Dev + description: | + Simplifies block import API to match intended design where independent blocks can technically be imported + concurrently and in practice was called through `Arc` anyway + +crates: + - name: cumulus-client-consensus-common + bump: major + - name: cumulus-client-network + bump: none + - name: cumulus-relay-chain-inprocess-interface + bump: none + - name: cumulus-pallet-parachain-system + bump: none + - name: sc-basic-authorship + bump: patch + - name: sc-consensus-babe + bump: major + - name: sc-consensus-beefy + bump: major + - name: sc-consensus + bump: major + - name: sc-consensus-grandpa + bump: major + - name: sc-consensus-pow + bump: major + - name: mmr-gadget + bump: none + - name: sc-network + bump: none + - name: sc-network-sync + bump: none + - name: sc-offchain + bump: none + - name: sc-rpc-spec-v2 + bump: none + - name: sc-rpc + bump: none + - name: sc-service + bump: major + - name: sc-transaction-pool + bump: none diff --git a/prdoc/pr_5344.prdoc b/prdoc/pr_5344.prdoc new file mode 100644 index 000000000000..9f83c113686d --- /dev/null +++ b/prdoc/pr_5344.prdoc @@ -0,0 +1,10 @@ +title: Fix storage weight reclaim bug. + +doc: + - audience: Node Dev + description: | + Improvement in slot worker loop that will not call create inherent data providers if the major sync is in progress. Before it was called every slot and the results were discarded during major sync. + +crates: + - name: sc-consensus-slots + bump: minor diff --git a/prdoc/pr_5348.prdoc b/prdoc/pr_5348.prdoc new file mode 100644 index 000000000000..c2282c4c74c4 --- /dev/null +++ b/prdoc/pr_5348.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: allow for u8 to be used as hold/freeze reason + +doc: + - audience: Runtime Dev + description: | + Allows for `u8` type to be configured as `HoldReason` and `FreezeReason` + +crates: + - name: frame-support + bump: patch diff --git a/prdoc/pr_5352.prdoc b/prdoc/pr_5352.prdoc new file mode 100644 index 000000000000..4b055d81cb51 --- /dev/null +++ b/prdoc/pr_5352.prdoc @@ -0,0 +1,10 @@ +title: "Aura: Ensure parachains are building on all relay chain forks" + +doc: + - audience: Node Dev + description: | + Ensure that parachains using the `basic` collator are building on all relay chain forks. + +crates: + - name: cumulus-client-consensus-aura + bump: patch diff --git a/prdoc/pr_5354.prdoc b/prdoc/pr_5354.prdoc new file mode 100644 index 000000000000..e3037b66fbca --- /dev/null +++ b/prdoc/pr_5354.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: Fix benchmark failures when using insecure_zero_ed flag + +doc: + - audience: Runtime Dev + description: | + Currently, when the pallet is compiled with the insecure_zero_ed flag, benchmarks fail because the minimum balance is set to zero. + + The PR aims to resolve this issue by implementing a placeholder value for the minimum balance when the insecure_zero_ed flag is active. it ensures that benchmarks run successfully regardless of whether this flag is used or not + +crates: +- name: pallet-balances + bump: minor diff --git a/prdoc/pr_5356.prdoc b/prdoc/pr_5356.prdoc new file mode 100644 index 000000000000..a306be335440 --- /dev/null +++ b/prdoc/pr_5356.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 OurViewChange small race + +doc: + - audience: Node Dev + description: | + Always queue OurViewChange event before we send view changes to our peers, because otherwise we risk + the peers sending us a message that can be processed by our subsystems before OurViewChange. + Normally, this is not really a problem because the latency of the ViewChange we send to our peers + is way higher than that of our subsystem processing OurViewChange, however on testnets like versi + where CPUs are sometimes overcommitted this race gets triggered occasionally, so let's fix it by + sending the messages in the right order. + +crates: + - name: polkadot-network-bridge + bump: minor diff --git a/prdoc/pr_5359.prdoc b/prdoc/pr_5359.prdoc new file mode 100644 index 000000000000..bf059129a436 --- /dev/null +++ b/prdoc/pr_5359.prdoc @@ -0,0 +1,21 @@ +title: Make ticket non-optional and add ensure_successful method to Consideration trait + +doc: + - audience: Runtime Dev + description: | + Make ticket non-optional and add ensure_successful method to Consideration trait. + + Reverts the optional return ticket type for the new function introduced in + [polkadot-sdk/4596](https://github.com/paritytech/polkadot-sdk/pull/4596) and adds a helper + `ensure_successful` function for the runtime benchmarks. + Since the existing FRAME pallet represents zero cost with a zero balance type rather than + `None` in an option, maintaining the ticket type as a non-optional balance is beneficial + for backward compatibility and helps avoid unnecessary migrations. + +crates: + - name: frame-support + bump: major + - name: pallet-preimage + bump: major + - name: pallet-balances + bump: patch diff --git a/prdoc/pr_5360.prdoc b/prdoc/pr_5360.prdoc new file mode 100644 index 000000000000..4b07f30bfd09 --- /dev/null +++ b/prdoc/pr_5360.prdoc @@ -0,0 +1,3 @@ +crates: + - name: sc-service + bump: none diff --git a/prdoc/pr_5369.prdoc b/prdoc/pr_5369.prdoc new file mode 100644 index 000000000000..1baa5e1cbe7d --- /dev/null +++ b/prdoc/pr_5369.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: Fix failing XCM from relay to Coretime Chain when revenue is zero + +doc: + - audience: Runtime Dev + description: | + The coretime assigner now always includes UnpaidExecution when calling `notify_revenue` via a + `Transact`, not just when revenue is nonzero. This fixes an issue where the XCM would fail to + process on the receiving side. + +crates: + - name: polkadot-runtime-parachains + bump: patch diff --git a/prdoc/pr_5376.prdoc b/prdoc/pr_5376.prdoc new file mode 100644 index 000000000000..c9874ef7d865 --- /dev/null +++ b/prdoc/pr_5376.prdoc @@ -0,0 +1,3 @@ +crates: + - name: binary-merkle-tree + bump: none diff --git a/prdoc/pr_5380.prdoc b/prdoc/pr_5380.prdoc new file mode 100644 index 000000000000..75063e335343 --- /dev/null +++ b/prdoc/pr_5380.prdoc @@ -0,0 +1,15 @@ +title: Fix leases with gaps and time slice calculation in MigrateToCoretime + +doc: + - audience: Runtime Dev + description: | + Agile Coretime storage migration wasn't transferring correctly leases with gaps and was + miscalculating time lease period. This patch provides fixes for both issues. + +crates: + - name: rococo-runtime + bump: patch + - name: westend-runtime + bump: major + - name: polkadot-runtime-parachains + bump: patch \ No newline at end of file diff --git a/prdoc/pr_5384.prdoc b/prdoc/pr_5384.prdoc new file mode 100644 index 000000000000..74d477f8e153 --- /dev/null +++ b/prdoc/pr_5384.prdoc @@ -0,0 +1,25 @@ +# 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: "`MaybeConsideration` extension trait for `Consideration`" + +doc: + - audience: Runtime Dev + description: | + The trait allows for the management of tickets that may represent no cost. While + the `MaybeConsideration` still requires proper handling, it introduces the ability + to determine if a ticket represents no cost and can be safely forgotten without any + side effects. + + The new trait is particularly useful when a consumer expects the cost to be zero under + certain conditions (e.g., when the proposal count is below a threshold N) and does not want + to store such consideration tickets in storage. The extension approach allows us to avoid + breaking changes to the existing trait and to continue using it as a non-optional version + for migrating pallets that utilize the `Currency` and `fungible` traits for `holds` and + `freezes`, without requiring any storage migration. + +crates: + - name: frame-support + bump: minor + - name: pallet-balances + bump: patch diff --git a/prdoc/pr_5392.prdoc b/prdoc/pr_5392.prdoc new file mode 100644 index 000000000000..aeeb05de0bc3 --- /dev/null +++ b/prdoc/pr_5392.prdoc @@ -0,0 +1,11 @@ +title: "Don't disconnect disabled nodes sending us dispute messages" + +doc: + - audience: Node Operator + description: | + No longer disconnect peers which we consider disabled when raising + disputes as this will affect the approval process and thus finality. + +crates: + - name: polkadot-dispute-distribution + bump: patch diff --git a/prdoc/pr_5393.prdoc b/prdoc/pr_5393.prdoc new file mode 100644 index 000000000000..7fcf3067fabc --- /dev/null +++ b/prdoc/pr_5393.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: Allow to enable full PoV size + +doc: + - audience: Node Dev + description: | + A feature is introduced allowing a collator to enable full PoV size at compile time. + WARNING: To use this feature, security considerations must be understood and the latest + SDK version must be used. + +crates: + - name: cumulus-client-consensus-aura + bump: minor diff --git a/prdoc/pr_5407.prdoc b/prdoc/pr_5407.prdoc new file mode 100644 index 000000000000..f7e6b86f9d1e --- /dev/null +++ b/prdoc/pr_5407.prdoc @@ -0,0 +1,17 @@ +title: Prepare PVFs if node is a validator in the next session + +doc: + - audience: [Node Operator, Node Dev] + description: | + This PR aims to remove the noise caused by the peer store's reputation system. + A warning was emitted each time a reputation was reported for a banned peer, + regardless of the reputation being positive. This has led in the past to + situations where it was hard to identify the actual reason of the ban and + caused noise for node operators. + + The `Banned, disconnecting.` warning is logged only when the peer is banned. + Other misbehaves are logged as `Misbehaved during the ban threshold`. + +crates: + - name: sc-network + bump: patch diff --git a/prdoc/pr_5410.prdoc b/prdoc/pr_5410.prdoc new file mode 100644 index 000000000000..d0a32bec7423 --- /dev/null +++ b/prdoc/pr_5410.prdoc @@ -0,0 +1,11 @@ +title: Reactive syncing metrics + +doc: + - audience: Node Dev + description: | + Syncing metrics are now updated immediate as changes happen rather than every 1100ms as it was happening before. + This resulted in minor, but breaking API changes. + +crates: + - name: sc-network-sync + bump: major diff --git a/prdoc/pr_5411.prdoc b/prdoc/pr_5411.prdoc new file mode 100644 index 000000000000..c24001d77bda --- /dev/null +++ b/prdoc/pr_5411.prdoc @@ -0,0 +1,3 @@ +crates: + - name: polkadot-approval-distribution + bump: none diff --git a/prdoc/pr_5424.prdoc b/prdoc/pr_5424.prdoc new file mode 100644 index 000000000000..a94bf7aaeba2 --- /dev/null +++ b/prdoc/pr_5424.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: Suppress the log output for transaction propagation when no transactions are present + +doc: + - audience: Node Dev + description: | + Previously, the log message `Propagating transactions` would always be printed, even when there were no transactions to propagate. This patch optimizes the logging by returning early when no transactions are present, resulting in cleaner and more relevant log output. + +crates: +- name: sc-network-transactions + bump: none diff --git a/prdoc/pr_5430.prdoc b/prdoc/pr_5430.prdoc new file mode 100644 index 000000000000..83d6d81e252e --- /dev/null +++ b/prdoc/pr_5430.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-collator-selection: correctly register weight in `new_session`" + +doc: + - audience: Runtime Dev + description: | + - Fixes an incorrect usage of the `WeightInfo` trait for `new_session`. + +crates: + - name: pallet-collator-selection + bump: patch diff --git a/prdoc/pr_5431.prdoc b/prdoc/pr_5431.prdoc new file mode 100644 index 000000000000..9f6db7136a58 --- /dev/null +++ b/prdoc/pr_5431.prdoc @@ -0,0 +1,20 @@ +title: Remove the need to wait for target block header in warp sync implementation + +doc: + - audience: Node Dev + description: | + Previously warp sync needed to wait for target block header of the relay chain to become available before warp + sync can start, which resulted in cumbersome APIs. Parachain initialization was refactored to initialize warp sync + with target block header from the very beginning, improving and simplifying sync API. + +crates: + - name: sc-service + bump: major + - name: sc-network-sync + bump: major + - name: polkadot-service + bump: major + - name: cumulus-client-service + bump: major + - name: sc-informant + bump: major diff --git a/prdoc/pr_5436.prdoc b/prdoc/pr_5436.prdoc new file mode 100644 index 000000000000..ea624b7bc32d --- /dev/null +++ b/prdoc/pr_5436.prdoc @@ -0,0 +1,20 @@ +# 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 Polkadot Coretime Chain genesis chain-spec + +doc: + - audience: Node Operator + description: | + The Polkadot Coretime Chain can now be run as all other system parachains without specifying a + chain-spec. However this will soon be deprecated and `--chain ./chain-spec.json` should continue + to be used instead. + + - audience: Runtime User + description: | + The Polkadot Coretime Chain chain-specs have been added to the polkadot-sdk repo and can now be + pulled from there along with all other system chains. + +crates: + - name: polkadot-parachain-bin + bump: minor \ No newline at end of file diff --git a/prdoc/pr_5439.prdoc b/prdoc/pr_5439.prdoc new file mode 100644 index 000000000000..00fa48de0e25 --- /dev/null +++ b/prdoc/pr_5439.prdoc @@ -0,0 +1,16 @@ +title: "Deprecated calls removed in cumulus parachain system pallet" + +doc: + - audience: [Runtime Dev, Runtime User] + description: | + Call `authorize_upgrade` in parachain system pallet `cumulus-pallet-parachain-system` has + been removed, use `authorize_upgrade` or `authorize_upgrade_without_checks` calls in system + pallet `frame-system` instead. + Call `enact_authorized_upgrade` in parachain system pallet `cumulus-pallet-parachain-system` + has been removed, use `apply_authorized_upgrade` call in system pallet `frame-system` instead. + +crates: + - name: cumulus-pallet-parachain-system + bump: major + - name: cumulus-pallet-xcmp-queue + bump: none diff --git a/prdoc/pr_5442.prdoc b/prdoc/pr_5442.prdoc new file mode 100644 index 000000000000..6adc34d71ad3 --- /dev/null +++ b/prdoc/pr_5442.prdoc @@ -0,0 +1,10 @@ +title: Derive `Clone` on `EncodableOpaqueLeaf` + +doc: + - audience: Runtime Dev + description: | + `Clone` was derived on `EncodableOpaqueLeaf` for convenience of downstream users + +crates: + - name: sp-mmr-primitives + bump: patch diff --git a/prdoc/pr_5443.prdoc b/prdoc/pr_5443.prdoc new file mode 100644 index 000000000000..0fd396be06a2 --- /dev/null +++ b/prdoc/pr_5443.prdoc @@ -0,0 +1,10 @@ +crates: +- name: frame-remote-externalities + bump: patch +doc: +- audience: Runtime Dev + description: as part of https://github.com/paritytech/devops/issues/3502, try-runtime + nodes were migrated to a new provider with a new domain address. The PR fixes + all the references to try-runtime nodes on rococo, westend, kusama and polkadot + networks +title: change try-runtime rpc domains diff --git a/prdoc/pr_5450.prdoc b/prdoc/pr_5450.prdoc new file mode 100644 index 000000000000..ccd319cad246 --- /dev/null +++ b/prdoc/pr_5450.prdoc @@ -0,0 +1,18 @@ +title: Sync status refactoring + +doc: + - audience: Node Dev + description: | + `SyncingService` API in `sc-network-sync` has changed with some of the redundant methods related to sync status + removed that were mostly used internally or for testing purposes and is unlikely to impact external code. + `ExtendedPeerInfo` now has working `Clone` and `Copy` implementation. + +crates: + - name: sc-informant + bump: major + - name: sc-network-sync + bump: major + - name: sc-network-test + bump: major + - name: sc-service + bump: major diff --git a/prdoc/pr_5466.prdoc b/prdoc/pr_5466.prdoc new file mode 100644 index 000000000000..57f20b3585b4 --- /dev/null +++ b/prdoc/pr_5466.prdoc @@ -0,0 +1,14 @@ +crates: +- bump: patch + name: frame-omni-bencher +- bump: patch + name: frame-benchmarking-cli +doc: +- audience: Runtime Dev + description: | + Changes: + - Set default level to `Info` again. Seems like a dependency update set it to something higher. + - Fix docs to not use `--locked` since we rely on dependency bumps via cargo. + - Add README with rust docs. + - Fix bug where the node ignored `--heap-pages` argument. +title: frame-omni-bencher maintenance diff --git a/prdoc/pr_5467.prdoc b/prdoc/pr_5467.prdoc new file mode 100644 index 000000000000..2634c255e168 --- /dev/null +++ b/prdoc/pr_5467.prdoc @@ -0,0 +1,10 @@ +title: Make PendingConfigs storage item public + +doc: + - audience: Runtime Dev + description: | + Make PendingConfigs storage item in polkadot's configuration crate public. + +crates: + - name: polkadot-runtime-parachains + bump: minor \ No newline at end of file diff --git a/scripts/bench-all.sh b/scripts/bench-all.sh deleted file mode 100755 index e5512e26bbad..000000000000 --- a/scripts/bench-all.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -set -eu -o pipefail -shopt -s inherit_errexit -shopt -s globstar - -. "$(realpath "$(dirname "${BASH_SOURCE[0]}")/command-utils.sh")" - -get_arg optional --pallet "$@" -PALLET="${out:-""}" - -if [[ ! -z "$PALLET" ]]; then - . "$(dirname "${BASH_SOURCE[0]}")/lib/bench-all-pallet.sh" "$@" -else - . "$(dirname "${BASH_SOURCE[0]}")/bench.sh" --subcommand=all "$@" -fi diff --git a/scripts/bench.sh b/scripts/bench.sh deleted file mode 100755 index 2f4ef7ec6a14..000000000000 --- a/scripts/bench.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/bash -# Initially based on https://github.com/paritytech/bench-bot/blob/cd3b2943d911ae29e41fe6204788ef99c19412c3/bench.js - -# Most external variables used in this script, such as $GH_CONTRIBUTOR, are -# related to https://github.com/paritytech/try-runtime-bot - -# This script relies on $GITHUB_TOKEN which is probably a protected GitLab CI -# variable; if this assumption holds true, it is implied that this script should -# be ran only on protected pipelines - -set -eu -o pipefail -shopt -s inherit_errexit - -# realpath allows to reuse the current -BENCH_ROOT_DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")") - -. "$(realpath "$(dirname "${BASH_SOURCE[0]}")/command-utils.sh")" - -repository_name="$(basename "$PWD")" - -get_arg optional --target_dir "$@" -target_dir="${out:-""}" - -get_arg optional --noexit "$@" -noexit="${out:-""}" - -output_path="." - -profile="production" - -if [[ "$repository_name" == "polkadot-sdk" ]]; then - output_path="./$target_dir" -fi - -cargo_run_benchmarks="cargo run --quiet --profile=${profile}" - -echo "Repository: $repository_name" -echo "Target Dir: $target_dir" -echo "Output Path: $output_path" - -cargo_run() { - echo "Running $cargo_run_benchmarks" "${args[@]}" - - # if not patched with PATCH_something=123 then use --locked - if [[ -z "${BENCH_PATCHED:-}" ]]; then - cargo_run_benchmarks+=" --locked" - fi - - $cargo_run_benchmarks "${args[@]}" -} - - -main() { - - # Remove the "github" remote since the same repository might be reused by a - # GitLab runner, therefore the remote might already exist from a previous run - # in case it was not cleaned up properly for some reason - &>/dev/null git remote remove github || : - - tmp_dirs=() - cleanup() { - exit_code=$? - # Clean up the "github" remote at the end since it contains the - # $GITHUB_TOKEN secret, which is only available for protected pipelines on - # GitLab - &>/dev/null git remote remove github || : - rm -rf "${tmp_dirs[@]}" - echo "Done, exit: $exit_code" - exit $exit_code - } - - # avoid exit if --noexit is passed - if [ -z "$noexit" ]; then - trap cleanup EXIT - fi - - # set -x - - get_arg required --subcommand "$@" - local subcommand="${out:-""}" - - case "$subcommand" in - runtime|pallet|xcm) - echo 'Running bench_pallet' - . "$BENCH_ROOT_DIR/lib/bench-pallet.sh" "$@" - ;; - overhead) - echo 'Running bench_overhead' - . "$BENCH_ROOT_DIR/lib/bench-overhead.sh" "$@" - ;; - all) - echo "Running all-$target_dir" - . "$BENCH_ROOT_DIR/lib/bench-all-${target_dir}.sh" "$@" - ;; - *) - die "Invalid subcommand $subcommand to process_args" - ;; - esac - - # set +x - - # in case we used diener to patch some dependency during benchmark execution, - # revert the patches so that they're not included in the diff - git checkout --quiet HEAD Cargo.toml - - # Save the generated weights to GitLab artifacts in case commit+push fails - echo "Showing weights diff for command" - git diff -P | tee -a "${ARTIFACTS_DIR}/weights.patch" - echo "Wrote weights patch to \"${ARTIFACTS_DIR}/weights.patch\"" - - - # instead of using `cargo run --locked`, we allow the Cargo files to be updated - # but avoid committing them. It is so `cmd_runner_apply_patches` can work - git restore --staged Cargo.* -} - -main "$@" diff --git a/scripts/command-utils.sh b/scripts/command-utils.sh deleted file mode 100644 index 252e4c86480e..000000000000 --- a/scripts/command-utils.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env bash - -if [ "${LOADED_UTILS_SH:-}" ]; then - return -else - export LOADED_UTILS_SH=true -fi - -export ARTIFACTS_DIR="$PWD/.git/.artifacts" - -die() { - if [ "${1:-}" ]; then - >&2 echo "$1" - fi - exit 1 -} - -get_arg() { - local arg_type="$1" - shift - - local is_required - case "$arg_type" in - required|required-many) - is_required=true - ;; - optional|optional-many) ;; - *) - die "Invalid is_required argument \"$2\" in get_arg" - ;; - esac - - local has_many_values - if [ "${arg_type: -6}" == "-many" ]; then - has_many_values=true - fi - - local option_arg="$1" - shift - - local args=("$@") - - unset out - out=() - - local get_next_arg - for arg in "${args[@]}"; do - if [ "${get_next_arg:-}" ]; then - out+=("$arg") - unset get_next_arg - if [ ! "${has_many_values:-}" ]; then - break - fi - # --foo=bar (get the value after '=') - elif [ "${arg:0:$(( ${#option_arg} + 1 ))}" == "$option_arg=" ]; then - out+=("${arg:$(( ${#option_arg} + 1 ))}") - if [ ! "${has_many_values:-}" ]; then - break - fi - # --foo bar (get the next argument) - elif [ "$arg" == "$option_arg" ]; then - get_next_arg=true - fi - done - - # arg list ended with --something but no argument was provided next - if [ "${get_next_arg:-}" ]; then - die "Expected argument after \"${args[-1]}"\" - fi - - if [ "${out[0]:-}" ]; then - if [ ! "${has_many_values:-}" ]; then - out="${out[0]}" - fi - elif [ "${is_required:-}" ]; then - die "Argument $option_arg is required, but was not found" - else - unset out - fi -} diff --git a/scripts/generate-umbrella.py b/scripts/generate-umbrella.py index 3293c30bc828..e1ef6de86f9c 100644 --- a/scripts/generate-umbrella.py +++ b/scripts/generate-umbrella.py @@ -64,7 +64,7 @@ def main(path, version): if manifest['lib']['proc-macro']: nostd_crates.append((crate, path)) continue - + # Crates without a lib.rs cannot be no_std if not os.path.exists(lib_path): print(f"Skipping {crate.name} as it does not have a 'src/lib.rs'") @@ -86,16 +86,18 @@ def main(path, version): # Sort by name std_crates.sort(key=lambda x: x[0].name) nostd_crates.sort(key=lambda x: x[0].name) + + runtime_crates = [crate for crate in nostd_crates if 'frame' in crate[0].name or crate[0].name.startswith('sp-')] all_crates = std_crates + nostd_crates all_crates.sort(key=lambda x: x[0].name) dependencies = {} for (crate, path) in nostd_crates: dependencies[crate.name] = {"path": f"../{path}", "default-features": False, "optional": True} - + for (crate, path) in std_crates: dependencies[crate.name] = {"path": f"../{path}", "default-features": False, "optional": True} - + # The empty features are filled by Zepter features = { "default": [ "std" ], @@ -105,9 +107,11 @@ def main(path, version): "serde": [], "experimental": [], "with-tracing": [], - "runtime": list([f"{d.name}" for d, _ in nostd_crates]), + "runtime-full": list([f"{d.name}" for d, _ in nostd_crates]), + "runtime": list([f"{d.name}" for d, _ in runtime_crates]), "node": ["std"] + list([f"{d.name}" for d, _ in std_crates]), "tuples-96": [], + "riscv": [], } manifest = { @@ -119,7 +123,7 @@ def main(path, version): "description": "Polkadot SDK umbrella crate.", "license": "Apache-2.0", "metadata": { "docs": { "rs": { - "features": ["runtime", "node"], + "features": ["runtime-full", "node"], "targets": ["x86_64-unknown-linux-gnu"] }}} }, @@ -159,9 +163,9 @@ def main(path, version): f.write(f'\n/// {desc}') f.write(f'\n#[cfg(feature = "{crate.name}")]\n') f.write(f"pub use {use};\n") - + print(f"Wrote {lib_path}") - + add_to_workspace(workspace.path) """ @@ -188,7 +192,7 @@ def add_to_workspace(path): manifest = re.sub(r'^members = \[', 'members = [\n "umbrella",', manifest, flags=re.M) with open(os.path.join(path, "Cargo.toml"), "w") as f: f.write(manifest) - + os.chdir(path) # hack os.system("cargo metadata --format-version 1 > /dev/null") # update the lockfile os.system(f"zepter") # enable the features @@ -203,3 +207,4 @@ def parse_args(): if __name__ == "__main__": args = parse_args() main(args.sdk, args.version) + diff --git a/scripts/lib/bench-all-cumulus.sh b/scripts/lib/bench-all-cumulus.sh deleted file mode 100755 index f4c2a35c6b6b..000000000000 --- a/scripts/lib/bench-all-cumulus.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env bash -# originally moved from https://github.com/paritytech/cumulus/blob/445f9277ab55b4d930ced4fbbb38d27c617c6658/scripts/benchmarks-ci.sh - -# default RUST_LOG is warn, but could be overridden -export RUST_LOG="${RUST_LOG:-error}" - -THIS_DIR=$(dirname "${BASH_SOURCE[0]}") -. "$THIS_DIR/../command-utils.sh" - -POLKADOT_PARACHAIN="./target/$profile/polkadot-parachain" - -run_cumulus_bench() { - local artifactsDir="$ARTIFACTS_DIR" - local category=$1 - local runtimeName=$2 - local paraId=${3:-} - - local benchmarkOutput="$output_path/parachains/runtimes/$category/$runtimeName/src/weights" - local benchmarkRuntimeChain - if [[ ! -z "$paraId" ]]; then - benchmarkRuntimeChain="${runtimeName}-dev-$paraId" - else - benchmarkRuntimeChain="$runtimeName-dev" - fi - - local benchmarkMetadataOutputDir="$artifactsDir/$runtimeName" - mkdir -p "$benchmarkMetadataOutputDir" - - # Load all pallet names in an array. - echo "[+] Listing pallets for runtime $runtimeName for chain: $benchmarkRuntimeChain ..." - local pallets=($( - $POLKADOT_PARACHAIN benchmark pallet --list --chain="${benchmarkRuntimeChain}" |\ - tail -n+2 |\ - cut -d',' -f1 |\ - sort |\ - uniq - )) - - if [ ${#pallets[@]} -ne 0 ]; then - echo "[+] Benchmarking ${#pallets[@]} pallets for runtime $runtimeName for chain: $benchmarkRuntimeChain, pallets:" - for pallet in "${pallets[@]}"; do - echo " [+] $pallet" - done - else - echo "$runtimeName pallet list not found in benchmarks-ci.sh" - exit 1 - fi - - for pallet in "${pallets[@]}"; do - # (by default) do not choose output_file, like `pallet_assets.rs` because it does not work for multiple instances - # `benchmark pallet` command will decide the output_file name if there are multiple instances - local output_file="" - local extra_args="" - # a little hack for pallet_xcm_benchmarks - we want to force custom implementation for XcmWeightInfo - if [[ "$pallet" == "pallet_xcm_benchmarks::generic" ]] || [[ "$pallet" == "pallet_xcm_benchmarks::fungible" ]]; then - output_file="xcm/${pallet//::/_}.rs" - extra_args="--template=$output_path/templates/xcm-bench-template.hbs" - fi - $POLKADOT_PARACHAIN benchmark pallet \ - $extra_args \ - --chain="${benchmarkRuntimeChain}" \ - --wasm-execution=compiled \ - --pallet="$pallet" \ - --no-storage-info \ - --no-median-slopes \ - --no-min-squares \ - --extrinsic='*' \ - --steps=50 \ - --repeat=20 \ - --json \ - --header="$output_path/file_header.txt" \ - --output="${benchmarkOutput}/${output_file}" >> "$benchmarkMetadataOutputDir/${pallet//::/_}_benchmark.json" - done -} - - -echo "[+] Compiling benchmarks..." -cargo build --profile $profile --locked --features=runtime-benchmarks -p polkadot-parachain-bin - -# Run benchmarks for all pallets of a given runtime if runtime argument provided -get_arg optional --runtime "$@" -runtime="${out:-""}" - -if [[ $runtime ]]; then - paraId="" - case "$runtime" in - asset-*) - category="assets" - ;; - collectives-*) - category="collectives" - ;; - coretime-*) - category="coretime" - ;; - bridge-*) - category="bridge-hubs" - ;; - contracts-*) - category="contracts" - ;; - people-*) - category="people" - ;; - glutton-*) - category="glutton" - paraId="1300" - ;; - *) - echo "Unknown runtime: $runtime" - exit 1 - ;; - esac - - run_cumulus_bench $category $runtime $paraId - -else # run all - # Assets - run_cumulus_bench assets asset-hub-rococo - run_cumulus_bench assets asset-hub-westend - - # Collectives - run_cumulus_bench collectives collectives-westend - - # Coretime - run_cumulus_bench coretime coretime-rococo - run_cumulus_bench coretime coretime-westend - - # People - run_cumulus_bench people people-rococo - run_cumulus_bench people people-westend - - # Bridge Hubs - run_cumulus_bench bridge-hubs bridge-hub-rococo - run_cumulus_bench bridge-hubs bridge-hub-westend - - # Glutton - run_cumulus_bench glutton glutton-westend 1300 -fi diff --git a/scripts/lib/bench-all-pallet.sh b/scripts/lib/bench-all-pallet.sh deleted file mode 100644 index e6908045ddbd..000000000000 --- a/scripts/lib/bench-all-pallet.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash - -set -eu -o pipefail -shopt -s inherit_errexit -shopt -s globstar - -. "$(dirname "${BASH_SOURCE[0]}")/../command-utils.sh" - -get_arg required --pallet "$@" -PALLET="${out:-""}" - -REPO_NAME="$(basename "$PWD")" -BASE_COMMAND="$(dirname "${BASH_SOURCE[0]}")/../../bench/bench.sh --noexit=true --subcommand=pallet" - -WEIGHT_FILE_PATHS=( $(find . -type f -name "${PALLET}.rs" -path "**/weights/*" | sed 's|^\./||g') ) - -# convert pallet_ranked_collective to ranked-collective -CLEAN_PALLET=$(echo $PALLET | sed 's/pallet_//g' | sed 's/_/-/g') - -# add substrate pallet weights to a list -SUBSTRATE_PALLET_PATH=$(ls substrate/frame/$CLEAN_PALLET/src/weights.rs || :) -if [ ! -z "${SUBSTRATE_PALLET_PATH}" ]; then - WEIGHT_FILE_PATHS+=("$SUBSTRATE_PALLET_PATH") -fi - -# add trappist pallet weights to a list -TRAPPIST_PALLET_PATH=$(ls pallet/$CLEAN_PALLET/src/weights.rs || :) -if [ ! -z "${TRAPPIST_PALLET_PATH}" ]; then - WEIGHT_FILE_PATHS+=("$TRAPPIST_PALLET_PATH") -fi - -COMMANDS=() - -if [ "${#WEIGHT_FILE_PATHS[@]}" -eq 0 ]; then - echo "No weights files found for pallet: $PALLET" - exit 1 -else - echo "Found weights files for pallet: $PALLET" -fi - -for f in ${WEIGHT_FILE_PATHS[@]}; do - echo "- $f" - # f examples: - # cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_balances.rs - # polkadot/runtime/rococo/src/weights/pallet_balances.rs - # runtime/trappist/src/weights/pallet_assets.rs - TARGET_DIR=$(echo $f | cut -d'/' -f 1) - - if [ "$REPO_NAME" == "polkadot-sdk" ]; then - case $TARGET_DIR in - cumulus) - TYPE=$(echo $f | cut -d'/' -f 2) - # Example: cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_balances.rs - if [ "$TYPE" == "parachains" ]; then - RUNTIME=$(echo $f | cut -d'/' -f 5) - RUNTIME_DIR=$(echo $f | cut -d'/' -f 4) - COMMANDS+=("$BASE_COMMAND --runtime=$RUNTIME --runtime_dir=$RUNTIME_DIR --target_dir=$TARGET_DIR --pallet=$PALLET") - fi - ;; - polkadot) - # Example: polkadot/runtime/rococo/src/weights/pallet_balances.rs - RUNTIME=$(echo $f | cut -d'/' -f 3) - COMMANDS+=("$BASE_COMMAND --runtime=$RUNTIME --target_dir=$TARGET_DIR --pallet=$PALLET") - ;; - substrate) - # Example: substrate/frame/contracts/src/weights.rs - COMMANDS+=("$BASE_COMMAND --target_dir=$TARGET_DIR --runtime=dev --pallet=$PALLET") - ;; - *) - echo "Unknown dir: $TARGET_DIR" - exit 1 - ;; - esac - fi - - if [ "$REPO_NAME" == "trappist" ]; then - case $TARGET_DIR in - runtime) - TYPE=$(echo $f | cut -d'/' -f 2) - if [ "$TYPE" == "trappist" || "$TYPE" == "stout" ]; then - # Example: runtime/trappist/src/weights/pallet_assets.rs - COMMANDS+=("$BASE_COMMAND --target_dir=trappist --runtime=$TYPE --pallet=$PALLET") - fi - ;; - *) - echo "Unknown dir: $TARGET_DIR" - exit 1 - ;; - esac - fi -done - -for cmd in "${COMMANDS[@]}"; do - echo "Running command: $cmd" - . $cmd -done diff --git a/scripts/lib/bench-all-polkadot.sh b/scripts/lib/bench-all-polkadot.sh deleted file mode 100644 index ac52e00140e3..000000000000 --- a/scripts/lib/bench-all-polkadot.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash - -# Runs all benchmarks for all pallets, for a given runtime, provided by $1 -# Should be run on a reference machine to gain accurate benchmarks -# current reference machine: https://github.com/paritytech/polkadot/pull/6508/files -# original source: https://github.com/paritytech/polkadot/blob/b9842c4b52f6791fef6c11ecd020b22fe614f041/scripts/run_all_benches.sh - -get_arg required --runtime "$@" -runtime="${out:-""}" - -# default RUST_LOG is error, but could be overridden -export RUST_LOG="${RUST_LOG:-error}" - -echo "[+] Compiling benchmarks..." -cargo build --profile $profile --locked --features=runtime-benchmarks -p polkadot - -POLKADOT_BIN="./target/$profile/polkadot" - -# Update the block and extrinsic overhead weights. -echo "[+] Benchmarking block and extrinsic overheads..." -OUTPUT=$( - $POLKADOT_BIN benchmark overhead \ - --chain="${runtime}-dev" \ - --wasm-execution=compiled \ - --weight-path="$output_path/runtime/${runtime}/constants/src/weights/" \ - --warmup=10 \ - --repeat=100 \ - --header="$output_path/file_header.txt" -) -if [ $? -ne 0 ]; then - echo "$OUTPUT" >> "$ERR_FILE" - echo "[-] Failed to benchmark the block and extrinsic overheads. Error written to $ERR_FILE; continuing..." -fi - - -# Load all pallet names in an array. -PALLETS=($( - $POLKADOT_BIN benchmark pallet --list --chain="${runtime}-dev" |\ - tail -n+2 |\ - cut -d',' -f1 |\ - sort |\ - uniq -)) - -echo "[+] Benchmarking ${#PALLETS[@]} pallets for runtime $runtime" - -# Define the error file. -ERR_FILE="${ARTIFACTS_DIR}/benchmarking_errors.txt" -# Delete the error file before each run. -rm -f $ERR_FILE - -# Benchmark each pallet. -for PALLET in "${PALLETS[@]}"; do - echo "[+] Benchmarking $PALLET for $runtime"; - - output_file="" - if [[ $PALLET == *"::"* ]]; then - # translates e.g. "pallet_foo::bar" to "pallet_foo_bar" - output_file="${PALLET//::/_}.rs" - fi - - OUTPUT=$( - $POLKADOT_BIN benchmark pallet \ - --chain="${runtime}-dev" \ - --steps=50 \ - --repeat=20 \ - --no-storage-info \ - --no-median-slopes \ - --no-min-squares \ - --pallet="$PALLET" \ - --extrinsic="*" \ - --execution=wasm \ - --wasm-execution=compiled \ - --header="$output_path/file_header.txt" \ - --output="$output_path/runtime/${runtime}/src/weights/${output_file}" 2>&1 - ) - if [ $? -ne 0 ]; then - echo "$OUTPUT" >> "$ERR_FILE" - echo "[-] Failed to benchmark $PALLET. Error written to $ERR_FILE; continuing..." - fi -done - -# Check if the error file exists. -if [ -f "$ERR_FILE" ]; then - echo "[-] Some benchmarks failed. See: $ERR_FILE" -else - echo "[+] All benchmarks passed." -fi diff --git a/scripts/lib/bench-all-substrate.sh b/scripts/lib/bench-all-substrate.sh deleted file mode 100644 index eeb18cdd8bbb..000000000000 --- a/scripts/lib/bench-all-substrate.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env bash - -# This file is part of Substrate. -# Copyright (C) 2022 Parity Technologies (UK) Ltd. -# SPDX-License-Identifier: Apache-2.0 -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This script has three parts which all use the Substrate runtime: -# - Pallet benchmarking to update the pallet weights -# - Overhead benchmarking for the Extrinsic and Block weights -# - Machine benchmarking -# -# Should be run on a reference machine to gain accurate benchmarks -# current reference machine: https://github.com/paritytech/substrate/pull/5848 - -# Original source: https://github.com/paritytech/substrate/blob/ff9921a260a67e3a71f25c8b402cd5c7da787a96/scripts/run_all_benchmarks.sh -# Fail if any sub-command in a pipe fails, not just the last one. -set -o pipefail -# Fail on undeclared variables. -set -u -# Fail if any sub-command fails. -set -e -# Fail on traps. -# set -E - -# default RUST_LOG is warn, but could be overridden -export RUST_LOG="${RUST_LOG:-error}" - -echo "[+] Compiling Substrate benchmarks..." -cargo build --profile=$profile --locked --features=runtime-benchmarks -p staging-node-cli - -# The executable to use. -SUBSTRATE="./target/$profile/substrate-node" - -# Manually exclude some pallets. -EXCLUDED_PALLETS=( - # Helper pallets - "pallet_election_provider_support_benchmarking" - # Pallets without automatic benchmarking - "pallet_babe" - "pallet_grandpa" - "pallet_mmr" - "pallet_offences" - # Only used for testing, does not need real weights. - "frame_benchmarking_pallet_pov" - "pallet_example_tasks" - "pallet_example_basic" - "pallet_example_split" - "pallet_example_kitchensink" - "pallet_example_mbm" - "tasks_example" -) - -# Load all pallet names in an array. -ALL_PALLETS=($( - $SUBSTRATE benchmark pallet --list --chain=dev |\ - tail -n+2 |\ - cut -d',' -f1 |\ - sort |\ - uniq -)) - -# Define the error file. -ERR_FILE="${ARTIFACTS_DIR}/benchmarking_errors.txt" - -# Delete the error file before each run. -rm -f "$ERR_FILE" - -mkdir -p "$(dirname "$ERR_FILE")" - -# Update the block and extrinsic overhead weights. -echo "[+] Benchmarking block and extrinsic overheads..." -OUTPUT=$( - $SUBSTRATE benchmark overhead \ - --chain=dev \ - --wasm-execution=compiled \ - --weight-path="$output_path/frame/support/src/weights/" \ - --header="$output_path/HEADER-APACHE2" \ - --warmup=10 \ - --repeat=100 2>&1 -) -if [ $? -ne 0 ]; then - echo "$OUTPUT" >> "$ERR_FILE" - echo "[-] Failed to benchmark the block and extrinsic overheads. Error written to $ERR_FILE; continuing..." -fi - -echo "[+] Benchmarking ${#ALL_PALLETS[@]} Substrate pallets and excluding ${#EXCLUDED_PALLETS[@]}." - -echo "[+] Excluded pallets ${EXCLUDED_PALLETS[@]}" -echo "[+] ------ " -echo "[+] Whole list pallets ${ALL_PALLETS[@]}" - -# Benchmark each pallet. -for PALLET in "${ALL_PALLETS[@]}"; do - FOLDER="$(echo "${PALLET#*_}" | tr '_' '-')"; - WEIGHT_FILE="$output_path/frame/${FOLDER}/src/weights.rs" - - # Skip the pallet if it is in the excluded list. - - if [[ " ${EXCLUDED_PALLETS[@]} " =~ " ${PALLET} " ]]; then - echo "[+] Skipping $PALLET as it is in the excluded list." - continue - fi - - echo "[+] Benchmarking $PALLET with weight file $WEIGHT_FILE"; - - set +e # Disable exit on error for the benchmarking of the pallets - OUTPUT=$( - $SUBSTRATE benchmark pallet \ - --chain=dev \ - --steps=50 \ - --repeat=20 \ - --pallet="$PALLET" \ - --no-storage-info \ - --no-median-slopes \ - --no-min-squares \ - --extrinsic="*" \ - --wasm-execution=compiled \ - --heap-pages=4096 \ - --output="$WEIGHT_FILE" \ - --header="$output_path/HEADER-APACHE2" \ - --template="$output_path/.maintain/frame-weight-template.hbs" 2>&1 - ) - if [ $? -ne 0 ]; then - echo -e "$PALLET: $OUTPUT\n" >> "$ERR_FILE" - echo "[-] Failed to benchmark $PALLET. Error written to $ERR_FILE; continuing..." - fi - set -e # Re-enable exit on error -done - - -# Check if the error file exists. -if [ -s "$ERR_FILE" ]; then - echo "[-] Some benchmarks failed. See: $ERR_FILE" - exit 1 -else - echo "[+] All benchmarks passed." -fi diff --git a/scripts/lib/bench-overhead.sh b/scripts/lib/bench-overhead.sh deleted file mode 100644 index c4cca8b4c128..000000000000 --- a/scripts/lib/bench-overhead.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -THIS_DIR=$(dirname "${BASH_SOURCE[0]}") -. "$THIS_DIR/../command-utils.sh" - -bench_overhead_common_args=( - -- - benchmark - overhead - --wasm-execution=compiled - --warmup=10 - --repeat=100 -) -bench_overhead() { - local args - case "$target_dir" in - substrate) - args=( - --bin=substrate - "${bench_overhead_common_args[@]}" - --header="$output_path/HEADER-APACHE2" - --weight-path="$output_path/frame/support/src/weights" - --chain="dev" - ) - ;; - polkadot) - get_arg required --runtime "$@" - local runtime="${out:-""}" - args=( - --bin=polkadot - "${bench_overhead_common_args[@]}" - --header="$output_path/file_header.txt" - --weight-path="$output_path/runtime/$runtime/constants/src/weights" - --chain="$runtime-dev" - ) - ;; - cumulus) - get_arg required --runtime "$@" - local runtime="${out:-""}" - args=( - -p=polkadot-parachain-bin - "${bench_overhead_common_args[@]}" - --header="$output_path/file_header.txt" - --weight-path="$output_path/parachains/runtimes/assets/$runtime/src/weights" - --chain="$runtime" - ) - ;; - trappist) - get_arg required --runtime "$@" - local runtime="${out:-""}" - args=( - "${bench_overhead_common_args[@]}" - --header="$output_path/templates/file_header.txt" - --weight-path="$output_path/runtime/$runtime/src/weights" - --chain="$runtime-dev" - ) - ;; - *) - die "Target Dir \"$target_dir\" is not supported in bench_overhead" - ;; - esac - - cargo_run "${args[@]}" -} - -bench_overhead "$@" diff --git a/scripts/lib/bench-pallet.sh b/scripts/lib/bench-pallet.sh deleted file mode 100644 index 15eac31e3a45..000000000000 --- a/scripts/lib/bench-pallet.sh +++ /dev/null @@ -1,178 +0,0 @@ -#!/bin/bash - -THIS_DIR=$(dirname "${BASH_SOURCE[0]}") -. "$THIS_DIR/../command-utils.sh" - -bench_pallet_common_args=( - -- - benchmark - pallet - --steps=50 - --repeat=20 - --extrinsic="*" - --wasm-execution=compiled - --heap-pages=4096 - --json-file="${ARTIFACTS_DIR}/bench.json" -) -bench_pallet() { - get_arg required --subcommand "$@" - local subcommand="${out:-""}" - - get_arg required --runtime "$@" - local runtime="${out:-""}" - - get_arg required --pallet "$@" - local pallet="${out:-""}" - - local args - case "$target_dir" in - substrate) - args=( - --features=runtime-benchmarks - --manifest-path="$output_path/bin/node/cli/Cargo.toml" - "${bench_pallet_common_args[@]}" - --pallet="$pallet" - --chain="$runtime" - ) - - case "$subcommand" in - pallet) - # Translates e.g. "pallet_foo::bar" to "pallet_foo_bar" - local output_dir="${pallet//::/_}" - - # Substrate benchmarks are output to the "frame" directory but they aren't - # named exactly after the $pallet argument. For example: - # - When $pallet == pallet_balances, the output folder is frame/balances - # - When $pallet == frame_benchmarking, the output folder is frame/benchmarking - # The common pattern we infer from those examples is that we should remove - # the prefix - if [[ "$output_dir" =~ ^[A-Za-z]*[^A-Za-z](.*)$ ]]; then - output_dir="${BASH_REMATCH[1]}" - fi - - # We also need to translate '_' to '-' due to the folders' naming - # conventions - output_dir="${output_dir//_/-}" - - args+=( - --header="$output_path/HEADER-APACHE2" - --output="$output_path/frame/$output_dir/src/weights.rs" - --template="$output_path/.maintain/frame-weight-template.hbs" - ) - ;; - *) - die "Subcommand $subcommand is not supported for $target_dir in bench_pallet" - ;; - esac - ;; - polkadot) - # For backward compatibility: replace "-dev" with "" - runtime=${runtime/-dev/} - - local weights_dir="$output_path/runtime/${runtime}/src/weights" - - args=( - --bin=polkadot - --features=runtime-benchmarks - "${bench_pallet_common_args[@]}" - --pallet="$pallet" - --chain="${runtime}-dev" - ) - - case "$subcommand" in - pallet) - args+=( - --header="$output_path/file_header.txt" - --output="${weights_dir}/" - ) - ;; - xcm) - args+=( - --header="$output_path/file_header.txt" - --template="$output_path/xcm/pallet-xcm-benchmarks/template.hbs" - --output="${weights_dir}/xcm/" - ) - ;; - *) - die "Subcommand $subcommand is not supported for $target_dir in bench_pallet" - ;; - esac - ;; - cumulus) - get_arg required --runtime_dir "$@" - local runtime_dir="${out:-""}" - local chain="$runtime" - - # to support specifying parachain id from runtime name (e.g. ["glutton-westend", "glutton-westend-dev-1300"]) - # If runtime ends with "-dev" or "-dev-\d+", leave as it is, otherwise concat "-dev" at the end of $chain - if [[ ! "$runtime" =~ -dev(-[0-9]+)?$ ]]; then - chain="${runtime}-dev" - fi - - # replace "-dev" or "-dev-\d+" with "" for runtime - runtime=$(echo "$runtime" | sed 's/-dev.*//g') - - args=( - -p=polkadot-parachain-bin - --features=runtime-benchmarks - "${bench_pallet_common_args[@]}" - --pallet="$pallet" - --chain="${chain}" - --header="$output_path/file_header.txt" - ) - - case "$subcommand" in - pallet) - args+=( - --output="$output_path/parachains/runtimes/$runtime_dir/$runtime/src/weights/" - ) - ;; - xcm) - mkdir -p "$output_path/parachains/runtimes/$runtime_dir/$runtime/src/weights/xcm" - args+=( - --template="$output_path/templates/xcm-bench-template.hbs" - --output="$output_path/parachains/runtimes/$runtime_dir/$runtime/src/weights/xcm/" - ) - ;; - *) - die "Subcommand $subcommand is not supported for $target_dir in bench_pallet" - ;; - esac - ;; - trappist) - local weights_dir="$output_path/runtime/$runtime/src/weights" - - args=( - --features=runtime-benchmarks - "${bench_pallet_common_args[@]}" - --pallet="$pallet" - --chain="${runtime}-dev" - --header="$output_path/templates/file_header.txt" - ) - - case "$subcommand" in - pallet) - args+=( - --output="${weights_dir}/" - ) - ;; - xcm) - args+=( - --template="$output_path/templates/xcm-bench-template.hbs" - --output="${weights_dir}/xcm/" - ) - ;; - *) - die "Subcommand $subcommand is not supported for $target_dir in bench_pallet" - ;; - esac - ;; - *) - die "Repository $target_dir is not supported in bench_pallet" - ;; - esac - - cargo_run "${args[@]}" -} - -bench_pallet "$@" diff --git a/scripts/sync.sh b/scripts/sync.sh deleted file mode 100755 index b5d8a5219937..000000000000 --- a/scripts/sync.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash - -set -eu -o pipefail - -. "$(realpath "$(dirname "${BASH_SOURCE[0]}")/command-utils.sh")" - - -# Function to check syncing status -check_syncing() { - # Send the system_health request and parse the isSyncing field - RESPONSE=$(curl -sSX POST http://127.0.0.1:9944 \ - --header 'Content-Type: application/json' \ - --data-raw '{"jsonrpc": "2.0", "method": "system_health", "params": [], "id": "1"}') - - # Check for errors in the curl command - if [ $? -ne 0 ]; then - echo "Error: Unable to send request to Polkadot node" - fi - - IS_SYNCING=$(echo $RESPONSE | jq -r '.result.isSyncing') - - # Check for errors in the jq command or missing field in the response - if [ $? -ne 0 ] || [ "$IS_SYNCING" == "null" ]; then - echo "Error: Unable to parse sync status from response" - fi - - # Return the isSyncing value - echo $IS_SYNCING -} - -main() { - get_arg required --chain "$@" - local chain="${out:-""}" - - get_arg required --type "$@" - local type="${out:-""}" - - export RUST_LOG="${RUST_LOG:-remote-ext=debug,runtime=trace}" - - cargo build --release - - cp "./target/release/polkadot" ./polkadot-bin - - # Start sync. - # "&" runs the process in the background - # "> /dev/tty" redirects the output of the process to the terminal - ./polkadot-bin --sync="$type" --chain="$chain" > "$ARTIFACTS_DIR/sync.log" 2>&1 & - - # Get the PID of process - POLKADOT_SYNC_PID=$! - - sleep 10 - - # Poll the node every 100 seconds until syncing is complete - while :; do - SYNC_STATUS="$(check_syncing)" - if [ "$SYNC_STATUS" == "true" ]; then - echo "Node is still syncing..." - sleep 100 - elif [ "$SYNC_STATUS" == "false" ]; then - echo "Node sync is complete!" - kill "$POLKADOT_SYNC_PID" # Stop the Polkadot node process once syncing is complete - exit 0 # Success - elif [[ "$SYNC_STATUS" = Error:* ]]; then - echo "$SYNC_STATUS" - exit 1 # Error - else - echo "Unknown error: $SYNC_STATUS" - exit 1 # Unknown error - fi - done -} - -main "$@" diff --git a/scripts/update-ui-tests.sh b/scripts/update-ui-tests.sh deleted file mode 100755 index d363e51e4041..000000000000 --- a/scripts/update-ui-tests.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -# Script for updating the UI tests for a new rust stable version. -# Exit on error -set -e - -# by default current rust stable will be used -RUSTUP_RUN="" -# check if we have a parameter -# ./scripts/update-ui-tests.sh 1.70 -if [ ! -z "$1" ]; then - echo "RUST_VERSION: $1" - # This will run all UI tests with the rust stable 1.70. - # The script requires that rustup is installed. - RUST_VERSION=$1 - RUSTUP_RUN="rustup run $RUST_VERSION" - - - echo "installing rustup $RUST_VERSION" - if ! command -v rustup &> /dev/null - then - echo "rustup needs to be installed" - exit - fi - - rustup install $RUST_VERSION - rustup component add rust-src --toolchain $RUST_VERSION -fi - -# Ensure we run the ui tests -export RUN_UI_TESTS=1 -# We don't need any wasm files for ui tests -export SKIP_WASM_BUILD=1 -# Let trybuild overwrite the .stderr files -export TRYBUILD=overwrite - -# ./substrate -$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 --features=no-metadata-docs,try-runtime,experimental ui -$RUSTUP_RUN cargo test -p xcm-procedural ui diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 5b827c9d718f..6e734a723cd3 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -104,6 +104,10 @@ try-runtime = [ "polkadot-sdk/try-runtime", "substrate-cli-test-utils/try-runtime", ] +riscv = [ + "kitchensink-runtime/riscv", + "polkadot-sdk/riscv", +] [[bench]] name = "transaction_pool" diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs index 8239637b3a9f..9ac9d8b4f67d 100644 --- a/substrate/bin/node/cli/benches/block_production.rs +++ b/substrate/bin/node/cli/benches/block_production.rs @@ -124,7 +124,7 @@ fn extrinsic_set_time(now: u64) -> OpaqueExtrinsic { .into() } -fn import_block(mut client: &FullClient, built: BuiltBlock) { +fn import_block(client: &FullClient, built: BuiltBlock) { let mut params = BlockImportParams::new(BlockOrigin::File, built.block.header); params.state_action = StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes(built.storage_changes)); diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index d58ca888d419..8fdcc7261b55 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -37,7 +37,7 @@ use sc_consensus_babe::{self, SlotProportion}; use sc_network::{ event::Event, service::traits::NetworkService, NetworkBackend, NetworkEventStream, }; -use sc_network_sync::{strategy::warp::WarpSyncParams, SyncingService}; +use sc_network_sync::{strategy::warp::WarpSyncConfig, SyncingService}; use sc_service::{config::Configuration, error::Error as ServiceError, RpcHandlers, TaskManager}; use sc_statement_store::Store as StatementStore; use sc_telemetry::{Telemetry, TelemetryWorker}; @@ -177,7 +177,6 @@ pub fn new_partial( sc_transaction_pool::FullPool, ( impl Fn( - node_rpc::DenyUnsafe, sc_rpc::SubscriptionTaskExecutor, ) -> Result, sc_service::Error>, ( @@ -318,13 +317,12 @@ 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: node_rpc::SubscriptionTaskExecutor| { + move |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(), @@ -517,7 +515,7 @@ pub fn new_full_base::Hash>>( spawn_handle: task_manager.spawn_handle(), import_queue, block_announce_validator_builder: None, - warp_sync_params: Some(WarpSyncParams::WithProvider(warp_sync)), + warp_sync_config: Some(WarpSyncConfig::WithProvider(warp_sync)), block_relay: None, metrics, })?; diff --git a/substrate/bin/node/cli/tests/basic.rs b/substrate/bin/node/cli/tests/basic.rs index 0a2e3fd25047..037ddbb1e47b 100644 --- a/substrate/bin/node/cli/tests/basic.rs +++ b/substrate/bin/node/cli/tests/basic.rs @@ -850,7 +850,7 @@ fn should_import_block_with_test_client() { sp_consensus::BlockOrigin, ClientBlockImportExt, TestClientBuilder, TestClientBuilderExt, }; - let mut client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().build(); let block1 = changes_trie_block(); let block_data = block1.0; let block = node_primitives::Block::decode(&mut &block_data[..]).unwrap(); diff --git a/substrate/bin/node/rpc/Cargo.toml b/substrate/bin/node/rpc/Cargo.toml index d85998e3c87b..02f5d9a4a702 100644 --- a/substrate/bin/node/rpc/Cargo.toml +++ b/substrate/bin/node/rpc/Cargo.toml @@ -31,7 +31,6 @@ sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-grandpa-rpc = { workspace = true, default-features = true } sc-mixnet = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } -sc-rpc-api = { workspace = true, default-features = true } sc-sync-state-rpc = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } diff --git a/substrate/bin/node/rpc/src/lib.rs b/substrate/bin/node/rpc/src/lib.rs index c55e03ee9d6f..988502bb2bfd 100644 --- a/substrate/bin/node/rpc/src/lib.rs +++ b/substrate/bin/node/rpc/src/lib.rs @@ -44,7 +44,6 @@ use sc_consensus_grandpa::{ FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState, }; pub use sc_rpc::SubscriptionTaskExecutor; -pub use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_application_crypto::RuntimeAppPublic; @@ -97,8 +96,6 @@ pub struct FullDeps { pub select_chain: SC, /// A copy of the chain spec. pub chain_spec: Box, - /// Whether to deny unsafe calls - pub deny_unsafe: DenyUnsafe, /// BABE specific dependencies. pub babe: BabeDeps, /// GRANDPA specific dependencies. @@ -120,7 +117,6 @@ pub fn create_full( pool, select_chain, chain_spec, - deny_unsafe, babe, grandpa, beefy, @@ -175,7 +171,7 @@ where finality_provider, } = grandpa; - io.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; + io.merge(System::new(client.clone(), pool).into_rpc())?; // Making synchronous calls in light client freezes the browser currently, // more context: https://github.com/paritytech/substrate/pull/3480 // These RPCs should use an asynchronous caller instead. @@ -190,8 +186,7 @@ where )?; io.merge(TransactionPayment::new(client.clone()).into_rpc())?; io.merge( - Babe::new(client.clone(), babe_worker_handle.clone(), keystore, select_chain, deny_unsafe) - .into_rpc(), + Babe::new(client.clone(), babe_worker_handle.clone(), keystore, select_chain).into_rpc(), )?; io.merge( Grandpa::new( @@ -209,10 +204,9 @@ where .into_rpc(), )?; - io.merge(StateMigration::new(client.clone(), backend, deny_unsafe).into_rpc())?; - io.merge(Dev::new(client, deny_unsafe).into_rpc())?; - let statement_store = - sc_rpc::statement::StatementStore::new(statement_store, deny_unsafe).into_rpc(); + io.merge(StateMigration::new(client.clone(), backend).into_rpc())?; + io.merge(Dev::new(client).into_rpc())?; + let statement_store = sc_rpc::statement::StatementStore::new(statement_store).into_rpc(); io.merge(statement_store)?; if let Some(mixnet_api) = mixnet_api { diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 2ad655883916..6310e16d5a14 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -31,7 +31,7 @@ serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } # pallet-asset-conversion: turn on "num-traits" feature primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } -polkadot-sdk = { features = ["runtime", "tuples-96"], workspace = true } +polkadot-sdk = { features = ["runtime-full", "tuples-96"], workspace = true } # shared code between runtime and node node-primitives = { workspace = true } @@ -71,5 +71,5 @@ try-runtime = [ experimental = [ "pallet-example-tasks/experimental", ] - metadata-hash = ["substrate-wasm-builder/metadata-hash"] +riscv = ["polkadot-sdk/riscv"] diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index a94838cf20c0..ef5c52bf6e6e 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1381,6 +1381,34 @@ impl pallet_contracts::Config for Runtime { type Xcm = (); } +impl pallet_revive::Config for Runtime { + type Time = Timestamp; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type CallFilter = Nothing; + type DepositPerItem = DepositPerItem; + type DepositPerByte = DepositPerByte; + type WeightPrice = pallet_transaction_payment::Pallet; + type WeightInfo = pallet_revive::weights::SubstrateWeight; + type ChainExtension = (); + type AddressGenerator = pallet_revive::DefaultAddressGenerator; + type MaxCodeLen = ConstU32<{ 123 * 1024 }>; + type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; + type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; + type UnsafeUnstableInterface = ConstBool; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; + type RuntimeHoldReason = RuntimeHoldReason; + #[cfg(not(feature = "runtime-benchmarks"))] + type Migrations = (); + #[cfg(feature = "runtime-benchmarks")] + type Migrations = pallet_revive::migration::codegen::BenchMigrations; + type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type Debug = (); + type Xcm = (); +} + impl pallet_sudo::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -1612,6 +1640,7 @@ impl pallet_beefy_mmr::Config for Runtime { type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum; type LeafExtra = Vec; type BeefyDataProvider = (); + type WeightInfo = (); } parameter_types! { @@ -2481,6 +2510,9 @@ mod runtime { #[runtime::pallet_index(79)] pub type AssetConversionMigration = pallet_asset_conversion_ops::Pallet; + + #[runtime::pallet_index(80)] + pub type Revive = pallet_revive::Pallet; } /// The address format for describing accounts. @@ -2540,6 +2572,7 @@ type Migrations = ( pallet_nomination_pools::migration::versioned::V6ToV7, pallet_alliance::migration::Migration, pallet_contracts::Migration, + pallet_revive::Migration, pallet_identity::migration::versioned::V0ToV1, ); @@ -2585,12 +2618,14 @@ mod benches { [pallet_babe, Babe] [pallet_bags_list, VoterList] [pallet_balances, Balances] + [pallet_beefy_mmr, MmrLeaf] [pallet_bounties, Bounties] [pallet_broker, Broker] [pallet_child_bounties, ChildBounties] [pallet_collective, Council] [pallet_conviction_voting, ConvictionVoting] [pallet_contracts, Contracts] + [pallet_revive, Revive] [pallet_core_fellowship, CoreFellowship] [tasks_example, TasksExample] [pallet_democracy, Democracy] @@ -2782,6 +2817,14 @@ impl_runtime_apis! { fn member_needs_delegate_migration(member: AccountId) -> bool { NominationPools::api_member_needs_delegate_migration(member) } + + fn member_total_balance(member: AccountId) -> Balance { + NominationPools::api_member_total_balance(member) + } + + fn pool_balance(pool_id: pallet_nomination_pools::PoolId) -> Balance { + NominationPools::api_pool_balance(pool_id) + } } impl pallet_staking_runtime_api::StakingApi for Runtime { @@ -2945,6 +2988,75 @@ impl_runtime_apis! { } } + impl pallet_revive::ReviveApi for Runtime + { + fn call( + origin: AccountId, + dest: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + input_data: Vec, + ) -> pallet_revive::ContractExecResult { + Revive::bare_call( + RuntimeOrigin::signed(origin), + dest, + value, + gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), + storage_deposit_limit.unwrap_or(u128::MAX), + input_data, + pallet_revive::DebugInfo::UnsafeDebug, + pallet_revive::CollectEvents::UnsafeCollect, + ) + } + + fn instantiate( + origin: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + code: pallet_revive::Code, + data: Vec, + salt: Vec, + ) -> pallet_revive::ContractInstantiateResult + { + Revive::bare_instantiate( + RuntimeOrigin::signed(origin), + value, + gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), + storage_deposit_limit.unwrap_or(u128::MAX), + code, + data, + salt, + pallet_revive::DebugInfo::UnsafeDebug, + pallet_revive::CollectEvents::UnsafeCollect, + ) + } + + fn upload_code( + origin: AccountId, + code: Vec, + storage_deposit_limit: Option, + ) -> pallet_revive::CodeUploadResult + { + Revive::bare_upload_code( + RuntimeOrigin::signed(origin), + code, + storage_deposit_limit.unwrap_or(u128::MAX), + ) + } + + fn get_storage( + address: AccountId, + key: Vec, + ) -> pallet_revive::GetStorageResult { + Revive::get_storage( + address, + key + ) + } + } + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< Block, Balance, diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs index 74805488792a..527a3d12d9e7 100644 --- a/substrate/client/basic-authorship/src/basic_authorship.rs +++ b/substrate/client/basic-authorship/src/basic_authorship.rs @@ -852,7 +852,7 @@ mod tests { block }; - let import_and_maintain = |mut client: Arc, block: TestBlock| { + let import_and_maintain = |client: Arc, block: TestBlock| { let hash = block.hash(); block_on(client.import(BlockOrigin::Own, block)).unwrap(); block_on(txpool.maintain(chain_event( diff --git a/substrate/client/chain-spec/src/lib.rs b/substrate/client/chain-spec/src/lib.rs index c43f9e89b8a9..5451428d3481 100644 --- a/substrate/client/chain-spec/src/lib.rs +++ b/substrate/client/chain-spec/src/lib.rs @@ -172,6 +172,12 @@ //! //! //! +//! The main purpose of the `RuntimeGenesisConfig` patch is to: +//! - minimize the maintenance effort when RuntimeGenesisConfig is changed in the future (e.g. new +//! pallets added to the runtime or pallet's genesis config changed), +//! - increase the readability - it only contains the relevant fields, +//! - allow to apply numerous changes in distinct domains (e.g. for zombienet). +//! //! 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. diff --git a/substrate/client/cli/src/arg_enums.rs b/substrate/client/cli/src/arg_enums.rs index b5819d03447a..cd245dc01554 100644 --- a/substrate/client/cli/src/arg_enums.rs +++ b/substrate/client/cli/src/arg_enums.rs @@ -168,6 +168,19 @@ pub enum RpcMethods { Unsafe, } +impl FromStr for RpcMethods { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "safe" => Ok(RpcMethods::Safe), + "unsafe" => Ok(RpcMethods::Unsafe), + "auto" => Ok(RpcMethods::Auto), + invalid => Err(format!("Invalid rpc methods {invalid}")), + } + } +} + impl Into for RpcMethods { fn into(self) -> sc_service::config::RpcMethods { match self { diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs index c1288b502c95..7245b46e2f7d 100644 --- a/substrate/client/cli/src/commands/run_cmd.rs +++ b/substrate/client/cli/src/commands/run_cmd.rs @@ -20,8 +20,8 @@ use crate::{ arg_enums::{Cors, RpcMethods}, error::{Error, Result}, params::{ - ImportParams, KeystoreParams, NetworkParams, OffchainWorkerParams, SharedParams, - TransactionPoolParams, + ImportParams, KeystoreParams, NetworkParams, OffchainWorkerParams, RpcEndpoint, + SharedParams, TransactionPoolParams, }, CliConfiguration, PrometheusParams, RuntimeParams, TelemetryParams, RPC_DEFAULT_MAX_CONNECTIONS, RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB, @@ -37,7 +37,7 @@ use sc_service::{ }; use sc_telemetry::TelemetryEndpoints; use std::{ - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{Ipv4Addr, Ipv6Addr, SocketAddr}, num::NonZeroU32, }; @@ -128,6 +128,47 @@ pub struct RunCmd { #[arg(long, value_name = "PORT")] pub rpc_port: Option, + /// EXPERIMENTAL: Specify the JSON-RPC server interface and this option which can be enabled + /// several times if you want expose several RPC interfaces with different configurations. + /// + /// The format for this option is: + /// `--experimental-rpc-endpoint" listen-addr=,,..."` where each option is + /// separated by a comma and `listen-addr` is the only required param. + /// + /// The following options are available: + /// • listen-addr: The socket address (ip:port) to listen on. Be careful to not expose the + /// server to the public internet unless you know what you're doing. (required) + /// • disable-batch-requests: Disable batch requests (optional) + /// • max-connections: The maximum number of concurrent connections that the server will + /// accept (optional) + /// • max-request-size: The maximum size of a request body in megabytes (optional) + /// • max-response-size: The maximum size of a response body in megabytes (optional) + /// • max-subscriptions-per-connection: The maximum number of subscriptions per connection + /// (optional) + /// • max-buffer-capacity-per-connection: The maximum buffer capacity per connection + /// (optional) + /// • max-batch-request-len: The maximum number of requests in a batch (optional) + /// • cors: The CORS allowed origins, this can enabled more than once (optional) + /// • methods: Which RPC methods to allow, valid values are "safe", "unsafe" and "auto" + /// (optional) + /// • optional: If the listen address is optional i.e the interface is not required to be + /// available For example this may be useful if some platforms doesn't support ipv6 + /// (optional) + /// • rate-limit: The rate limit in calls per minute for each connection (optional) + /// • rate-limit-trust-proxy-headers: Trust proxy headers for disable rate limiting (optional) + /// • rate-limit-whitelisted-ips: Disable rate limiting for certain ip addresses, this can be + /// enabled more than once (optional) • retry-random-port: If the port is already in use, + /// retry with a random port (optional) + /// + /// Use with care, this flag is unstable and subject to change. + #[arg( + long, + num_args = 1.., + verbatim_doc_comment, + conflicts_with_all = &["rpc_external", "unsafe_rpc_external", "rpc_port", "rpc_cors", "rpc_rate_limit_trust_proxy_headers", "rpc_rate_limit", "rpc_rate_limit_whitelisted_ips", "rpc_message_buffer_capacity_per_connection", "rpc_disable_batch_requests", "rpc_max_subscriptions_per_connection", "rpc_max_request_size", "rpc_max_response_size"] + )] + pub experimental_rpc_endpoint: Vec, + /// Maximum number of RPC server connections. #[arg(long, value_name = "COUNT", default_value_t = RPC_DEFAULT_MAX_CONNECTIONS)] pub rpc_max_connections: u32, @@ -410,15 +451,68 @@ impl CliConfiguration for RunCmd { .into()) } - fn rpc_addr(&self, default_listen_port: u16) -> Result> { - let interface = rpc_interface( + fn rpc_addr(&self, default_listen_port: u16) -> Result>> { + if !self.experimental_rpc_endpoint.is_empty() { + for endpoint in &self.experimental_rpc_endpoint { + // Technically, `0.0.0.0` isn't a public IP address, but it's a way to listen on + // all interfaces. Thus, we consider it as a public endpoint and warn about it. + if endpoint.rpc_methods == RpcMethods::Unsafe && endpoint.is_global() || + endpoint.listen_addr.ip().is_unspecified() + { + log::warn!( + "It isn't safe to expose RPC publicly without a proxy server that filters \ + available set of RPC methods." + ); + } + } + + return Ok(Some(self.experimental_rpc_endpoint.clone())); + } + + let (ipv4, ipv6) = rpc_interface( self.rpc_external, self.unsafe_rpc_external, self.rpc_methods, self.validator, )?; - Ok(Some(SocketAddr::new(interface, self.rpc_port.unwrap_or(default_listen_port)))) + let cors = self.rpc_cors(self.is_dev()?)?; + let port = self.rpc_port.unwrap_or(default_listen_port); + + Ok(Some(vec![ + RpcEndpoint { + batch_config: self.rpc_batch_config()?, + max_connections: self.rpc_max_connections, + listen_addr: SocketAddr::new(std::net::IpAddr::V4(ipv4), port), + rpc_methods: self.rpc_methods, + rate_limit: self.rpc_rate_limit, + rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips.clone(), + max_payload_in_mb: self.rpc_max_request_size, + max_payload_out_mb: self.rpc_max_response_size, + max_subscriptions_per_connection: self.rpc_max_subscriptions_per_connection, + max_buffer_capacity_per_connection: self.rpc_message_buffer_capacity_per_connection, + cors: cors.clone(), + retry_random_port: true, + is_optional: false, + }, + RpcEndpoint { + batch_config: self.rpc_batch_config()?, + max_connections: self.rpc_max_connections, + listen_addr: SocketAddr::new(std::net::IpAddr::V6(ipv6), port), + rpc_methods: self.rpc_methods, + rate_limit: self.rpc_rate_limit, + rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips.clone(), + max_payload_in_mb: self.rpc_max_request_size, + max_payload_out_mb: self.rpc_max_response_size, + max_subscriptions_per_connection: self.rpc_max_subscriptions_per_connection, + max_buffer_capacity_per_connection: self.rpc_message_buffer_capacity_per_connection, + cors: cors.clone(), + retry_random_port: true, + is_optional: true, + }, + ])) } fn rpc_methods(&self) -> Result { @@ -523,7 +617,7 @@ fn rpc_interface( is_unsafe_external: bool, rpc_methods: RpcMethods, is_validator: bool, -) -> Result { +) -> Result<(Ipv4Addr, Ipv6Addr)> { if is_external && is_validator && rpc_methods != RpcMethods::Unsafe { return Err(Error::Input( "--rpc-external option shouldn't be used if the node is running as \ @@ -541,9 +635,9 @@ fn rpc_interface( ); } - Ok(Ipv4Addr::UNSPECIFIED.into()) + Ok((Ipv4Addr::UNSPECIFIED, Ipv6Addr::UNSPECIFIED)) } else { - Ok(Ipv4Addr::LOCALHOST.into()) + Ok((Ipv4Addr::LOCALHOST, Ipv6Addr::LOCALHOST)) } } diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index 406d1fb264dd..7c2358477611 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -20,7 +20,8 @@ use crate::{ arg_enums::Database, error::Result, DatabaseParams, ImportParams, KeystoreParams, - NetworkParams, NodeKeyParams, OffchainWorkerParams, PruningParams, SharedParams, SubstrateCli, + NetworkParams, NodeKeyParams, OffchainWorkerParams, PruningParams, RpcEndpoint, SharedParams, + SubstrateCli, }; use log::warn; use names::{Generator, Name}; @@ -34,7 +35,7 @@ use sc_service::{ BlocksPruning, ChainSpec, TracingReceiver, }; use sc_tracing::logging::LoggerBuilder; -use std::{net::SocketAddr, num::NonZeroU32, path::PathBuf}; +use std::{num::NonZeroU32, path::PathBuf}; /// The maximum number of characters for a node name. pub(crate) const NODE_NAME_MAX_LENGTH: usize = 64; @@ -301,7 +302,7 @@ pub trait CliConfiguration: Sized { } /// Get the RPC address. - fn rpc_addr(&self, _default_listen_port: u16) -> Result> { + fn rpc_addr(&self, _default_listen_port: u16) -> Result>> { Ok(None) } @@ -504,6 +505,10 @@ pub trait CliConfiguration: Sized { let telemetry_endpoints = self.telemetry_endpoints(&chain_spec)?; let runtime_cache_size = self.runtime_cache_size()?; + let rpc_addrs: Option> = self + .rpc_addr(DCV::rpc_listen_port())? + .map(|addrs| addrs.into_iter().map(Into::into).collect()); + Ok(Configuration { impl_name: C::impl_name(), impl_version: C::impl_version(), @@ -527,7 +532,7 @@ pub trait CliConfiguration: Sized { blocks_pruning: self.blocks_pruning()?, wasm_method: self.wasm_method()?, wasm_runtime_overrides: self.wasm_runtime_overrides(), - rpc_addr: self.rpc_addr(DCV::rpc_listen_port())?, + rpc_addr: rpc_addrs, rpc_methods: self.rpc_methods()?, rpc_max_connections: self.rpc_max_connections()?, rpc_cors: self.rpc_cors(is_dev)?, diff --git a/substrate/client/cli/src/params/mod.rs b/substrate/client/cli/src/params/mod.rs index f07223ec6a73..977b57ba0658 100644 --- a/substrate/client/cli/src/params/mod.rs +++ b/substrate/client/cli/src/params/mod.rs @@ -25,6 +25,7 @@ mod node_key_params; mod offchain_worker_params; mod prometheus_params; mod pruning_params; +mod rpc_params; mod runtime_params; mod shared_params; mod telemetry_params; @@ -32,6 +33,7 @@ mod transaction_pool_params; use crate::arg_enums::{CryptoScheme, OutputType}; use clap::Args; +use sc_service::config::{IpNetwork, RpcBatchRequestConfig}; use sp_core::crypto::{Ss58AddressFormat, Ss58AddressFormatRegistry}; use sp_runtime::{ generic::BlockId, @@ -42,7 +44,7 @@ use std::{fmt::Debug, str::FromStr}; pub use crate::params::{ database_params::*, import_params::*, keystore_params::*, message_params::*, mixnet_params::*, network_params::*, node_key_params::*, offchain_worker_params::*, prometheus_params::*, - pruning_params::*, runtime_params::*, shared_params::*, telemetry_params::*, + pruning_params::*, rpc_params::*, runtime_params::*, shared_params::*, telemetry_params::*, transaction_pool_params::*, }; diff --git a/substrate/client/cli/src/params/rpc_params.rs b/substrate/client/cli/src/params/rpc_params.rs new file mode 100644 index 000000000000..d0ec1fd00443 --- /dev/null +++ b/substrate/client/cli/src/params/rpc_params.rs @@ -0,0 +1,395 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{ + arg_enums::RpcMethods, + params::{IpNetwork, RpcBatchRequestConfig}, + RPC_DEFAULT_MAX_CONNECTIONS, RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB, + RPC_DEFAULT_MAX_SUBS_PER_CONN, RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN, +}; +use std::{net::SocketAddr, num::NonZeroU32}; + +const RPC_LISTEN_ADDR: &str = "listen-addr"; +const RPC_CORS: &str = "cors"; +const RPC_MAX_CONNS: &str = "max-connections"; +const RPC_MAX_REQUEST_SIZE: &str = "max-request-size"; +const RPC_MAX_RESPONSE_SIZE: &str = "max-response-size"; +const RPC_MAX_SUBS_PER_CONN: &str = "max-subscriptions-per-connection"; +const RPC_MAX_BUF_CAP_PER_CONN: &str = "max-buffer-capacity-per-connection"; +const RPC_RATE_LIMIT: &str = "rate-limit"; +const RPC_RATE_LIMIT_TRUST_PROXY_HEADERS: &str = "rate-limit-trust-proxy-headers"; +const RPC_RATE_LIMIT_WHITELISTED_IPS: &str = "rate-limit-whitelisted-ips"; +const RPC_RETRY_RANDOM_PORT: &str = "retry-random-port"; +const RPC_METHODS: &str = "methods"; +const RPC_OPTIONAL: &str = "optional"; +const RPC_DISABLE_BATCH: &str = "disable-batch-requests"; +const RPC_BATCH_LIMIT: &str = "max-batch-request-len"; + +/// Represent a single RPC endpoint with its configuration. +#[derive(Debug, Clone)] +pub struct RpcEndpoint { + /// Listen address. + pub listen_addr: SocketAddr, + /// Batch request configuration. + pub batch_config: RpcBatchRequestConfig, + /// Maximum number of connections. + pub max_connections: u32, + /// Maximum inbound payload size in MB. + pub max_payload_in_mb: u32, + /// Maximum outbound payload size in MB. + pub max_payload_out_mb: u32, + /// Maximum number of subscriptions per connection. + pub max_subscriptions_per_connection: u32, + /// Maximum buffer capacity per connection. + pub max_buffer_capacity_per_connection: u32, + /// Rate limit per minute. + pub rate_limit: Option, + /// Whether to trust proxy headers for rate limiting. + pub rate_limit_trust_proxy_headers: bool, + /// Whitelisted IPs for rate limiting. + pub rate_limit_whitelisted_ips: Vec, + /// CORS. + pub cors: Option>, + /// RPC methods to expose. + pub rpc_methods: RpcMethods, + /// Whether it's an optional listening address i.e, it's ignored if it fails to bind. + /// For example substrate tries to bind both ipv4 and ipv6 addresses but some platforms + /// may not support ipv6. + pub is_optional: bool, + /// Whether to retry with a random port if the provided port is already in use. + pub retry_random_port: bool, +} + +impl std::str::FromStr for RpcEndpoint { + type Err = String; + + fn from_str(s: &str) -> Result { + let mut listen_addr = None; + let mut max_connections = None; + let mut max_payload_in_mb = None; + let mut max_payload_out_mb = None; + let mut max_subscriptions_per_connection = None; + let mut max_buffer_capacity_per_connection = None; + let mut cors: Option> = None; + let mut rpc_methods = None; + let mut is_optional = None; + let mut disable_batch_requests = None; + let mut max_batch_request_len = None; + let mut rate_limit = None; + let mut rate_limit_trust_proxy_headers = None; + let mut rate_limit_whitelisted_ips = Vec::new(); + let mut retry_random_port = None; + + for input in s.split(',') { + let (key, val) = input.trim().split_once('=').ok_or_else(|| invalid_input(input))?; + let key = key.trim(); + let val = val.trim(); + + match key { + RPC_LISTEN_ADDR => { + if listen_addr.is_some() { + return Err(only_once_err(RPC_LISTEN_ADDR)); + } + let val: SocketAddr = + val.parse().map_err(|_| invalid_value(RPC_LISTEN_ADDR, &val))?; + listen_addr = Some(val); + }, + RPC_CORS => { + if val.is_empty() { + return Err(invalid_value(RPC_CORS, &val)); + } + + if let Some(cors) = cors.as_mut() { + cors.push(val.to_string()); + } else { + cors = Some(vec![val.to_string()]); + } + }, + RPC_MAX_CONNS => { + if max_connections.is_some() { + return Err(only_once_err(RPC_MAX_CONNS)); + } + + let val = val.parse().map_err(|_| invalid_value(RPC_MAX_CONNS, &val))?; + max_connections = Some(val); + }, + RPC_MAX_REQUEST_SIZE => { + if max_payload_in_mb.is_some() { + return Err(only_once_err(RPC_MAX_REQUEST_SIZE)); + } + + let val = + val.parse().map_err(|_| invalid_value(RPC_MAX_RESPONSE_SIZE, &val))?; + max_payload_in_mb = Some(val); + }, + RPC_MAX_RESPONSE_SIZE => { + if max_payload_out_mb.is_some() { + return Err(only_once_err(RPC_MAX_RESPONSE_SIZE)); + } + + let val = + val.parse().map_err(|_| invalid_value(RPC_MAX_RESPONSE_SIZE, &val))?; + max_payload_out_mb = Some(val); + }, + RPC_MAX_SUBS_PER_CONN => { + if max_subscriptions_per_connection.is_some() { + return Err(only_once_err(RPC_MAX_SUBS_PER_CONN)); + } + + let val = + val.parse().map_err(|_| invalid_value(RPC_MAX_SUBS_PER_CONN, &val))?; + max_subscriptions_per_connection = Some(val); + }, + RPC_MAX_BUF_CAP_PER_CONN => { + if max_buffer_capacity_per_connection.is_some() { + return Err(only_once_err(RPC_MAX_BUF_CAP_PER_CONN)); + } + + let val = + val.parse().map_err(|_| invalid_value(RPC_MAX_BUF_CAP_PER_CONN, &val))?; + max_buffer_capacity_per_connection = Some(val); + }, + RPC_RATE_LIMIT => { + if rate_limit.is_some() { + return Err(only_once_err("rate-limit")); + } + + let val = val.parse().map_err(|_| invalid_value(RPC_RATE_LIMIT, &val))?; + rate_limit = Some(val); + }, + RPC_RATE_LIMIT_TRUST_PROXY_HEADERS => { + if rate_limit_trust_proxy_headers.is_some() { + return Err(only_once_err(RPC_RATE_LIMIT_TRUST_PROXY_HEADERS)); + } + + let val = val + .parse() + .map_err(|_| invalid_value(RPC_RATE_LIMIT_TRUST_PROXY_HEADERS, &val))?; + rate_limit_trust_proxy_headers = Some(val); + }, + RPC_RATE_LIMIT_WHITELISTED_IPS => { + let ip: IpNetwork = val + .parse() + .map_err(|_| invalid_value(RPC_RATE_LIMIT_WHITELISTED_IPS, &val))?; + rate_limit_whitelisted_ips.push(ip); + }, + RPC_RETRY_RANDOM_PORT => { + if retry_random_port.is_some() { + return Err(only_once_err(RPC_RETRY_RANDOM_PORT)); + } + let val = + val.parse().map_err(|_| invalid_value(RPC_RETRY_RANDOM_PORT, &val))?; + retry_random_port = Some(val); + }, + RPC_METHODS => { + if rpc_methods.is_some() { + return Err(only_once_err("methods")); + } + let val = val.parse().map_err(|_| invalid_value(RPC_METHODS, &val))?; + rpc_methods = Some(val); + }, + RPC_OPTIONAL => { + if is_optional.is_some() { + return Err(only_once_err(RPC_OPTIONAL)); + } + + let val = val.parse().map_err(|_| invalid_value(RPC_OPTIONAL, &val))?; + is_optional = Some(val); + }, + RPC_DISABLE_BATCH => { + if disable_batch_requests.is_some() { + return Err(only_once_err(RPC_DISABLE_BATCH)); + } + + let val = val.parse().map_err(|_| invalid_value(RPC_DISABLE_BATCH, &val))?; + disable_batch_requests = Some(val); + }, + RPC_BATCH_LIMIT => { + if max_batch_request_len.is_some() { + return Err(only_once_err(RPC_BATCH_LIMIT)); + } + + let val = val.parse().map_err(|_| invalid_value(RPC_BATCH_LIMIT, &val))?; + max_batch_request_len = Some(val); + }, + _ => return Err(invalid_key(key)), + } + } + + let listen_addr = listen_addr.ok_or("`listen-addr` must be specified exactly once")?; + + let batch_config = match (disable_batch_requests, max_batch_request_len) { + (Some(true), Some(_)) => { + return Err(format!("`{RPC_BATCH_LIMIT}` and `{RPC_DISABLE_BATCH}` are mutually exclusive and can't be used together")); + }, + (Some(false), None) => RpcBatchRequestConfig::Disabled, + (None, Some(len)) => RpcBatchRequestConfig::Limit(len), + _ => RpcBatchRequestConfig::Unlimited, + }; + + Ok(Self { + listen_addr, + batch_config, + max_connections: max_connections.unwrap_or(RPC_DEFAULT_MAX_CONNECTIONS), + max_payload_in_mb: max_payload_in_mb.unwrap_or(RPC_DEFAULT_MAX_REQUEST_SIZE_MB), + max_payload_out_mb: max_payload_out_mb.unwrap_or(RPC_DEFAULT_MAX_RESPONSE_SIZE_MB), + cors, + max_buffer_capacity_per_connection: max_buffer_capacity_per_connection + .unwrap_or(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN), + max_subscriptions_per_connection: max_subscriptions_per_connection + .unwrap_or(RPC_DEFAULT_MAX_SUBS_PER_CONN), + rpc_methods: rpc_methods.unwrap_or(RpcMethods::Auto), + rate_limit, + rate_limit_trust_proxy_headers: rate_limit_trust_proxy_headers.unwrap_or(false), + rate_limit_whitelisted_ips, + is_optional: is_optional.unwrap_or(false), + retry_random_port: retry_random_port.unwrap_or(false), + }) + } +} + +impl Into for RpcEndpoint { + fn into(self) -> sc_service::config::RpcEndpoint { + sc_service::config::RpcEndpoint { + batch_config: self.batch_config, + listen_addr: self.listen_addr, + max_buffer_capacity_per_connection: self.max_buffer_capacity_per_connection, + max_connections: self.max_connections, + max_payload_in_mb: self.max_payload_in_mb, + max_payload_out_mb: self.max_payload_out_mb, + max_subscriptions_per_connection: self.max_subscriptions_per_connection, + rpc_methods: self.rpc_methods.into(), + rate_limit: self.rate_limit, + rate_limit_trust_proxy_headers: self.rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: self.rate_limit_whitelisted_ips, + cors: self.cors, + retry_random_port: self.retry_random_port, + is_optional: self.is_optional, + } + } +} + +impl RpcEndpoint { + /// Returns whether the endpoint is globally exposed. + pub fn is_global(&self) -> bool { + let ip = IpNetwork::from(self.listen_addr.ip()); + ip.is_global() + } +} + +fn only_once_err(reason: &str) -> String { + format!("`{reason}` is only allowed be specified once") +} + +fn invalid_input(input: &str) -> String { + format!("`{input}`, expects: `key=value`") +} + +fn invalid_value(key: &str, value: &str) -> String { + format!("value=`{value}` key=`{key}`") +} + +fn invalid_key(key: &str) -> String { + format!("unknown key=`{key}`, see `--help` for available options") +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{num::NonZeroU32, str::FromStr}; + + #[test] + fn parse_rpc_endpoint_works() { + assert!(RpcEndpoint::from_str("listen-addr=127.0.0.1:9944").is_ok()); + assert!(RpcEndpoint::from_str("listen-addr=[::1]:9944").is_ok()); + assert!(RpcEndpoint::from_str("listen-addr=127.0.0.1:9944,methods=auto").is_ok()); + assert!(RpcEndpoint::from_str("listen-addr=[::1]:9944,methods=auto").is_ok()); + assert!(RpcEndpoint::from_str( + "listen-addr=127.0.0.1:9944,methods=auto,cors=*,optional=true" + ) + .is_ok()); + + assert!(RpcEndpoint::from_str("listen-addrs=127.0.0.1:9944,foo=*").is_err()); + assert!(RpcEndpoint::from_str("listen-addrs=127.0.0.1:9944,cors=").is_err()); + } + + #[test] + fn parse_rpc_endpoint_all() { + let endpoint = RpcEndpoint::from_str( + "listen-addr=127.0.0.1:9944,methods=unsafe,cors=*,optional=true,retry-random-port=true,rate-limit=99,\ + max-batch-request-len=100,rate-limit-trust-proxy-headers=true,max-connections=33,max-request-size=4,\ + max-response-size=3,max-subscriptions-per-connection=7,max-buffer-capacity-per-connection=8,\ + rate-limit-whitelisted-ips=192.168.1.0/24,rate-limit-whitelisted-ips=ff01::0/32" + ).unwrap(); + assert_eq!(endpoint.listen_addr, ([127, 0, 0, 1], 9944).into()); + assert_eq!(endpoint.rpc_methods, RpcMethods::Unsafe); + assert_eq!(endpoint.cors, Some(vec!["*".to_string()])); + assert_eq!(endpoint.is_optional, true); + assert_eq!(endpoint.retry_random_port, true); + assert_eq!(endpoint.rate_limit, Some(NonZeroU32::new(99).unwrap())); + assert!(matches!(endpoint.batch_config, RpcBatchRequestConfig::Limit(l) if l == 100)); + assert_eq!(endpoint.rate_limit_trust_proxy_headers, true); + assert_eq!( + endpoint.rate_limit_whitelisted_ips, + vec![ + IpNetwork::V4("192.168.1.0/24".parse().unwrap()), + IpNetwork::V6("ff01::0/32".parse().unwrap()) + ] + ); + assert_eq!(endpoint.max_connections, 33); + assert_eq!(endpoint.max_payload_in_mb, 4); + assert_eq!(endpoint.max_payload_out_mb, 3); + assert_eq!(endpoint.max_subscriptions_per_connection, 7); + assert_eq!(endpoint.max_buffer_capacity_per_connection, 8); + } + + #[test] + fn parse_rpc_endpoint_multiple_cors() { + let addr = RpcEndpoint::from_str( + "listen-addr=127.0.0.1:9944,methods=auto,cors=https://polkadot.js.org,cors=*,cors=localhost:*", + ) + .unwrap(); + + assert_eq!( + addr.cors, + Some(vec![ + "https://polkadot.js.org".to_string(), + "*".to_string(), + "localhost:*".to_string() + ]) + ); + } + + #[test] + fn parse_rpc_endpoint_whitespaces() { + let addr = RpcEndpoint::from_str( + " listen-addr = 127.0.0.1:9944, methods = auto, optional = true ", + ) + .unwrap(); + assert_eq!(addr.rpc_methods, RpcMethods::Auto); + assert_eq!(addr.is_optional, true); + } + + #[test] + fn parse_rpc_endpoint_batch_options_mutually_exclusive() { + assert!(RpcEndpoint::from_str( + "listen-addr = 127.0.0.1:9944,disable-batch-requests=true,max-batch-request-len=100", + ) + .is_err()); + } +} diff --git a/substrate/client/consensus/babe/rpc/src/lib.rs b/substrate/client/consensus/babe/rpc/src/lib.rs index a3e811baecff..338d71a43256 100644 --- a/substrate/client/consensus/babe/rpc/src/lib.rs +++ b/substrate/client/consensus/babe/rpc/src/lib.rs @@ -25,12 +25,13 @@ use jsonrpsee::{ core::async_trait, proc_macros::rpc, types::{ErrorObject, ErrorObjectOwned}, + Extensions, }; use serde::{Deserialize, Serialize}; use sc_consensus_babe::{authorship, BabeWorkerHandle}; use sc_consensus_epochs::Epoch as EpochT; -use sc_rpc_api::{DenyUnsafe, UnsafeRpcError}; +use sc_rpc_api::{check_if_safe, UnsafeRpcError}; use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppCrypto; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; @@ -47,7 +48,7 @@ const BABE_ERROR: i32 = 9000; 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")] + #[method(name = "babe_epochAuthorship", with_extensions)] async fn epoch_authorship(&self) -> Result, Error>; } @@ -61,8 +62,6 @@ pub struct Babe { keystore: KeystorePtr, /// The SelectChain strategy select_chain: SC, - /// Whether to deny unsafe calls - deny_unsafe: DenyUnsafe, } impl Babe { @@ -72,9 +71,8 @@ impl Babe { babe_worker_handle: BabeWorkerHandle, keystore: KeystorePtr, select_chain: SC, - deny_unsafe: DenyUnsafe, ) -> Self { - Self { client, babe_worker_handle, keystore, select_chain, deny_unsafe } + Self { client, babe_worker_handle, keystore, select_chain } } } @@ -89,8 +87,11 @@ where C::Api: BabeRuntimeApi, SC: SelectChain + Clone + 'static, { - async fn epoch_authorship(&self) -> Result, Error> { - self.deny_unsafe.check_if_safe()?; + async fn epoch_authorship( + &self, + ext: &Extensions, + ) -> Result, Error> { + check_if_safe(ext)?; let best_header = self.select_chain.best_chain().map_err(Error::SelectChain).await?; @@ -193,6 +194,7 @@ impl From for ErrorObjectOwned { mod tests { use super::*; use sc_consensus_babe::ImportQueueParams; + use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{OffchainTransactionPoolFactory, RejectAllTxPool}; use sp_consensus_babe::inherents::InherentDataProvider; use sp_core::{crypto::key_types::BABE, testing::TaskExecutor}; @@ -211,9 +213,8 @@ mod tests { keystore.into() } - fn test_babe_rpc_module( - deny_unsafe: DenyUnsafe, - ) -> Babe> { + fn test_babe_rpc_module() -> Babe> + { let builder = TestClientBuilder::new(); let (client, longest_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); @@ -248,29 +249,31 @@ mod tests { }) .unwrap(); - Babe::new(client.clone(), babe_worker_handle, keystore, longest_chain, deny_unsafe) + Babe::new(client.clone(), babe_worker_handle, keystore, longest_chain) } #[tokio::test] async fn epoch_authorship_works() { - let babe_rpc = test_babe_rpc_module(DenyUnsafe::No); - let api = babe_rpc.into_rpc(); + let babe_rpc = test_babe_rpc_module(); + let mut api = babe_rpc.into_rpc(); + api.extensions_mut().insert(DenyUnsafe::No); - let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","id":1,"method":"babe_epochAuthorship","params":[]}"#; 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}"#; + let expected = r#"{"jsonrpc":"2.0","id":1,"result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[],"secondary_vrf":[1,2,4]}}}"#; assert_eq!(response, expected); } #[tokio::test] async fn epoch_authorship_is_unsafe() { - let babe_rpc = test_babe_rpc_module(DenyUnsafe::Yes); - let api = babe_rpc.into_rpc(); + let babe_rpc = test_babe_rpc_module(); + let mut api = babe_rpc.into_rpc(); + api.extensions_mut().insert(DenyUnsafe::Yes); let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params":[],"id":1}"#; let (response, _) = api.raw_json_request(request, 1).await.unwrap(); - let expected = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"#; + let expected = r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"RPC call is unsafe to be called externally"}}"#; assert_eq!(response, expected); } diff --git a/substrate/client/consensus/babe/src/lib.rs b/substrate/client/consensus/babe/src/lib.rs index 0c1eb8875864..9770b16871e1 100644 --- a/substrate/client/consensus/babe/src/lib.rs +++ b/substrate/client/consensus/babe/src/lib.rs @@ -1342,7 +1342,7 @@ where // This function makes multiple transactions to the DB. If one of them fails we may // end up in an inconsistent state and have to resync. async fn import_state( - &mut self, + &self, mut block: BlockImportParams, ) -> Result { let hash = block.post_hash(); @@ -1405,7 +1405,7 @@ where type Error = ConsensusError; async fn import_block( - &mut self, + &self, mut block: BlockImportParams, ) -> Result { let hash = block.post_hash(); diff --git a/substrate/client/consensus/babe/src/tests.rs b/substrate/client/consensus/babe/src/tests.rs index 6f805188b9a4..5c2e0eae959c 100644 --- a/substrate/client/consensus/babe/src/tests.rs +++ b/substrate/client/consensus/babe/src/tests.rs @@ -150,7 +150,7 @@ where type Error = BI::Error; async fn import_block( - &mut self, + &self, block: BlockImportParams, ) -> Result { Ok(self.0.import_block(block).await.expect("importing block failed")) diff --git a/substrate/client/consensus/beefy/README.md b/substrate/client/consensus/beefy/README.md index a7956cfcd42e..cb9a9267f77e 100644 --- a/substrate/client/consensus/beefy/README.md +++ b/substrate/client/consensus/beefy/README.md @@ -159,7 +159,7 @@ ambiguity despite using block number instead of a hash. A collection of **votes* a Commitment and a collection of signatures is going to be called **Signed Commitment**. A valid (see later for the rules) Signed Commitment is also called a **BEEFY Justification** or **BEEFY Finality Proof**. For more details on the actual data structures please see -[BEEFY primitives definitions](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/primitives/beefy/src). +[BEEFY primitives definitions](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/primitives/consensus/beefy/src). A **round** is an attempt by BEEFY validators to produce a BEEFY Justification. **Round number** is simply defined as a block number the validators are voting for, or to be more precise, the diff --git a/substrate/client/consensus/beefy/rpc/src/lib.rs b/substrate/client/consensus/beefy/rpc/src/lib.rs index 83477d223dd2..ab58f6866275 100644 --- a/substrate/client/consensus/beefy/rpc/src/lib.rs +++ b/substrate/client/consensus/beefy/rpc/src/lib.rs @@ -201,7 +201,7 @@ 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}"#; + let expected_response = r#"{"jsonrpc":"2.0","id":1,"error":{"code":1,"message":"BEEFY RPC endpoint not ready"}}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); assert_eq!(expected_response, response); @@ -220,13 +220,13 @@ mod tests { let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; let expected = "{\ \"jsonrpc\":\"2.0\",\ - \"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\",\ - \"id\":1\ + \"id\":1,\ + \"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\"\ }"; - let not_ready = "{\ + let not_ready: &str = "{\ \"jsonrpc\":\"2.0\",\ - \"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"},\ - \"id\":1\ + \"id\":1,\ + \"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"}\ }"; let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); @@ -262,7 +262,7 @@ mod tests { ) .await .unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; + let expected = r#"{"jsonrpc":"2.0","id":1,"result":false}"#; assert_eq!(response, expected); } diff --git a/substrate/client/consensus/beefy/src/import.rs b/substrate/client/consensus/beefy/src/import.rs index 848026852933..17a4a5866636 100644 --- a/substrate/client/consensus/beefy/src/import.rs +++ b/substrate/client/consensus/beefy/src/import.rs @@ -132,7 +132,7 @@ where type Error = ConsensusError; async fn import_block( - &mut self, + &self, mut block: BlockImportParams, ) -> Result { let hash = block.post_hash(); diff --git a/substrate/client/consensus/beefy/src/tests.rs b/substrate/client/consensus/beefy/src/tests.rs index 4b1dd447320b..afa6191d8bfe 100644 --- a/substrate/client/consensus/beefy/src/tests.rs +++ b/substrate/client/consensus/beefy/src/tests.rs @@ -790,7 +790,7 @@ async fn beefy_importing_justifications() { let client = net.peer(0).client().clone(); let full_client = client.as_client(); - let (mut block_import, _, peer_data) = net.make_block_import(client.clone()); + let (block_import, _, peer_data) = net.make_block_import(client.clone()); let PeerData { beefy_voter_links, .. } = peer_data; let justif_stream = beefy_voter_links.lock().take().unwrap().from_block_import_justif_stream; let mut justif_recv = justif_stream.subscribe(100_000); diff --git a/substrate/client/consensus/common/src/block_import.rs b/substrate/client/consensus/common/src/block_import.rs index c5adbb5a5fca..4d7b89f37d86 100644 --- a/substrate/client/consensus/common/src/block_import.rs +++ b/substrate/client/consensus/common/src/block_import.rs @@ -310,10 +310,7 @@ pub trait BlockImport { async fn check_block(&self, block: BlockCheckParams) -> Result; /// Import a block. - async fn import_block( - &mut self, - block: BlockImportParams, - ) -> Result; + async fn import_block(&self, block: BlockImportParams) -> Result; } #[async_trait::async_trait] @@ -326,10 +323,7 @@ impl BlockImport for crate::import_queue::BoxBlockImport { } /// Import a block. - async fn import_block( - &mut self, - block: BlockImportParams, - ) -> Result { + async fn import_block(&self, block: BlockImportParams) -> Result { (**self).import_block(block).await } } @@ -346,10 +340,7 @@ where (&**self).check_block(block).await } - async fn import_block( - &mut self, - block: BlockImportParams, - ) -> Result { + async fn import_block(&self, block: BlockImportParams) -> Result { (&**self).import_block(block).await } } diff --git a/substrate/client/consensus/common/src/import_queue.rs b/substrate/client/consensus/common/src/import_queue.rs index 35fc8ad4a402..1baa67398a49 100644 --- a/substrate/client/consensus/common/src/import_queue.rs +++ b/substrate/client/consensus/common/src/import_queue.rs @@ -225,7 +225,7 @@ pub async fn import_single_block>( import_handle: &mut impl BlockImport, block_origin: BlockOrigin, block: IncomingBlock, - verifier: &mut V, + verifier: &V, ) -> BlockImportResult { match verify_single_block_metered(import_handle, block_origin, block, verifier, None).await? { SingleBlockVerificationOutcome::Imported(import_status) => Ok(import_status), @@ -295,7 +295,7 @@ pub(crate) async fn verify_single_block_metered>( import_handle: &impl BlockImport, block_origin: BlockOrigin, block: IncomingBlock, - verifier: &mut V, + verifier: &V, metrics: Option<&Metrics>, ) -> Result, BlockImportError> { let peer = block.origin; diff --git a/substrate/client/consensus/common/src/import_queue/basic_queue.rs b/substrate/client/consensus/common/src/import_queue/basic_queue.rs index 05f2b2527961..7b371145e2e7 100644 --- a/substrate/client/consensus/common/src/import_queue/basic_queue.rs +++ b/substrate/client/consensus/common/src/import_queue/basic_queue.rs @@ -222,7 +222,7 @@ mod worker_messages { /// Returns when `block_import` ended. async fn block_import_process( mut block_import: BoxBlockImport, - mut verifier: impl Verifier, + verifier: impl Verifier, mut result_sender: BufferedLinkSender, mut block_import_receiver: TracingUnboundedReceiver>, metrics: Option, @@ -241,8 +241,7 @@ async fn block_import_process( }; let res = - import_many_blocks(&mut block_import, origin, blocks, &mut verifier, metrics.clone()) - .await; + import_many_blocks(&mut block_import, origin, blocks, &verifier, metrics.clone()).await; result_sender.blocks_processed(res.imported, res.block_count, res.results); } @@ -388,7 +387,7 @@ async fn import_many_blocks>( import_handle: &mut BoxBlockImport, blocks_origin: BlockOrigin, blocks: Vec>, - verifier: &mut V, + verifier: &V, metrics: Option, ) -> ImportManyBlocksResult { let count = blocks.len(); @@ -526,7 +525,7 @@ mod tests { } async fn import_block( - &mut self, + &self, _block: BlockImportParams, ) -> Result { Ok(ImportResult::imported(true)) diff --git a/substrate/client/consensus/grandpa/rpc/src/lib.rs b/substrate/client/consensus/grandpa/rpc/src/lib.rs index a41b14299089..99f98b81261a 100644 --- a/substrate/client/consensus/grandpa/rpc/src/lib.rs +++ b/substrate/client/consensus/grandpa/rpc/src/lib.rs @@ -275,7 +275,7 @@ mod tests { #[tokio::test] async fn uninitialized_rpc_handler() { 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 expected_response = r#"{"jsonrpc":"2.0","id":0,"error":{"code":1,"message":"GRANDPA RPC endpoint not ready"}}"#.to_string(); let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); @@ -285,7 +285,7 @@ mod tests { #[tokio::test] async fn working_rpc_handler() { let (rpc, _) = setup_io_handler(TestVoterState); - let expected_response = "{\"jsonrpc\":\"2.0\",\"result\":{\ + let expected_response = "{\"jsonrpc\":\"2.0\",\"id\":0,\"result\":{\ \"setId\":1,\ \"best\":{\ \"round\":2,\"totalWeight\":100,\"thresholdWeight\":67,\ @@ -297,7 +297,7 @@ mod tests { \"prevotes\":{\"currentWeight\":100,\"missing\":[]},\ \"precommits\":{\"currentWeight\":100,\"missing\":[]}\ }]\ - },\"id\":0}".to_string(); + }}".to_string(); let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); @@ -321,7 +321,7 @@ mod tests { ) .await .unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; + let expected = r#"{"jsonrpc":"2.0","id":1,"result":false}"#; assert_eq!(response, expected); } diff --git a/substrate/client/consensus/grandpa/src/finality_proof.rs b/substrate/client/consensus/grandpa/src/finality_proof.rs index af965f2e4ae6..8a47d121e869 100644 --- a/substrate/client/consensus/grandpa/src/finality_proof.rs +++ b/substrate/client/consensus/grandpa/src/finality_proof.rs @@ -319,7 +319,7 @@ mod tests { ) -> (Arc, Arc, Vec) { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let mut blocks = Vec::new(); for _ in 0..number_of_blocks { diff --git a/substrate/client/consensus/grandpa/src/import.rs b/substrate/client/consensus/grandpa/src/import.rs index 8b7b02f180ec..5cec5204c739 100644 --- a/substrate/client/consensus/grandpa/src/import.rs +++ b/substrate/client/consensus/grandpa/src/import.rs @@ -20,6 +20,7 @@ use std::{collections::HashMap, marker::PhantomData, sync::Arc}; use codec::Decode; use log::debug; +use parking_lot::Mutex; use sc_client_api::{backend::Backend, utils::is_descendent_of}; use sc_consensus::{ @@ -62,7 +63,8 @@ pub struct GrandpaBlockImport { select_chain: SC, authority_set: SharedAuthoritySet>, send_voter_commands: TracingUnboundedSender>>, - authority_set_hard_forks: HashMap>>, + authority_set_hard_forks: + Mutex>>>, justification_sender: GrandpaJustificationSender, telemetry: Option, _phantom: PhantomData, @@ -78,7 +80,7 @@ impl Clone select_chain: self.select_chain.clone(), authority_set: self.authority_set.clone(), send_voter_commands: self.send_voter_commands.clone(), - authority_set_hard_forks: self.authority_set_hard_forks.clone(), + authority_set_hard_forks: Mutex::new(self.authority_set_hard_forks.lock().clone()), justification_sender: self.justification_sender.clone(), telemetry: self.telemetry.clone(), _phantom: PhantomData, @@ -242,7 +244,7 @@ where hash: Block::Hash, ) -> Option>> { // check for forced authority set hard forks - if let Some(change) = self.authority_set_hard_forks.get(&hash) { + if let Some(change) = self.authority_set_hard_forks.lock().get(&hash) { return Some(change.clone()) } @@ -461,7 +463,7 @@ where /// Import whole new state and reset authority set. async fn import_state( - &mut self, + &self, mut block: BlockImportParams, ) -> Result { let hash = block.post_hash(); @@ -474,7 +476,7 @@ where // We've just imported a new state. We trust the sync module has verified // finality proofs and that the state is correct and final. // So we can read the authority list and set id from the state. - self.authority_set_hard_forks.clear(); + self.authority_set_hard_forks.lock().clear(); let authorities = self .inner .runtime_api() @@ -523,7 +525,7 @@ where type Error = ConsensusError; async fn import_block( - &mut self, + &self, mut block: BlockImportParams, ) -> Result { let hash = block.post_hash(); @@ -750,7 +752,7 @@ impl GrandpaBlockImport, justification: Justification, diff --git a/substrate/client/consensus/grandpa/src/tests.rs b/substrate/client/consensus/grandpa/src/tests.rs index 2aa1b5f6ee1b..9cca28a39599 100644 --- a/substrate/client/consensus/grandpa/src/tests.rs +++ b/substrate/client/consensus/grandpa/src/tests.rs @@ -906,7 +906,7 @@ async fn allows_reimporting_change_blocks() { let mut net = GrandpaTestNet::new(api.clone(), 3, 0); let client = net.peer(0).client().clone(); - let (mut block_import, ..) = net.make_block_import(client.clone()); + let (block_import, ..) = net.make_block_import(client.clone()); let full_client = client.as_client(); let mut builder = BlockBuilderBuilder::new(&*full_client) @@ -954,7 +954,7 @@ async fn test_bad_justification() { let mut net = GrandpaTestNet::new(api.clone(), 3, 0); let client = net.peer(0).client().clone(); - let (mut block_import, ..) = net.make_block_import(client.clone()); + let (block_import, ..) = net.make_block_import(client.clone()); let full_client = client.as_client(); let mut builder = BlockBuilderBuilder::new(&*full_client) @@ -2083,7 +2083,7 @@ async fn imports_justification_for_regular_blocks_on_import() { let mut net = GrandpaTestNet::new(api.clone(), 1, 0); let client = net.peer(0).client().clone(); - let (mut block_import, ..) = net.make_block_import(client.clone()); + let (block_import, ..) = net.make_block_import(client.clone()); let full_client = client.as_client(); // create a new block (without importing it) @@ -2122,7 +2122,7 @@ async fn imports_justification_for_regular_blocks_on_import() { GrandpaJustification::from_commit(&full_client, round, commit).unwrap() }; - let mut generate_and_import_block_with_justification = |parent| { + let generate_and_import_block_with_justification = |parent| { // we import the block with justification attached let block = generate_block(parent); let block_hash = block.hash(); diff --git a/substrate/client/consensus/grandpa/src/voting_rule.rs b/substrate/client/consensus/grandpa/src/voting_rule.rs index c37596d20f68..c1d3cd2fbd6a 100644 --- a/substrate/client/consensus/grandpa/src/voting_rule.rs +++ b/substrate/client/consensus/grandpa/src/voting_rule.rs @@ -367,7 +367,7 @@ mod tests { // where each subtracts 50 blocks from the current target let rule = VotingRulesBuilder::new().add(Subtract(50)).add(Subtract(50)).build(); - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = Arc::new(TestClientBuilder::new().build()); let mut hashes = Vec::with_capacity(200); for _ in 0..200 { @@ -416,7 +416,7 @@ mod tests { fn before_best_by_has_cutoff_at_base() { let rule = BeforeBestBlockBy(2); - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = Arc::new(TestClientBuilder::new().build()); let n = 5; let mut hashes = Vec::with_capacity(n); diff --git a/substrate/client/consensus/grandpa/src/warp_proof.rs b/substrate/client/consensus/grandpa/src/warp_proof.rs index c836ab09fd5d..a79581b1e9f1 100644 --- a/substrate/client/consensus/grandpa/src/warp_proof.rs +++ b/substrate/client/consensus/grandpa/src/warp_proof.rs @@ -338,7 +338,7 @@ mod tests { let mut rng = rand::rngs::StdRng::from_seed([0; 32]); let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let available_authorities = Ed25519Keyring::iter().collect::>(); let genesis_authorities = vec![(Ed25519Keyring::Alice.public().into(), 1)]; diff --git a/substrate/client/consensus/pow/src/lib.rs b/substrate/client/consensus/pow/src/lib.rs index 50e9533abb36..cd7da128549f 100644 --- a/substrate/client/consensus/pow/src/lib.rs +++ b/substrate/client/consensus/pow/src/lib.rs @@ -317,7 +317,7 @@ where } async fn import_block( - &mut self, + &self, mut block: BlockImportParams, ) -> Result { let best_header = self diff --git a/substrate/client/consensus/pow/src/worker.rs b/substrate/client/consensus/pow/src/worker.rs index 9e9c4fc137d8..73400136483a 100644 --- a/substrate/client/consensus/pow/src/worker.rs +++ b/substrate/client/consensus/pow/src/worker.rs @@ -192,7 +192,7 @@ where import_block.insert_intermediate(INTERMEDIATE_KEY, intermediate); let header = import_block.post_header(); - let mut block_import = self.block_import.lock(); + let block_import = self.block_import.lock(); match block_import.import_block(import_block).await { Ok(res) => { diff --git a/substrate/client/consensus/slots/src/lib.rs b/substrate/client/consensus/slots/src/lib.rs index 7cdf90877dff..06e0756fc968 100644 --- a/substrate/client/consensus/slots/src/lib.rs +++ b/substrate/client/consensus/slots/src/lib.rs @@ -517,16 +517,15 @@ pub async fn start_slot_worker( CIDP: CreateInherentDataProviders + Send + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send, { - let mut slots = Slots::new(slot_duration.as_duration(), create_inherent_data_providers, client); + let mut slots = Slots::new( + slot_duration.as_duration(), + create_inherent_data_providers, + client, + sync_oracle, + ); loop { let slot_info = slots.next_slot().await; - - if sync_oracle.is_major_syncing() { - debug!(target: LOG_TARGET, "Skipping proposal slot due to sync."); - continue - } - let _ = worker.on_slot(slot_info).await; } } diff --git a/substrate/client/consensus/slots/src/slots.rs b/substrate/client/consensus/slots/src/slots.rs index 203764310601..c0b412e8ad5b 100644 --- a/substrate/client/consensus/slots/src/slots.rs +++ b/substrate/client/consensus/slots/src/slots.rs @@ -21,7 +21,7 @@ //! This is used instead of `futures_timer::Interval` because it was unreliable. use super::{InherentDataProviderExt, Slot, LOG_TARGET}; -use sp_consensus::SelectChain; +use sp_consensus::{SelectChain, SyncOracle}; use sp_inherents::{CreateInherentDataProviders, InherentDataProvider}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; @@ -87,21 +87,23 @@ impl SlotInfo { } /// A stream that returns every time there is a new slot. -pub(crate) struct Slots { +pub(crate) struct Slots { last_slot: Slot, slot_duration: Duration, until_next_slot: Option, create_inherent_data_providers: IDP, select_chain: SC, + sync_oracle: SO, _phantom: std::marker::PhantomData, } -impl Slots { +impl Slots { /// Create a new `Slots` stream. pub fn new( slot_duration: Duration, create_inherent_data_providers: IDP, select_chain: SC, + sync_oracle: SO, ) -> Self { Slots { last_slot: 0.into(), @@ -109,17 +111,19 @@ impl Slots { until_next_slot: None, create_inherent_data_providers, select_chain, + sync_oracle, _phantom: Default::default(), } } } -impl Slots +impl Slots where Block: BlockT, SC: SelectChain, IDP: CreateInherentDataProviders + 'static, IDP::InherentDataProviders: crate::InherentDataProviderExt, + SO: SyncOracle, { /// Returns a future that fires when the next slot starts. pub async fn next_slot(&mut self) -> SlotInfo { @@ -138,6 +142,11 @@ where let wait_dur = time_until_next_slot(self.slot_duration); self.until_next_slot = Some(Delay::new(wait_dur)); + if self.sync_oracle.is_major_syncing() { + log::debug!(target: LOG_TARGET, "Skipping slot: major sync is in progress."); + continue; + } + let chain_head = match self.select_chain.best_chain().await { Ok(x) => x, Err(e) => { diff --git a/substrate/client/informant/src/display.rs b/substrate/client/informant/src/display.rs index 655bf21c7115..2decd7674782 100644 --- a/substrate/client/informant/src/display.rs +++ b/substrate/client/informant/src/display.rs @@ -65,11 +65,11 @@ impl InformantDisplay { info: &ClientInfo, net_status: NetworkStatus, sync_status: SyncStatus, + num_connected_peers: usize, ) { let best_number = info.chain.best_number; let best_hash = info.chain.best_hash; let finalized_number = info.chain.finalized_number; - let num_connected_peers = sync_status.num_connected_peers; let speed = speed::(best_number, self.last_number, self.last_update); let total_bytes_inbound = net_status.total_bytes_inbound; let total_bytes_outbound = net_status.total_bytes_outbound; @@ -101,17 +101,9 @@ impl InformantDisplay { _, Some(WarpSyncProgress { phase: WarpSyncPhase::DownloadingBlocks(n), .. }), ) if !sync_status.is_major_syncing() => ("⏩", "Block history".into(), format!(", #{}", n)), - ( - _, - _, - Some(WarpSyncProgress { phase: WarpSyncPhase::AwaitingTargetBlock, .. }), - ) => ("⏩", "Waiting for pending target block".into(), "".into()), // Handle all phases besides the two phases we already handle above. (_, _, Some(warp)) - if !matches!( - warp.phase, - WarpSyncPhase::AwaitingTargetBlock | WarpSyncPhase::DownloadingBlocks(_) - ) => + if !matches!(warp.phase, WarpSyncPhase::DownloadingBlocks(_)) => ( "⏩", "Warping".into(), diff --git a/substrate/client/informant/src/lib.rs b/substrate/client/informant/src/lib.rs index d44364539a29..0b0e13dc08bb 100644 --- a/substrate/client/informant/src/lib.rs +++ b/substrate/client/informant/src/lib.rs @@ -24,7 +24,7 @@ use futures_timer::Delay; use log::{debug, info, trace}; use sc_client_api::{BlockchainEvents, UsageProvider}; use sc_network::NetworkStatusProvider; -use sc_network_sync::SyncStatusProvider; +use sc_network_sync::{SyncStatusProvider, SyncingService}; use sp_blockchain::HeaderMetadata; use sp_runtime::traits::{Block as BlockT, Header}; use std::{collections::VecDeque, fmt::Display, sync::Arc, time::Duration}; @@ -37,10 +37,9 @@ fn interval(duration: Duration) -> impl Stream + Unpin { } /// Builds the informant and returns a `Future` that drives the informant. -pub async fn build(client: Arc, network: N, syncing: S) +pub async fn build(client: Arc, network: N, syncing: Arc>) where N: NetworkStatusProvider, - S: SyncStatusProvider, C: UsageProvider + HeaderMetadata + BlockchainEvents, >::Error: Display, { @@ -52,13 +51,14 @@ where .filter_map(|_| async { let net_status = network.status().await; let sync_status = syncing.status().await; + let num_connected_peers = syncing.num_connected_peers(); - match (net_status.ok(), sync_status.ok()) { - (Some(net), Some(sync)) => Some((net, sync)), + match (net_status, sync_status) { + (Ok(net), Ok(sync)) => Some((net, sync, num_connected_peers)), _ => None, } }) - .for_each(move |(net_status, sync_status)| { + .for_each(move |(net_status, sync_status, num_connected_peers)| { let info = client_1.usage_info(); if let Some(ref usage) = info.usage { trace!(target: "usage", "Usage statistics: {}", usage); @@ -68,7 +68,7 @@ where "Usage statistics not displayed as backend does not provide it", ) } - display.display(&info, net_status, sync_status); + display.display(&info, net_status, sync_status, num_connected_peers); future::ready(()) }); diff --git a/substrate/client/merkle-mountain-range/src/test_utils.rs b/substrate/client/merkle-mountain-range/src/test_utils.rs index fcf9fa25b593..3b0506ef55d3 100644 --- a/substrate/client/merkle-mountain-range/src/test_utils.rs +++ b/substrate/client/merkle-mountain-range/src/test_utils.rs @@ -122,7 +122,7 @@ impl MockClient { name: &[u8], maybe_leaf_idx: Option, ) -> MmrBlock { - let mut client = self.client.lock(); + let client = self.client.lock(); let hash = client.expect_block_hash_from_id(&at).unwrap(); let mut block_builder = BlockBuilderBuilder::new(&*client) diff --git a/substrate/client/network/src/bitswap/mod.rs b/substrate/client/network/src/bitswap/mod.rs index 22f1973adcb2..1e20572eeeb1 100644 --- a/substrate/client/network/src/bitswap/mod.rs +++ b/substrate/client/network/src/bitswap/mod.rs @@ -468,7 +468,7 @@ mod tests { #[tokio::test] async fn transaction_found() { - let mut client = TestClientBuilder::with_tx_storage(u32::MAX).build(); + let client = TestClientBuilder::with_tx_storage(u32::MAX).build(); let mut block_builder = BlockBuilderBuilder::new(&client) .on_parent_block(client.chain_info().genesis_hash) .with_parent_block_number(0) diff --git a/substrate/client/network/src/litep2p/peerstore.rs b/substrate/client/network/src/litep2p/peerstore.rs index 55e912c31f18..dd8d92bcee64 100644 --- a/substrate/client/network/src/litep2p/peerstore.rs +++ b/substrate/client/network/src/litep2p/peerstore.rs @@ -192,38 +192,63 @@ impl PeerStoreProvider for PeerstoreHandle { } /// Adjust peer reputation. - fn report_peer(&self, peer: PeerId, reputation_change: ReputationChange) { + fn report_peer(&self, peer_id: PeerId, change: ReputationChange) { let mut lock = self.0.lock(); + let peer_info = lock.peers.entry(peer_id).or_default(); + let was_banned = peer_info.is_banned(); + peer_info.add_reputation(change.value); + let peer_reputation = peer_info.reputation; + + log::trace!( + target: LOG_TARGET, + "Report {}: {:+} to {}. Reason: {}.", + peer_id, + change.value, + peer_reputation, + change.reason, + ); - log::trace!(target: LOG_TARGET, "report peer {reputation_change:?}"); - - match lock.peers.get_mut(&peer) { - Some(info) => { - info.add_reputation(reputation_change.value); - }, - None => { - lock.peers.insert( - peer, - PeerInfo { - reputation: reputation_change.value, - last_updated: Instant::now(), - role: None, - }, + if !peer_info.is_banned() { + if was_banned { + log::info!( + target: LOG_TARGET, + "Peer {} is now unbanned: {:+} to {}. Reason: {}.", + peer_id, + change.value, + peer_reputation, + change.reason, ); - }, + } + return; } - if lock - .peers - .get(&peer) - .expect("peer exist since it was just modified; qed") - .is_banned() - { - log::warn!(target: LOG_TARGET, "{peer:?} banned, disconnecting, reason: {}", reputation_change.reason); - - for sender in &lock.protocols { - sender.disconnect_peer(peer); - } + // Peer is currently banned, disconnect it from all protocols. + lock.protocols.iter().for_each(|handle| handle.disconnect_peer(peer_id.into())); + + // The peer is banned for the first time. + if !was_banned { + log::warn!( + target: LOG_TARGET, + "Report {}: {:+} to {}. Reason: {}. Banned, disconnecting.", + peer_id, + change.value, + peer_reputation, + change.reason, + ); + return; + } + + // The peer was already banned and it got another negative report. + // This may happen during a batch report. + if change.value < 0 { + log::debug!( + target: LOG_TARGET, + "Report {}: {:+} to {}. Reason: {}. Misbehaved during the ban threshold.", + peer_id, + change.value, + peer_reputation, + change.reason, + ); } } diff --git a/substrate/client/network/src/peer_store.rs b/substrate/client/network/src/peer_store.rs index 63e98a2fb4bf..0e57791542e1 100644 --- a/substrate/client/network/src/peer_store.rs +++ b/substrate/client/network/src/peer_store.rs @@ -260,11 +260,37 @@ impl PeerStoreInner { fn report_peer(&mut self, peer_id: PeerId, change: ReputationChange) { let peer_info = self.peers.entry(peer_id).or_default(); + let was_banned = peer_info.is_banned(); peer_info.add_reputation(change.value); - if peer_info.is_banned() { - self.protocols.iter().for_each(|handle| handle.disconnect_peer(peer_id.into())); + log::trace!( + target: LOG_TARGET, + "Report {}: {:+} to {}. Reason: {}.", + peer_id, + change.value, + peer_info.reputation, + change.reason, + ); + + if !peer_info.is_banned() { + if was_banned { + log::info!( + target: LOG_TARGET, + "Peer {} is now unbanned: {:+} to {}. Reason: {}.", + peer_id, + change.value, + peer_info.reputation, + change.reason, + ); + } + return; + } + + // Peer is currently banned, disconnect it from all protocols. + self.protocols.iter().for_each(|handle| handle.disconnect_peer(peer_id.into())); + // The peer is banned for the first time. + if !was_banned { log::warn!( target: LOG_TARGET, "Report {}: {:+} to {}. Reason: {}. Banned, disconnecting.", @@ -273,10 +299,15 @@ impl PeerStoreInner { peer_info.reputation, change.reason, ); - } else { - log::trace!( + return; + } + + // The peer was already banned and it got another negative report. + // This may happen during a batch report. + if change.value < 0 { + log::debug!( target: LOG_TARGET, - "Report {}: {:+} to {}. Reason: {}.", + "Report {}: {:+} to {}. Reason: {}. Misbehaved during the ban threshold.", peer_id, change.value, peer_info.reputation, diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index ee7576c22f16..4a57d61df457 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -32,7 +32,7 @@ use crate::{ syncing_service::{SyncingService, ToServiceCommand}, }, strategy::{ - warp::{EncodedProof, WarpProofRequest, WarpSyncParams}, + warp::{EncodedProof, WarpProofRequest, WarpSyncConfig}, StrategyKey, SyncingAction, SyncingConfig, SyncingStrategy, }, types::{ @@ -42,11 +42,7 @@ use crate::{ }; use codec::{Decode, DecodeAll, Encode}; -use futures::{ - channel::oneshot, - future::{BoxFuture, Fuse}, - FutureExt, StreamExt, -}; +use futures::{channel::oneshot, FutureExt, StreamExt}; use libp2p::request_response::OutboundFailure; use log::{debug, error, trace, warn}; use prometheus_endpoint::{ @@ -257,10 +253,6 @@ pub struct SyncingEngine { /// The `PeerId`'s of all boot nodes. boot_node_ids: HashSet, - /// A channel to get target block header if we skip over proofs downloading during warp sync. - warp_sync_target_block_header_rx_fused: - Fuse>>, - /// Protocol name used for block announcements block_announce_protocol_name: ProtocolName, @@ -309,7 +301,7 @@ where protocol_id: ProtocolId, fork_id: &Option, block_announce_validator: Box + Send>, - warp_sync_params: Option>, + warp_sync_config: Option>, network_service: service::network::NetworkServiceHandle, import_queue: Box>, block_downloader: Arc>, @@ -404,19 +396,6 @@ where Arc::clone(&peer_store_handle), ); - // Split warp sync params into warp sync config and a channel to retrieve target block - // header. - let (warp_sync_config, warp_sync_target_block_header_rx) = - warp_sync_params.map_or((None, None), |params| { - let (config, target_block_rx) = params.split(); - (Some(config), target_block_rx) - }); - - // Make sure polling of the target block channel is a no-op if there is no block to - // retrieve. - let warp_sync_target_block_header_rx_fused = warp_sync_target_block_header_rx - .map_or(futures::future::pending().boxed().fuse(), |rx| rx.boxed().fuse()); - // Initialize syncing strategy. let strategy = SyncingStrategy::new(syncing_config, client.clone(), warp_sync_config)?; @@ -460,7 +439,6 @@ where genesis_hash, important_peers, default_peers_set_no_slot_connected_peers: HashSet::new(), - warp_sync_target_block_header_rx_fused, boot_node_ids, default_peers_set_no_slot_peers, default_peers_set_num_full, @@ -493,15 +471,6 @@ where )) } - /// Report Prometheus metrics. - pub fn report_metrics(&self) { - if let Some(metrics) = &self.metrics { - let n = u64::try_from(self.peers.len()).unwrap_or(std::u64::MAX); - metrics.peers.set(n); - } - self.strategy.report_metrics(); - } - fn update_peer_info( &mut self, peer_id: &PeerId, @@ -628,24 +597,17 @@ where pub async fn run(mut self) { loop { tokio::select! { - _ = self.tick_timeout.tick() => self.perform_periodic_actions(), + _ = self.tick_timeout.tick() => { + // TODO: This tick should not be necessary, but + // `self.process_strategy_actions()` is not called in some cases otherwise and + // some tests fail because of this + }, command = self.service_rx.select_next_some() => self.process_service_command(command), notification_event = self.notification_service.next_event() => match notification_event { Some(event) => self.process_notification_event(event), None => return, }, - // TODO: setting of warp sync target block should be moved to the initialization of - // `SyncingEngine`, see https://github.com/paritytech/polkadot-sdk/issues/3537. - warp_target_block_header = &mut self.warp_sync_target_block_header_rx_fused => { - if let Err(_) = self.pass_warp_sync_target_block_header(warp_target_block_header) { - error!( - target: LOG_TARGET, - "Failed to set warp sync target block header, terminating `SyncingEngine`.", - ); - return - } - }, response_event = self.pending_responses.select_next_some() => self.process_response_event(response_event), validation_result = self.block_announce_validator.select_next_some() => @@ -653,7 +615,6 @@ where } // Update atomic variables - self.num_connected.store(self.peers.len(), Ordering::Relaxed); self.is_major_syncing.store(self.strategy.is_major_syncing(), Ordering::Relaxed); // Process actions requested by a syncing strategy. @@ -757,10 +718,6 @@ where Ok(()) } - fn perform_periodic_actions(&mut self) { - self.report_metrics(); - } - fn process_service_command(&mut self, command: ToServiceCommand) { match command { ToServiceCommand::SetSyncForkRequest(peers, hash, number) => { @@ -803,25 +760,11 @@ where ); }, ToServiceCommand::Status(tx) => { - let mut status = self.strategy.status(); - status.num_connected_peers = self.peers.len() as u32; - let _ = tx.send(status); + let _ = tx.send(self.strategy.status()); }, ToServiceCommand::NumActivePeers(tx) => { let _ = tx.send(self.num_active_peers()); }, - ToServiceCommand::SyncState(tx) => { - let _ = tx.send(self.strategy.status()); - }, - ToServiceCommand::BestSeenBlock(tx) => { - let _ = tx.send(self.strategy.status().best_seen_block); - }, - ToServiceCommand::NumSyncPeers(tx) => { - let _ = tx.send(self.strategy.status().num_peers); - }, - ToServiceCommand::NumQueuedBlocks(tx) => { - let _ = tx.send(self.strategy.status().queued_blocks); - }, ToServiceCommand::NumDownloadedBlocks(tx) => { let _ = tx.send(self.strategy.num_downloaded_blocks()); }, @@ -829,11 +772,8 @@ where let _ = tx.send(self.strategy.num_sync_requests()); }, ToServiceCommand::PeersInfo(tx) => { - let peers_info = self - .peers - .iter() - .map(|(peer_id, peer)| (*peer_id, peer.info.clone())) - .collect(); + let peers_info = + self.peers.iter().map(|(peer_id, peer)| (*peer_id, peer.info)).collect(); let _ = tx.send(peers_info); }, ToServiceCommand::OnBlockFinalized(hash, header) => @@ -898,22 +838,6 @@ where } } - fn pass_warp_sync_target_block_header( - &mut self, - header: Result, - ) -> Result<(), ()> { - match header { - Ok(header) => self.strategy.set_warp_sync_target_block_header(header), - Err(err) => { - error!( - target: LOG_TARGET, - "Failed to get target block for warp sync. Error: {err:?}", - ); - Err(()) - }, - } - } - /// Called by peer when it is disconnecting. /// /// Returns a result if the handshake of this peer was indeed accepted. @@ -922,6 +846,10 @@ where log::debug!(target: LOG_TARGET, "{peer_id} does not exist in `SyncingEngine`"); return }; + if let Some(metrics) = &self.metrics { + metrics.peers.dec(); + } + self.num_connected.fetch_sub(1, Ordering::AcqRel); if self.important_peers.contains(&peer_id) { log::warn!(target: LOG_TARGET, "Reserved peer {peer_id} disconnected"); @@ -1097,7 +1025,12 @@ where log::debug!(target: LOG_TARGET, "Connected {peer_id}"); - self.peers.insert(peer_id, peer); + if self.peers.insert(peer_id, peer).is_none() { + if let Some(metrics) = &self.metrics { + metrics.peers.inc(); + } + self.num_connected.fetch_add(1, Ordering::AcqRel); + } self.peer_store_handle.set_peer_role(&peer_id, status.roles.into()); if self.default_peers_set_no_slot_peers.contains(&peer_id) { diff --git a/substrate/client/network/sync/src/justification_requests.rs b/substrate/client/network/sync/src/justification_requests.rs index 2b50c85602d7..f2d7488b2c35 100644 --- a/substrate/client/network/sync/src/justification_requests.rs +++ b/substrate/client/network/sync/src/justification_requests.rs @@ -21,12 +21,14 @@ //! that don't make sense after one of the forks is finalized). use crate::{ - request_metrics::Metrics, strategy::chain_sync::{PeerSync, PeerSyncState}, LOG_TARGET, }; use fork_tree::ForkTree; use log::{debug, trace, warn}; +use prometheus_endpoint::{ + prometheus::core::GenericGauge, register, GaugeVec, Opts, PrometheusError, Registry, U64, +}; use sc_network_types::PeerId; use sp_blockchain::Error as ClientError; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; @@ -41,6 +43,34 @@ const EXTRA_RETRY_WAIT: Duration = Duration::from_secs(10); /// Pending extra data request for the given block (hash and number). type ExtraRequest = (::Hash, NumberFor); +#[derive(Debug)] +struct Metrics { + pending: GenericGauge, + active: GenericGauge, + failed: GenericGauge, + importing: GenericGauge, +} + +impl Metrics { + fn register(registry: &Registry) -> Result { + let justifications = GaugeVec::::new( + Opts::new( + "substrate_sync_extra_justifications", + "Number of extra justifications requests", + ), + &["status"], + )?; + let justifications = register(justifications, registry)?; + + Ok(Self { + pending: justifications.with_label_values(&["pending"]), + active: justifications.with_label_values(&["active"]), + failed: justifications.with_label_values(&["failed"]), + importing: justifications.with_label_values(&["importing"]), + }) + } +} + /// Manages pending block extra data (e.g. justification) requests. /// /// Multiple extras may be requested for competing forks, or for the same branch @@ -62,10 +92,14 @@ pub(crate) struct ExtraRequests { importing_requests: HashSet>, /// the name of this type of extra request (useful for logging.) request_type_name: &'static str, + metrics: Option, } impl ExtraRequests { - pub(crate) fn new(request_type_name: &'static str) -> Self { + pub(crate) fn new( + request_type_name: &'static str, + metrics_registry: Option<&Registry>, + ) -> Self { Self { tree: ForkTree::new(), best_seen_finalized_number: Zero::zero(), @@ -74,6 +108,16 @@ impl ExtraRequests { failed_requests: HashMap::new(), importing_requests: HashSet::new(), request_type_name, + metrics: metrics_registry.and_then(|registry| { + Metrics::register(registry) + .inspect_err(|error| { + log::error!( + target: LOG_TARGET, + "Failed to register `ExtraRequests` metrics {error}", + ); + }) + .ok() + }), } } @@ -83,6 +127,12 @@ impl ExtraRequests { self.pending_requests.clear(); self.active_requests.clear(); self.failed_requests.clear(); + + if let Some(metrics) = &self.metrics { + metrics.pending.set(0); + metrics.active.set(0); + metrics.failed.set(0); + } } /// Returns an iterator-like struct that yields peers which extra @@ -100,6 +150,9 @@ impl ExtraRequests { Ok(true) => { // this is a new root so we add it to the current `pending_requests` self.pending_requests.push_back((request.0, request.1)); + if let Some(metrics) = &self.metrics { + metrics.pending.inc(); + } }, Err(fork_tree::Error::Revert) => { // we have finalized further than the given request, presumably @@ -117,6 +170,10 @@ impl ExtraRequests { pub(crate) fn peer_disconnected(&mut self, who: &PeerId) { if let Some(request) = self.active_requests.remove(who) { self.pending_requests.push_front(request); + if let Some(metrics) = &self.metrics { + metrics.active.dec(); + metrics.pending.inc(); + } } } @@ -130,13 +187,21 @@ impl ExtraRequests { // currently enforced by the outer network protocol before passing on // messages to chain sync. if let Some(request) = self.active_requests.remove(&who) { + if let Some(metrics) = &self.metrics { + metrics.active.dec(); + } + if let Some(r) = resp { trace!(target: LOG_TARGET, "Queuing import of {} from {:?} for {:?}", self.request_type_name, who, request, ); - self.importing_requests.insert(request); + if self.importing_requests.insert(request) { + if let Some(metrics) = &self.metrics { + metrics.importing.inc(); + } + } return Some((who, request.0, request.1, r)) } else { trace!(target: LOG_TARGET, @@ -146,6 +211,10 @@ impl ExtraRequests { } self.failed_requests.entry(request).or_default().push((who, Instant::now())); self.pending_requests.push_front(request); + if let Some(metrics) = &self.metrics { + metrics.failed.set(self.failed_requests.len().try_into().unwrap_or(u64::MAX)); + metrics.pending.inc(); + } } else { trace!(target: LOG_TARGET, "No active {} request to {:?}", @@ -194,6 +263,11 @@ impl ExtraRequests { self.pending_requests.retain(|(h, n)| roots.contains(&(h, n, &()))); self.active_requests.retain(|_, (h, n)| roots.contains(&(h, n, &()))); self.failed_requests.retain(|(h, n), _| roots.contains(&(h, n, &()))); + if let Some(metrics) = &self.metrics { + metrics.pending.set(self.pending_requests.len().try_into().unwrap_or(u64::MAX)); + metrics.active.set(self.active_requests.len().try_into().unwrap_or(u64::MAX)); + metrics.failed.set(self.failed_requests.len().try_into().unwrap_or(u64::MAX)); + } Ok(()) } @@ -210,12 +284,18 @@ impl ExtraRequests { if !self.importing_requests.remove(&request) { return false } + if let Some(metrics) = &self.metrics { + metrics.importing.dec(); + } let (finalized_hash, finalized_number) = match result { Ok(req) => (req.0, req.1), Err(_) => { if reschedule_on_failure { self.pending_requests.push_front(request); + if let Some(metrics) = &self.metrics { + metrics.pending.inc(); + } } return true }, @@ -233,6 +313,11 @@ impl ExtraRequests { self.active_requests.clear(); self.pending_requests.clear(); self.pending_requests.extend(self.tree.roots().map(|(&h, &n, _)| (h, n))); + if let Some(metrics) = &self.metrics { + metrics.failed.set(0); + metrics.active.set(0); + metrics.pending.set(self.pending_requests.len().try_into().unwrap_or(u64::MAX)); + } self.best_seen_finalized_number = finalized_number; true @@ -249,16 +334,6 @@ impl ExtraRequests { pub(crate) fn pending_requests(&self) -> impl Iterator> { self.pending_requests.iter() } - - /// Get some key metrics. - pub(crate) fn metrics(&self) -> Metrics { - Metrics { - pending_requests: self.pending_requests.len().try_into().unwrap_or(std::u32::MAX), - active_requests: self.active_requests.len().try_into().unwrap_or(std::u32::MAX), - failed_requests: self.failed_requests.len().try_into().unwrap_or(std::u32::MAX), - importing_requests: self.importing_requests.len().try_into().unwrap_or(std::u32::MAX), - } - } } /// Matches peers with pending extra requests. @@ -301,8 +376,17 @@ impl<'a, B: BlockT> Matcher<'a, B> { for requests in self.extras.failed_requests.values_mut() { requests.retain(|(_, instant)| instant.elapsed() < EXTRA_RETRY_WAIT); } + if let Some(metrics) = &self.extras.metrics { + metrics + .failed + .set(self.extras.failed_requests.len().try_into().unwrap_or(u64::MAX)); + } while let Some(request) = self.extras.pending_requests.pop_front() { + if let Some(metrics) = &self.extras.metrics { + metrics.pending.dec(); + } + for (peer, sync) in peers.iter().filter(|(_, sync)| sync.state == PeerSyncState::Available) { @@ -326,6 +410,9 @@ impl<'a, B: BlockT> Matcher<'a, B> { continue } self.extras.active_requests.insert(*peer, request); + if let Some(metrics) = &self.extras.metrics { + metrics.active.inc(); + } trace!(target: LOG_TARGET, "Sending {} request to {:?} for {:?}", @@ -336,6 +423,9 @@ impl<'a, B: BlockT> Matcher<'a, B> { } self.extras.pending_requests.push_back(request); + if let Some(metrics) = &self.extras.metrics { + metrics.pending.inc(); + } self.remaining -= 1; if self.remaining == 0 { @@ -359,7 +449,7 @@ mod tests { #[test] fn requests_are_processed_in_order() { fn property(mut peers: ArbitraryPeers) { - let mut requests = ExtraRequests::::new("test"); + let mut requests = ExtraRequests::::new("test", None); let num_peers_available = peers.0.values().filter(|s| s.state == PeerSyncState::Available).count(); @@ -385,7 +475,7 @@ mod tests { #[test] fn new_roots_schedule_new_request() { fn property(data: Vec) { - let mut requests = ExtraRequests::::new("test"); + let mut requests = ExtraRequests::::new("test", None); for (i, number) in data.into_iter().enumerate() { let hash = [i as u8; 32].into(); let pending = requests.pending_requests.len(); @@ -402,7 +492,7 @@ mod tests { #[test] fn disconnecting_implies_rescheduling() { fn property(mut peers: ArbitraryPeers) -> bool { - let mut requests = ExtraRequests::::new("test"); + let mut requests = ExtraRequests::::new("test", None); let num_peers_available = peers.0.values().filter(|s| s.state == PeerSyncState::Available).count(); @@ -438,7 +528,7 @@ mod tests { #[test] fn no_response_reschedules() { fn property(mut peers: ArbitraryPeers) { - let mut requests = ExtraRequests::::new("test"); + let mut requests = ExtraRequests::::new("test", None); let num_peers_available = peers.0.values().filter(|s| s.state == PeerSyncState::Available).count(); @@ -480,7 +570,7 @@ mod tests { fn request_is_rescheduled_when_earlier_block_is_finalized() { sp_tracing::try_init_simple(); - let mut finality_proofs = ExtraRequests::::new("test"); + let mut finality_proofs = ExtraRequests::::new("test", None); let hash4 = [4; 32].into(); let hash5 = [5; 32].into(); @@ -521,7 +611,7 @@ mod tests { #[test] fn ancestor_roots_are_finalized_when_finality_notification_is_missed() { - let mut finality_proofs = ExtraRequests::::new("test"); + let mut finality_proofs = ExtraRequests::::new("test", None); let hash4 = [4; 32].into(); let hash5 = [5; 32].into(); diff --git a/substrate/client/network/sync/src/lib.rs b/substrate/client/network/sync/src/lib.rs index 9f6c0f45d089..ca7280edba5f 100644 --- a/substrate/client/network/sync/src/lib.rs +++ b/substrate/client/network/sync/src/lib.rs @@ -19,7 +19,7 @@ //! Blockchain syncing implementation in Substrate. pub use service::syncing_service::SyncingService; -pub use strategy::warp::{WarpSyncParams, WarpSyncPhase, WarpSyncProgress}; +pub use strategy::warp::{WarpSyncConfig, WarpSyncPhase, WarpSyncProgress}; pub use types::{SyncEvent, SyncEventStream, SyncState, SyncStatus, SyncStatusProvider}; mod block_announce_validator; diff --git a/substrate/client/network/sync/src/service/syncing_service.rs b/substrate/client/network/sync/src/service/syncing_service.rs index f4bc58afd4fd..08a2b36118a9 100644 --- a/substrate/client/network/sync/src/service/syncing_service.rs +++ b/substrate/client/network/sync/src/service/syncing_service.rs @@ -50,10 +50,6 @@ pub enum ToServiceCommand { EventStream(TracingUnboundedSender), Status(oneshot::Sender>), NumActivePeers(oneshot::Sender), - SyncState(oneshot::Sender>), - BestSeenBlock(oneshot::Sender>>), - NumSyncPeers(oneshot::Sender), - NumQueuedBlocks(oneshot::Sender), NumDownloadedBlocks(oneshot::Sender), NumSyncRequests(oneshot::Sender), PeersInfo(oneshot::Sender)>>), @@ -83,6 +79,11 @@ impl SyncingService { Self { tx, num_connected, is_major_syncing } } + /// Get the number of peers known to `SyncingEngine` (both full and light). + pub fn num_connected_peers(&self) -> usize { + self.num_connected.load(Ordering::Relaxed) + } + /// Get the number of active peers. pub async fn num_active_peers(&self) -> Result { let (tx, rx) = oneshot::channel(); @@ -91,30 +92,6 @@ impl SyncingService { rx.await } - /// Get best seen block. - pub async fn best_seen_block(&self) -> Result>, oneshot::Canceled> { - let (tx, rx) = oneshot::channel(); - let _ = self.tx.unbounded_send(ToServiceCommand::BestSeenBlock(tx)); - - rx.await - } - - /// Get the number of sync peers. - pub async fn num_sync_peers(&self) -> Result { - let (tx, rx) = oneshot::channel(); - let _ = self.tx.unbounded_send(ToServiceCommand::NumSyncPeers(tx)); - - rx.await - } - - /// Get the number of queued blocks. - pub async fn num_queued_blocks(&self) -> Result { - let (tx, rx) = oneshot::channel(); - let _ = self.tx.unbounded_send(ToServiceCommand::NumQueuedBlocks(tx)); - - rx.await - } - /// Get the number of downloaded blocks. pub async fn num_downloaded_blocks(&self) -> Result { let (tx, rx) = oneshot::channel(); @@ -149,11 +126,11 @@ impl SyncingService { /// Get sync status /// /// Returns an error if `SyncingEngine` has terminated. - pub async fn status(&self) -> Result, ()> { + pub async fn status(&self) -> Result, oneshot::Canceled> { let (tx, rx) = oneshot::channel(); let _ = self.tx.unbounded_send(ToServiceCommand::Status(tx)); - rx.await.map_err(|_| ()) + rx.await } } diff --git a/substrate/client/network/sync/src/strategy.rs b/substrate/client/network/sync/src/strategy.rs index 72f84d1c286e..ad3a9461c93b 100644 --- a/substrate/client/network/sync/src/strategy.rs +++ b/substrate/client/network/sync/src/strategy.rs @@ -30,7 +30,7 @@ use crate::{ LOG_TARGET, }; use chain_sync::{ChainSync, ChainSyncAction, ChainSyncMode}; -use log::{debug, error, info, warn}; +use log::{debug, error, info}; use prometheus_endpoint::Registry; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; @@ -210,7 +210,7 @@ where client.clone(), config.max_parallel_downloads, config.max_blocks_per_request, - config.metrics_registry.clone(), + config.metrics_registry.as_ref(), std::iter::empty(), )?; Ok(Self { @@ -455,46 +455,6 @@ where self.chain_sync.as_ref().map_or(0, |chain_sync| chain_sync.num_sync_requests()) } - /// Report Prometheus metrics - pub fn report_metrics(&self) { - if let Some(ref chain_sync) = self.chain_sync { - chain_sync.report_metrics(); - } - } - - /// Let `WarpSync` know about target block header - pub fn set_warp_sync_target_block_header( - &mut self, - target_header: B::Header, - ) -> Result<(), ()> { - match self.config.mode { - SyncMode::Warp => match self.warp { - Some(ref mut warp) => { - warp.set_target_block(target_header); - Ok(()) - }, - None => { - // As mode is set to warp sync, but no warp sync strategy is active, this means - // that warp sync has already finished / was skipped. - warn!( - target: LOG_TARGET, - "Discarding warp sync target, as warp sync was seemingly skipped due \ - to node being (partially) synced.", - ); - Ok(()) - }, - }, - _ => { - error!( - target: LOG_TARGET, - "Cannot set warp sync target block: not in warp sync mode." - ); - debug_assert!(false); - Err(()) - }, - } - } - /// Get actions that should be performed by the owner on the strategy's behalf #[must_use] pub fn actions(&mut self) -> Result>, ClientError> { @@ -552,7 +512,7 @@ where self.client.clone(), self.config.max_parallel_downloads, self.config.max_blocks_per_request, - self.config.metrics_registry.clone(), + self.config.metrics_registry.as_ref(), self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| { (*peer_id, *best_hash, *best_number) }), @@ -580,7 +540,7 @@ where self.client.clone(), self.config.max_parallel_downloads, self.config.max_blocks_per_request, - self.config.metrics_registry.clone(), + self.config.metrics_registry.as_ref(), self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| { (*peer_id, *best_hash, *best_number) }), @@ -600,63 +560,3 @@ where } } } - -#[cfg(test)] -mod test { - use super::*; - use futures::executor::block_on; - use sc_block_builder::BlockBuilderBuilder; - use substrate_test_runtime_client::{ - ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, TestClientBuilder, - TestClientBuilderExt, - }; - - /// Regression test for crash when starting already synced parachain node with `--sync=warp`. - /// We must remove this after setting of warp sync target block is moved to initialization of - /// `SyncingEngine` (issue https://github.com/paritytech/polkadot-sdk/issues/3537). - #[test] - fn set_target_block_finished_warp_sync() { - // Populate database with finalized state. - let mut client = Arc::new(TestClientBuilder::new().build()); - 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(); - let just = (*b"TEST", Vec::new()); - client.finalize_block(block.hash(), Some(just)).unwrap(); - let target_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; - - // Initialize syncing strategy. - let config = SyncingConfig { - mode: SyncMode::Warp, - max_parallel_downloads: 3, - max_blocks_per_request: 64, - metrics_registry: None, - }; - let mut strategy = - SyncingStrategy::new(config, client, Some(WarpSyncConfig::WaitForTarget)).unwrap(); - - // Warp sync instantly finishes as we have finalized state in DB. - let actions = strategy.actions().unwrap(); - assert_eq!(actions.len(), 1); - assert!(matches!(actions[0], SyncingAction::Finished)); - assert!(strategy.warp.is_none()); - - // Try setting the target block. We mustn't crash. - strategy - .set_warp_sync_target_block_header(target_block.header().clone()) - .unwrap(); - } -} diff --git a/substrate/client/network/sync/src/strategy/chain_sync.rs b/substrate/client/network/sync/src/strategy/chain_sync.rs index 52870d5ba151..21e474048625 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync.rs @@ -43,7 +43,7 @@ use crate::{ use codec::Encode; use log::{debug, error, info, trace, warn}; -use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; +use prometheus_endpoint::{register, Gauge, PrometheusError, Registry, U64}; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; use sc_network_common::sync::message::{ @@ -128,7 +128,6 @@ mod rep { struct Metrics { queued_blocks: Gauge, fork_targets: Gauge, - justifications: GaugeVec, } impl Metrics { @@ -143,16 +142,6 @@ impl Metrics { let g = Gauge::new("substrate_sync_fork_targets", "Number of fork sync targets")?; register(g, r)? }, - justifications: { - let g = GaugeVec::new( - Opts::new( - "substrate_sync_extra_justifications", - "Number of extra justifications requests", - ), - &["status"], - )?; - register(g, r)? - }, }) } } @@ -374,7 +363,7 @@ where client: Arc, max_parallel_downloads: u32, max_blocks_per_request: u32, - metrics_registry: Option, + metrics_registry: Option<&Registry>, initial_peers: impl Iterator)>, ) -> Result { let mut sync = Self { @@ -384,7 +373,7 @@ where blocks: BlockCollection::new(), best_queued_hash: Default::default(), best_queued_number: Zero::zero(), - extra_justifications: ExtraRequests::new("justification"), + extra_justifications: ExtraRequests::new("justification", metrics_registry), mode, queue_blocks: Default::default(), fork_targets: Default::default(), @@ -396,7 +385,7 @@ where import_existing: false, gap_sync: None, actions: Vec::new(), - metrics: metrics_registry.and_then(|r| match Metrics::register(&r) { + metrics: metrics_registry.and_then(|r| match Metrics::register(r) { Ok(metrics) => Some(metrics), Err(err) => { log::error!( @@ -449,7 +438,6 @@ where state: sync_state, best_seen_block, num_peers: self.peers.len() as u32, - num_connected_peers: 0u32, queued_blocks: self.queue_blocks.len() as u32, state_sync: self.state_sync.as_ref().map(|s| s.progress()), warp_sync: warp_sync_progress, @@ -676,7 +664,13 @@ where self.fork_targets .entry(*hash) - .or_insert_with(|| ForkTarget { number, peers: Default::default(), parent_hash: None }) + .or_insert_with(|| { + if let Some(metrics) = &self.metrics { + metrics.fork_targets.inc(); + } + + ForkTarget { number, peers: Default::default(), parent_hash: None } + }) .peers .extend(peers); } @@ -883,10 +877,16 @@ where ); self.fork_targets .entry(peer.best_hash) - .or_insert_with(|| ForkTarget { - number: peer.best_number, - parent_hash: None, - peers: Default::default(), + .or_insert_with(|| { + if let Some(metrics) = &self.metrics { + metrics.fork_targets.inc(); + } + + ForkTarget { + number: peer.best_number, + parent_hash: None, + peers: Default::default(), + } }) .peers .insert(*peer_id); @@ -1126,10 +1126,16 @@ where ); self.fork_targets .entry(hash) - .or_insert_with(|| ForkTarget { - number, - parent_hash: Some(*announce.header.parent_hash()), - peers: Default::default(), + .or_insert_with(|| { + if let Some(metrics) = &self.metrics { + metrics.fork_targets.inc(); + } + + ForkTarget { + number, + parent_hash: Some(*announce.header.parent_hash()), + peers: Default::default(), + } }) .peers .insert(peer_id); @@ -1161,6 +1167,9 @@ where target.peers.remove(peer_id); !target.peers.is_empty() }); + if let Some(metrics) = &self.metrics { + metrics.fork_targets.set(self.fork_targets.len().try_into().unwrap_or(u64::MAX)); + } let blocks = self.ready_blocks(); @@ -1169,36 +1178,6 @@ where } } - /// Report prometheus metrics. - pub fn report_metrics(&self) { - if let Some(metrics) = &self.metrics { - metrics - .fork_targets - .set(self.fork_targets.len().try_into().unwrap_or(std::u64::MAX)); - metrics - .queued_blocks - .set(self.queue_blocks.len().try_into().unwrap_or(std::u64::MAX)); - - let justifications_metrics = self.extra_justifications.metrics(); - metrics - .justifications - .with_label_values(&["pending"]) - .set(justifications_metrics.pending_requests.into()); - metrics - .justifications - .with_label_values(&["active"]) - .set(justifications_metrics.active_requests.into()); - metrics - .justifications - .with_label_values(&["failed"]) - .set(justifications_metrics.failed_requests.into()); - metrics - .justifications - .with_label_values(&["importing"]) - .set(justifications_metrics.importing_requests.into()); - } - } - /// Returns the median seen block number. fn median_seen(&self) -> Option> { let mut best_seens = self.peers.values().map(|p| p.best_number).collect::>(); @@ -1264,6 +1243,11 @@ where self.on_block_queued(h, n) } self.queue_blocks.extend(new_blocks.iter().map(|b| b.hash)); + if let Some(metrics) = &self.metrics { + metrics + .queued_blocks + .set(self.queue_blocks.len().try_into().unwrap_or(u64::MAX)); + } self.actions.push(ChainSyncAction::ImportBlocks { origin, blocks: new_blocks }) } @@ -1280,6 +1264,9 @@ where /// through all peers to update our view of their state as well. fn on_block_queued(&mut self, hash: &B::Hash, number: NumberFor) { if self.fork_targets.remove(hash).is_some() { + if let Some(metrics) = &self.metrics { + metrics.fork_targets.dec(); + } trace!(target: LOG_TARGET, "Completed fork sync {hash:?}"); } if let Some(gap_sync) = &mut self.gap_sync { @@ -1549,12 +1536,13 @@ where std::cmp::min(self.best_queued_number, self.client.info().finalized_number); let best_queued = self.best_queued_number; let client = &self.client; - let queue = &self.queue_blocks; + let queue_blocks = &self.queue_blocks; let allowed_requests = self.allowed_requests.take(); let max_parallel = if is_major_syncing { 1 } else { self.max_parallel_downloads }; let max_blocks_per_request = self.max_blocks_per_request; let gap_sync = &mut self.gap_sync; let disconnected_peers = &mut self.disconnected_peers; + let metrics = self.metrics.as_ref(); self.peers .iter_mut() .filter_map(move |(&id, peer)| { @@ -1574,7 +1562,7 @@ where MAX_BLOCKS_TO_LOOK_BACKWARDS.into() && best_queued < peer.best_number && peer.common_number < last_finalized && - queue.len() <= MAJOR_SYNC_BLOCKS.into() + queue_blocks.len() <= MAJOR_SYNC_BLOCKS.into() { trace!( target: LOG_TARGET, @@ -1617,13 +1605,14 @@ where last_finalized, attrs, |hash| { - if queue.contains(hash) { + if queue_blocks.contains(hash) { BlockStatus::Queued } else { client.block_status(*hash).unwrap_or(BlockStatus::Unknown) } }, max_blocks_per_request, + metrics, ) { trace!(target: LOG_TARGET, "Downloading fork {hash:?} from {id}"); peer.state = PeerSyncState::DownloadingStale(hash); @@ -1764,7 +1753,11 @@ where let mut has_error = false; for (_, hash) in &results { - self.queue_blocks.remove(hash); + if self.queue_blocks.remove(hash) { + if let Some(metrics) = &self.metrics { + metrics.queued_blocks.dec(); + } + } self.blocks.clear_queued(hash); if let Some(gap_sync) = &mut self.gap_sync { gap_sync.blocks.clear_queued(hash); @@ -2094,14 +2087,15 @@ fn peer_gap_block_request( /// Get pending fork sync targets for a peer. fn fork_sync_request( id: &PeerId, - targets: &mut HashMap>, + fork_targets: &mut HashMap>, best_num: NumberFor, finalized: NumberFor, attributes: BlockAttributes, check_block: impl Fn(&B::Hash) -> BlockStatus, max_blocks_per_request: u32, + metrics: Option<&Metrics>, ) -> Option<(B::Hash, BlockRequest)> { - targets.retain(|hash, r| { + fork_targets.retain(|hash, r| { if r.number <= finalized { trace!( target: LOG_TARGET, @@ -2122,7 +2116,10 @@ fn fork_sync_request( } true }); - for (hash, r) in targets { + if let Some(metrics) = metrics { + metrics.fork_targets.set(fork_targets.len().try_into().unwrap_or(u64::MAX)); + } + for (hash, r) in fork_targets { if !r.peers.contains(&id) { continue } diff --git a/substrate/client/network/sync/src/strategy/chain_sync/test.rs b/substrate/client/network/sync/src/strategy/chain_sync/test.rs index cd955113542b..39d0c8f8d4d6 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync/test.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync/test.rs @@ -91,7 +91,7 @@ fn processes_empty_response_on_justification_request_for_unknown_block() { #[test] fn restart_doesnt_affect_peers_downloading_finality_data() { - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = Arc::new(TestClientBuilder::new().build()); // we request max 8 blocks to always initiate block requests to both peers for the test to be // deterministic @@ -103,7 +103,7 @@ fn restart_doesnt_affect_peers_downloading_finality_data() { let peer_id2 = PeerId::random(); let peer_id3 = PeerId::random(); - let mut new_blocks = |n| { + let new_blocks = |n| { for _ in 0..n { let block = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().best_hash) @@ -242,12 +242,12 @@ fn get_block_request( } /// Build and import a new best block. -fn build_block(client: &mut Arc, at: Option, fork: bool) -> Block { +fn build_block(client: &TestClient, at: Option, fork: bool) -> Block { let at = at.unwrap_or_else(|| client.info().best_hash); - let mut block_builder = BlockBuilderBuilder::new(&**client) + let mut block_builder = BlockBuilderBuilder::new(client) .on_parent_block(at) - .fetch_parent_block_number(&**client) + .fetch_parent_block_number(client) .unwrap() .build() .unwrap(); @@ -282,13 +282,13 @@ fn do_ancestor_search_when_common_block_to_best_queued_gap_is_to_big() { sp_tracing::try_init_simple(); let blocks = { - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = TestClientBuilder::new().build(); (0..MAX_DOWNLOAD_AHEAD * 2) - .map(|_| build_block(&mut client, None, false)) + .map(|_| build_block(&client, None, false)) .collect::>() }; - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = Arc::new(TestClientBuilder::new().build()); let info = client.info(); let mut sync = @@ -415,13 +415,13 @@ fn do_ancestor_search_when_common_block_to_best_queued_gap_is_to_big() { fn can_sync_huge_fork() { sp_tracing::try_init_simple(); - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = Arc::new(TestClientBuilder::new().build()); let blocks = (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 4) - .map(|_| build_block(&mut client, None, false)) + .map(|_| build_block(&client, None, false)) .collect::>(); let fork_blocks = { - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = TestClientBuilder::new().build(); let fork_blocks = blocks[..MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2] .into_iter() .inspect(|b| block_on(client.import(BlockOrigin::Own, (*b).clone())).unwrap()) @@ -431,8 +431,7 @@ fn can_sync_huge_fork() { fork_blocks .into_iter() .chain( - (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 2 + 1) - .map(|_| build_block(&mut client, None, true)), + (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 2 + 1).map(|_| build_block(&client, None, true)), ) .collect::>() }; @@ -550,13 +549,13 @@ fn can_sync_huge_fork() { fn syncs_fork_without_duplicate_requests() { sp_tracing::try_init_simple(); - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = Arc::new(TestClientBuilder::new().build()); let blocks = (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 4) - .map(|_| build_block(&mut client, None, false)) + .map(|_| build_block(&client, None, false)) .collect::>(); let fork_blocks = { - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = TestClientBuilder::new().build(); let fork_blocks = blocks[..MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2] .into_iter() .inspect(|b| block_on(client.import(BlockOrigin::Own, (*b).clone())).unwrap()) @@ -566,8 +565,7 @@ fn syncs_fork_without_duplicate_requests() { fork_blocks .into_iter() .chain( - (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 2 + 1) - .map(|_| build_block(&mut client, None, true)), + (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 2 + 1).map(|_| build_block(&client, None, true)), ) .collect::>() }; @@ -708,8 +706,8 @@ fn syncs_fork_without_duplicate_requests() { #[test] fn removes_target_fork_on_disconnect() { sp_tracing::try_init_simple(); - let mut client = Arc::new(TestClientBuilder::new().build()); - let blocks = (0..3).map(|_| build_block(&mut client, None, false)).collect::>(); + let client = Arc::new(TestClientBuilder::new().build()); + let blocks = (0..3).map(|_| build_block(&client, None, false)).collect::>(); let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) @@ -733,8 +731,8 @@ fn removes_target_fork_on_disconnect() { #[test] fn can_import_response_with_missing_blocks() { sp_tracing::try_init_simple(); - let mut client2 = Arc::new(TestClientBuilder::new().build()); - let blocks = (0..4).map(|_| build_block(&mut client2, None, false)).collect::>(); + let client2 = TestClientBuilder::new().build(); + let blocks = (0..4).map(|_| build_block(&client2, None, false)).collect::>(); let empty_client = Arc::new(TestClientBuilder::new().build()); @@ -770,14 +768,14 @@ fn ancestor_search_repeat() { #[test] fn sync_restart_removes_block_but_not_justification_requests() { - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = Arc::new(TestClientBuilder::new().build()); let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) .unwrap(); let peers = vec![PeerId::random(), PeerId::random()]; - let mut new_blocks = |n| { + let new_blocks = |n| { for _ in 0..n { let block = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().best_hash) @@ -880,11 +878,11 @@ fn sync_restart_removes_block_but_not_justification_requests() { fn request_across_forks() { sp_tracing::try_init_simple(); - let mut client = Arc::new(TestClientBuilder::new().build()); - let blocks = (0..100).map(|_| build_block(&mut client, None, false)).collect::>(); + let client = Arc::new(TestClientBuilder::new().build()); + let blocks = (0..100).map(|_| build_block(&client, None, false)).collect::>(); let fork_a_blocks = { - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = TestClientBuilder::new().build(); let mut fork_blocks = blocks[..] .into_iter() .inspect(|b| { @@ -894,13 +892,13 @@ fn request_across_forks() { .cloned() .collect::>(); for _ in 0..10 { - fork_blocks.push(build_block(&mut client, None, false)); + fork_blocks.push(build_block(&client, None, false)); } fork_blocks }; let fork_b_blocks = { - let mut client = Arc::new(TestClientBuilder::new().build()); + let client = TestClientBuilder::new().build(); let mut fork_blocks = blocks[..] .into_iter() .inspect(|b| { @@ -910,7 +908,7 @@ fn request_across_forks() { .cloned() .collect::>(); for _ in 0..10 { - fork_blocks.push(build_block(&mut client, None, true)); + fork_blocks.push(build_block(&client, None, true)); } fork_blocks }; diff --git a/substrate/client/network/sync/src/strategy/state.rs b/substrate/client/network/sync/src/strategy/state.rs index ff229863a68b..6f06f238fe3a 100644 --- a/substrate/client/network/sync/src/strategy/state.rs +++ b/substrate/client/network/sync/src/strategy/state.rs @@ -340,7 +340,6 @@ impl StateStrategy { }, best_seen_block: Some(self.state_sync.target_number()), num_peers: self.peers.len().saturated_into(), - num_connected_peers: self.peers.len().saturated_into(), queued_blocks: 0, state_sync: Some(self.state_sync.progress()), warp_sync: None, diff --git a/substrate/client/network/sync/src/strategy/warp.rs b/substrate/client/network/sync/src/strategy/warp.rs index 00855578695d..99405c2e5f08 100644 --- a/substrate/client/network/sync/src/strategy/warp.rs +++ b/substrate/client/network/sync/src/strategy/warp.rs @@ -26,7 +26,6 @@ use crate::{ LOG_TARGET, }; use codec::{Decode, Encode}; -use futures::channel::oneshot; use log::{debug, error, trace}; use sc_network_common::sync::message::{ BlockAnnounce, BlockAttributes, BlockData, BlockRequest, Direction, FromBlock, @@ -104,8 +103,6 @@ mod rep { pub enum WarpSyncPhase { /// Waiting for peers to connect. AwaitingPeers { required_peers: usize }, - /// Waiting for target block to be received. - AwaitingTargetBlock, /// Downloading and verifying grandpa warp proofs. DownloadingWarpProofs, /// Downloading target block. @@ -125,7 +122,6 @@ impl fmt::Display for WarpSyncPhase { match self { Self::AwaitingPeers { required_peers } => write!(f, "Waiting for {required_peers} peers to be connected"), - Self::AwaitingTargetBlock => write!(f, "Waiting for target block to be received"), Self::DownloadingWarpProofs => write!(f, "Downloading finality proofs"), Self::DownloadingTargetBlock => write!(f, "Downloading target block"), Self::DownloadingState => write!(f, "Downloading state"), @@ -145,37 +141,14 @@ pub struct WarpSyncProgress { pub total_bytes: u64, } -/// The different types of warp syncing, passed to `build_network`. -pub enum WarpSyncParams { - /// Standard warp sync for the chain. - WithProvider(Arc>), - /// Skip downloading proofs and wait for a header of the state that should be downloaded. - /// - /// It is expected that the header provider ensures that the header is trusted. - WaitForTarget(oneshot::Receiver<::Header>), -} - /// Warp sync configuration as accepted by [`WarpSync`]. pub enum WarpSyncConfig { /// Standard warp sync for the chain. WithProvider(Arc>), - /// Skip downloading proofs and wait for a header of the state that should be downloaded. + /// Skip downloading proofs and use provided header of the state that should be downloaded. /// /// It is expected that the header provider ensures that the header is trusted. - WaitForTarget, -} - -impl WarpSyncParams { - /// Split `WarpSyncParams` into `WarpSyncConfig` and warp sync target block header receiver. - pub fn split( - self, - ) -> (WarpSyncConfig, Option::Header>>) { - match self { - WarpSyncParams::WithProvider(provider) => - (WarpSyncConfig::WithProvider(provider), None), - WarpSyncParams::WaitForTarget(rx) => (WarpSyncConfig::WaitForTarget, Some(rx)), - } - } + WithTarget(::Header), } /// Warp sync phase used by warp sync state machine. @@ -189,9 +162,6 @@ enum Phase { last_hash: B::Hash, warp_sync_provider: Arc>, }, - /// Waiting for target block to be set externally if we skip warp proofs downloading, - /// and start straight from the target block (used by parachains warp sync). - PendingTargetBlock, /// Downloading target block. TargetBlock(B::Header), /// Warp sync is complete. @@ -274,7 +244,7 @@ where let phase = match warp_sync_config { WarpSyncConfig::WithProvider(warp_sync_provider) => Phase::WaitingForPeers { warp_sync_provider }, - WarpSyncConfig::WaitForTarget => Phase::PendingTargetBlock, + WarpSyncConfig::WithTarget(target_header) => Phase::TargetBlock(target_header), }; Self { @@ -289,20 +259,6 @@ where } } - /// Set target block externally in case we skip warp proof downloading. - pub fn set_target_block(&mut self, header: B::Header) { - let Phase::PendingTargetBlock = self.phase else { - error!( - target: LOG_TARGET, - "Attempt to set warp sync target block in invalid phase.", - ); - debug_assert!(false); - return - }; - - self.phase = Phase::TargetBlock(header); - } - /// Notify that a new peer has connected. pub fn add_peer(&mut self, peer_id: PeerId, _best_hash: B::Hash, best_number: NumberFor) { self.peers.insert(peer_id, Peer { best_number, state: PeerState::Available }); @@ -592,10 +548,6 @@ where phase: WarpSyncPhase::DownloadingTargetBlock, total_bytes: self.total_proof_bytes, }, - Phase::PendingTargetBlock { .. } => WarpSyncProgress { - phase: WarpSyncPhase::AwaitingTargetBlock, - total_bytes: self.total_proof_bytes, - }, Phase::Complete => WarpSyncProgress { phase: WarpSyncPhase::Complete, total_bytes: self.total_proof_bytes + self.total_state_bytes, @@ -614,19 +566,16 @@ where state: match &self.phase { Phase::WaitingForPeers { .. } => SyncState::Downloading { target: Zero::zero() }, Phase::WarpProof { .. } => SyncState::Downloading { target: Zero::zero() }, - Phase::PendingTargetBlock => SyncState::Downloading { target: Zero::zero() }, Phase::TargetBlock(header) => SyncState::Downloading { target: *header.number() }, Phase::Complete => SyncState::Idle, }, best_seen_block: match &self.phase { Phase::WaitingForPeers { .. } => None, Phase::WarpProof { .. } => None, - Phase::PendingTargetBlock => None, Phase::TargetBlock(header) => Some(*header.number()), Phase::Complete => None, }, num_peers: self.peers.len().saturated_into(), - num_connected_peers: self.peers.len().saturated_into(), queued_blocks: 0, state_sync: None, warp_sync: Some(self.progress()), @@ -759,7 +708,13 @@ mod test { #[test] fn warp_sync_to_target_for_db_with_finalized_state_is_noop() { let client = mock_client_with_state(); - let config = WarpSyncConfig::WaitForTarget; + let config = WarpSyncConfig::WithTarget(::Header::new( + 1, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + )); let mut warp_sync = WarpSync::new(Arc::new(client), config); // Warp sync instantly finishes @@ -785,7 +740,13 @@ mod test { #[test] fn warp_sync_to_target_for_empty_db_doesnt_finish_instantly() { let client = mock_client_without_state(); - let config = WarpSyncConfig::WaitForTarget; + let config = WarpSyncConfig::WithTarget(::Header::new( + 1, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + )); let mut warp_sync = WarpSync::new(Arc::new(client), config); // No actions are emitted. @@ -936,7 +897,13 @@ mod test { } // Manually set to another phase. - warp_sync.phase = Phase::PendingTargetBlock; + warp_sync.phase = Phase::TargetBlock(::Header::new( + 1, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + )); // No request is made. assert!(warp_sync.warp_proof_request().is_none()); @@ -1193,7 +1160,7 @@ mod test { .unwrap() .block; let target_header = target_block.header().clone(); - let config = WarpSyncConfig::WaitForTarget; + let config = WarpSyncConfig::WithTarget(target_header); let mut warp_sync = WarpSync::new(client, config); // Make sure we have enough peers to make a request. @@ -1201,10 +1168,6 @@ mod test { warp_sync.add_peer(PeerId::random(), Hash::random(), best_number); } - // No actions generated so far. - assert_eq!(warp_sync.actions().count(), 0); - - warp_sync.set_target_block(target_header); assert!(matches!(warp_sync.phase, Phase::TargetBlock(_))); let (_peer_id, request) = warp_sync.target_block_request().unwrap(); diff --git a/substrate/client/network/sync/src/types.rs b/substrate/client/network/sync/src/types.rs index e8b8c8900360..c3403fe1e5f7 100644 --- a/substrate/client/network/sync/src/types.rs +++ b/substrate/client/network/sync/src/types.rs @@ -39,7 +39,7 @@ pub struct PeerInfo { } /// Info about a peer's known state (both full and light). -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct ExtendedPeerInfo { /// Roles pub roles: Roles, @@ -49,6 +49,17 @@ pub struct ExtendedPeerInfo { pub best_number: NumberFor, } +impl Clone for ExtendedPeerInfo +where + B: BlockT, +{ + fn clone(&self) -> Self { + Self { roles: self.roles, best_hash: self.best_hash, best_number: self.best_number } + } +} + +impl Copy for ExtendedPeerInfo where B: BlockT {} + /// Reported sync state. #[derive(Clone, Eq, PartialEq, Debug)] pub enum SyncState { @@ -76,8 +87,6 @@ pub struct SyncStatus { pub best_seen_block: Option>, /// Number of peers participating in syncing. pub num_peers: u32, - /// Number of peers known to `SyncingEngine` (both full and light). - pub num_connected_peers: u32, /// Number of blocks queued for import pub queued_blocks: u32, /// State sync status in progress, if any. diff --git a/substrate/client/network/test/src/block_import.rs b/substrate/client/network/test/src/block_import.rs index 690a579e0272..8c120d9de662 100644 --- a/substrate/client/network/test/src/block_import.rs +++ b/substrate/client/network/test/src/block_import.rs @@ -32,7 +32,7 @@ use substrate_test_runtime_client::{ }; fn prepare_good_block() -> (TestClient, Hash, u64, PeerId, IncomingBlock) { - let mut client = substrate_test_runtime_client::new(); + let client = substrate_test_runtime_client::new(); let block = BlockBuilderBuilder::new(&client) .on_parent_block(client.chain_info().best_hash) .with_parent_block_number(client.chain_info().best_number) @@ -78,7 +78,7 @@ fn import_single_good_block_works() { &mut substrate_test_runtime_client::new(), BlockOrigin::File, block, - &mut PassThroughVerifier::new(true), + &PassThroughVerifier::new(true), )) { Ok(BlockImportStatus::ImportedUnknown(ref num, ref aux, ref org)) if *num == number && *aux == expected_aux && *org == Some(peer_id.into()) => {}, @@ -93,7 +93,7 @@ fn import_single_good_known_block_is_ignored() { &mut client, BlockOrigin::File, block, - &mut PassThroughVerifier::new(true), + &PassThroughVerifier::new(true), )) { Ok(BlockImportStatus::ImportedKnown(ref n, _)) if *n == number => {}, _ => panic!(), @@ -108,7 +108,7 @@ fn import_single_good_block_without_header_fails() { &mut substrate_test_runtime_client::new(), BlockOrigin::File, block, - &mut PassThroughVerifier::new(true), + &PassThroughVerifier::new(true), )) { Err(BlockImportError::IncompleteHeader(ref org)) if *org == Some(peer_id.into()) => {}, _ => panic!(), diff --git a/substrate/client/network/test/src/lib.rs b/substrate/client/network/test/src/lib.rs index 6ac20842a33f..f84f353fb4a0 100644 --- a/substrate/client/network/test/src/lib.rs +++ b/substrate/client/network/test/src/lib.rs @@ -34,7 +34,7 @@ use std::{ time::Duration, }; -use futures::{channel::oneshot, future::BoxFuture, pin_mut, prelude::*}; +use futures::{future::BoxFuture, pin_mut, prelude::*}; use libp2p::PeerId; use log::trace; use parking_lot::Mutex; @@ -67,7 +67,7 @@ use sc_network_sync::{ service::{network::NetworkServiceProvider, syncing_service::SyncingService}, state_request_handler::StateRequestHandler, strategy::warp::{ - AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncParams, WarpSyncProvider, + AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncConfig, WarpSyncProvider, }, warp_request_handler, }; @@ -217,7 +217,7 @@ impl BlockImport for PeersClient { } async fn import_block( - &mut self, + &self, block: BlockImportParams, ) -> Result { self.client.import_block(block).await @@ -266,7 +266,7 @@ where /// Returns the number of peers we're connected to. pub async fn num_peers(&self) -> usize { - self.sync_service.status().await.unwrap().num_connected_peers as usize + self.sync_service.num_connected_peers() } /// Returns the number of downloaded blocks. @@ -607,7 +607,7 @@ where } async fn import_block( - &mut self, + &self, block: BlockImportParams, ) -> Result { self.inner.import_block(block).await @@ -701,7 +701,7 @@ pub struct FullPeerConfig { /// Enable transaction indexing. pub storage_chain: bool, /// Optional target block header to sync to - pub target_block: Option<::Header>, + pub target_header: Option<::Header>, /// Force genesis even in case of warp & light state sync. pub force_genesis: bool, } @@ -865,13 +865,9 @@ pub trait TestNetFactory: Default + Sized + Send { let warp_sync = Arc::new(TestWarpSyncProvider(client.clone())); - let warp_sync_params = match config.target_block { - Some(target_block) => { - let (sender, receiver) = oneshot::channel::<::Header>(); - let _ = sender.send(target_block); - WarpSyncParams::WaitForTarget(receiver) - }, - _ => WarpSyncParams::WithProvider(warp_sync.clone()), + let warp_sync_config = match config.target_header { + Some(target_header) => WarpSyncConfig::WithTarget(target_header), + _ => WarpSyncConfig::WithProvider(warp_sync.clone()), }; let warp_protocol_config = { @@ -919,7 +915,7 @@ pub trait TestNetFactory: Default + Sized + Send { protocol_id.clone(), &fork_id, block_announce_validator, - Some(warp_sync_params), + Some(warp_sync_config), chain_sync_network_handle, import_queue.service(), block_relay_params.downloader, @@ -1020,7 +1016,7 @@ pub trait TestNetFactory: Default + Sized + Send { for peer in peers { if peer.sync_service.is_major_syncing() || - peer.sync_service.num_queued_blocks().await.unwrap() != 0 + peer.sync_service.status().await.unwrap().queued_blocks != 0 { return false } @@ -1040,7 +1036,7 @@ pub trait TestNetFactory: Default + Sized + Send { async fn is_idle(&mut self) -> bool { let peers = self.peers_mut(); for peer in peers { - if peer.sync_service.num_queued_blocks().await.unwrap() != 0 { + if peer.sync_service.status().await.unwrap().queued_blocks != 0 { return false } if peer.sync_service.num_sync_requests().await.unwrap() != 0 { @@ -1098,9 +1094,7 @@ pub trait TestNetFactory: Default + Sized + Send { 'outer: loop { for sync_service in &sync_services { - if sync_service.status().await.unwrap().num_connected_peers as usize != - num_peers - 1 - { + if sync_service.num_connected_peers() != num_peers - 1 { futures::future::poll_fn::<(), _>(|cx| { self.poll(cx); Poll::Ready(()) diff --git a/substrate/client/network/test/src/sync.rs b/substrate/client/network/test/src/sync.rs index f1c1b7414303..4244c49bf7fb 100644 --- a/substrate/client/network/test/src/sync.rs +++ b/substrate/client/network/test/src/sync.rs @@ -1049,7 +1049,7 @@ async fn syncs_all_forks_from_single_peer() { }) .await; - if net.peer(1).sync_service().best_seen_block().await.unwrap() == Some(12) { + if net.peer(1).sync_service().status().await.unwrap().best_seen_block == Some(12) { break } } @@ -1298,7 +1298,7 @@ async fn warp_sync_to_target_block() { net.add_full_peer_with_config(FullPeerConfig { sync_mode: SyncMode::Warp, - target_block: Some(target_block), + target_header: Some(target_block), ..Default::default() }); diff --git a/substrate/client/network/transactions/src/lib.rs b/substrate/client/network/transactions/src/lib.rs index 31ad0781035e..a241041968fd 100644 --- a/substrate/client/network/transactions/src/lib.rs +++ b/substrate/client/network/transactions/src/lib.rs @@ -522,8 +522,14 @@ where return } - debug!(target: LOG_TARGET, "Propagating transactions"); let transactions = self.transaction_pool.transactions(); + + if transactions.is_empty() { + return + } + + debug!(target: LOG_TARGET, "Propagating transactions"); + let propagated_to = self.do_propagate_transactions(&transactions); self.transaction_pool.on_broadcasted(propagated_to); } diff --git a/substrate/client/offchain/src/lib.rs b/substrate/client/offchain/src/lib.rs index 48d3b8f1393a..7cee64e6ce7e 100644 --- a/substrate/client/offchain/src/lib.rs +++ b/substrate/client/offchain/src/lib.rs @@ -481,7 +481,7 @@ mod tests { let (client, backend) = substrate_test_runtime_client::TestClientBuilder::new() .enable_offchain_indexing_api() .build_with_backend(); - let mut client = Arc::new(client); + let client = Arc::new(client); let offchain_db = backend.offchain_storage().unwrap(); let key = &b"hello"[..]; diff --git a/substrate/client/rpc-api/src/author/mod.rs b/substrate/client/rpc-api/src/author/mod.rs index cfc56f4130ab..c39d6c68355a 100644 --- a/substrate/client/rpc-api/src/author/mod.rs +++ b/substrate/client/rpc-api/src/author/mod.rs @@ -34,11 +34,11 @@ pub trait AuthorApi { async fn submit_extrinsic(&self, extrinsic: Bytes) -> Result; /// Insert a key into the keystore. - #[method(name = "author_insertKey")] + #[method(name = "author_insertKey", with_extensions)] fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<(), Error>; /// Generate new session keys and returns the corresponding public keys. - #[method(name = "author_rotateKeys")] + #[method(name = "author_rotateKeys", with_extensions)] fn rotate_keys(&self) -> Result; /// Checks if the keystore has private keys for the given session public keys. @@ -46,13 +46,13 @@ pub trait AuthorApi { /// `session_keys` is the SCALE encoded session keys object from the runtime. /// /// Returns `true` iff all private keys could be found. - #[method(name = "author_hasSessionKeys")] + #[method(name = "author_hasSessionKeys", with_extensions)] fn has_session_keys(&self, session_keys: Bytes) -> Result; /// Checks if the keystore has private keys for the given public key and key type. /// /// Returns `true` if a private key could be found. - #[method(name = "author_hasKey")] + #[method(name = "author_hasKey", with_extensions)] fn has_key(&self, public_key: Bytes, key_type: String) -> Result; /// Returns all pending extrinsics, potentially grouped by sender. @@ -60,7 +60,7 @@ pub trait AuthorApi { fn pending_extrinsics(&self) -> Result, Error>; /// Remove given extrinsic from the pool and temporarily ban it to prevent reimporting. - #[method(name = "author_removeExtrinsic")] + #[method(name = "author_removeExtrinsic", with_extensions)] fn remove_extrinsic( &self, bytes_or_hash: Vec>, diff --git a/substrate/client/rpc-api/src/dev/mod.rs b/substrate/client/rpc-api/src/dev/mod.rs index 5bee6df73ba9..5c6208ff5d50 100644 --- a/substrate/client/rpc-api/src/dev/mod.rs +++ b/substrate/client/rpc-api/src/dev/mod.rs @@ -59,6 +59,6 @@ pub trait DevApi { /// This function requires the specified block and its parent to be available /// at the queried node. If either the specified block or the parent is pruned, /// this function will return `None`. - #[method(name = "dev_getBlockStats")] + #[method(name = "dev_getBlockStats", with_extensions)] fn block_stats(&self, block_hash: Hash) -> Result, Error>; } diff --git a/substrate/client/rpc-api/src/lib.rs b/substrate/client/rpc-api/src/lib.rs index 451ebdf7fc00..cc580fc0e7ca 100644 --- a/substrate/client/rpc-api/src/lib.rs +++ b/substrate/client/rpc-api/src/lib.rs @@ -25,7 +25,7 @@ mod error; mod policy; -pub use policy::{DenyUnsafe, UnsafeRpcError}; +pub use policy::{check_if_safe, DenyUnsafe, UnsafeRpcError}; pub mod author; pub mod chain; diff --git a/substrate/client/rpc-api/src/offchain/mod.rs b/substrate/client/rpc-api/src/offchain/mod.rs index 469e22d2b3fa..4dd5b066d49f 100644 --- a/substrate/client/rpc-api/src/offchain/mod.rs +++ b/substrate/client/rpc-api/src/offchain/mod.rs @@ -28,10 +28,10 @@ use sp_core::{offchain::StorageKind, Bytes}; #[rpc(client, server)] pub trait OffchainApi { /// Set offchain local storage under given key and prefix. - #[method(name = "offchain_localStorageSet")] + #[method(name = "offchain_localStorageSet", with_extensions)] fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<(), Error>; /// Get offchain local storage under given key and prefix. - #[method(name = "offchain_localStorageGet")] + #[method(name = "offchain_localStorageGet", with_extensions)] fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result, Error>; } diff --git a/substrate/client/rpc-api/src/policy.rs b/substrate/client/rpc-api/src/policy.rs index c0847de89d2c..7e0657db8d17 100644 --- a/substrate/client/rpc-api/src/policy.rs +++ b/substrate/client/rpc-api/src/policy.rs @@ -23,6 +23,15 @@ use jsonrpsee::types::{error::ErrorCode, ErrorObject, ErrorObjectOwned}; +/// Checks if the RPC call is safe to be called externally. +pub fn check_if_safe(ext: &jsonrpsee::Extensions) -> Result<(), UnsafeRpcError> { + match ext.get::().map(|deny_unsafe| deny_unsafe.check_if_safe()) { + Some(Ok(())) => Ok(()), + Some(Err(e)) => Err(e), + None => unreachable!("DenyUnsafe extension is always set by the substrate rpc server; qed"), + } +} + /// Signifies whether a potentially unsafe RPC should be denied. #[derive(Clone, Copy, Debug)] pub enum DenyUnsafe { diff --git a/substrate/client/rpc-api/src/state/mod.rs b/substrate/client/rpc-api/src/state/mod.rs index e38e383c4c15..ee62387b6bd4 100644 --- a/substrate/client/rpc-api/src/state/mod.rs +++ b/substrate/client/rpc-api/src/state/mod.rs @@ -48,7 +48,7 @@ pub trait StateApi { ) -> Result, Error>; /// Returns the keys with prefix, leave empty to get all the keys - #[method(name = "state_getPairs", blocking)] + #[method(name = "state_getPairs", blocking, with_extensions)] fn storage_pairs( &self, prefix: StorageKey, @@ -76,7 +76,7 @@ pub trait StateApi { fn storage_hash(&self, key: StorageKey, hash: Option) -> Result, Error>; /// Returns the size of a storage entry at a block's state. - #[method(name = "state_getStorageSize", aliases = ["state_getStorageSizeAt"])] + #[method(name = "state_getStorageSize", aliases = ["state_getStorageSizeAt"], with_extensions)] async fn storage_size(&self, key: StorageKey, hash: Option) -> Result, Error>; @@ -95,7 +95,7 @@ pub trait StateApi { /// Subsequent values in the vector represent changes to the previous state (diffs). /// WARNING: The time complexity of this query is O(|keys|*dist(block, hash)), and the /// memory complexity is O(dist(block, hash)) -- use with caution. - #[method(name = "state_queryStorage", blocking)] + #[method(name = "state_queryStorage", blocking, with_extensions)] fn query_storage( &self, keys: Vec, @@ -136,6 +136,7 @@ pub trait StateApi { name = "state_subscribeStorage" => "state_storage", unsubscribe = "state_unsubscribeStorage", item = StorageChangeSet, + with_extensions, )] fn subscribe_storage(&self, keys: Option>); @@ -291,7 +292,7 @@ pub trait StateApi { /// /// If you are having issues with maximum payload size you can use the flag /// `-ltracing=trace` to get some logging during tracing. - #[method(name = "state_traceBlock", blocking)] + #[method(name = "state_traceBlock", blocking, with_extensions)] fn trace_block( &self, block: Hash, diff --git a/substrate/client/rpc-api/src/statement/mod.rs b/substrate/client/rpc-api/src/statement/mod.rs index 39ec52cbea01..eee58f506e16 100644 --- a/substrate/client/rpc-api/src/statement/mod.rs +++ b/substrate/client/rpc-api/src/statement/mod.rs @@ -27,7 +27,7 @@ pub mod error; #[rpc(client, server)] pub trait StatementApi { /// Return all statements, SCALE-encoded. - #[method(name = "statement_dump")] + #[method(name = "statement_dump", with_extensions)] fn dump(&self) -> RpcResult>; /// Return the data of all known statements which include all topics and have no `DecryptionKey` diff --git a/substrate/client/rpc-api/src/system/mod.rs b/substrate/client/rpc-api/src/system/mod.rs index c38fa8f3d817..2d6d48e1ddb4 100644 --- a/substrate/client/rpc-api/src/system/mod.rs +++ b/substrate/client/rpc-api/src/system/mod.rs @@ -69,7 +69,7 @@ pub trait SystemApi { async fn system_local_listen_addresses(&self) -> Result, Error>; /// Returns currently connected peers - #[method(name = "system_peers")] + #[method(name = "system_peers", with_extensions)] async fn system_peers(&self) -> Result>, Error>; /// Returns current state of the network. @@ -78,7 +78,7 @@ pub trait SystemApi { /// as its format might change at any time. // TODO: the future of this call is uncertain: https://github.com/paritytech/substrate/issues/1890 // https://github.com/paritytech/substrate/issues/5541 - #[method(name = "system_unstable_networkState")] + #[method(name = "system_unstable_networkState", with_extensions)] async fn system_network_state(&self) -> Result; /// Adds a reserved peer. Returns the empty string or an error. The string @@ -86,12 +86,12 @@ pub trait SystemApi { /// /// `/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV` /// is an example of a valid, passing multiaddr with PeerId attached. - #[method(name = "system_addReservedPeer")] + #[method(name = "system_addReservedPeer", with_extensions)] async fn system_add_reserved_peer(&self, peer: String) -> Result<(), Error>; /// Remove a reserved peer. Returns the empty string or an error. The string /// should encode only the PeerId e.g. `QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV`. - #[method(name = "system_removeReservedPeer")] + #[method(name = "system_removeReservedPeer", with_extensions)] async fn system_remove_reserved_peer(&self, peer_id: String) -> Result<(), Error>; /// Returns the list of reserved peers @@ -112,10 +112,10 @@ pub trait SystemApi { /// The syntax is identical to the CLI `=`: /// /// `sync=debug,state=trace` - #[method(name = "system_addLogFilter")] + #[method(name = "system_addLogFilter", with_extensions)] fn system_add_log_filter(&self, directives: String) -> Result<(), Error>; /// Resets the log filter to Substrate defaults - #[method(name = "system_resetLogFilter")] + #[method(name = "system_resetLogFilter", with_extensions)] fn system_reset_log_filter(&self) -> Result<(), Error>; } diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index 7e3d3bf55b74..b39c3fdda67c 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -16,21 +16,20 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +dyn-clone = { workspace = true } forwarded-header-value = { workspace = true } futures = { workspace = true } governor = { workspace = true } http = { workspace = true } http-body-util = { workspace = true } +hyper = { workspace = true } ip_network = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } log = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +sc-rpc-api = { workspace = true } serde = { workspace = true } serde_json = { workspace = true, default-features = true } tokio = { features = ["parking_lot"], workspace = true, default-features = true } tower = { workspace = true, features = ["util"] } tower-http = { workspace = true, features = ["cors"] } - -# Dependencies outside the polkadot-sdk workspace -# which requires hyper v1 -hyper = "1.3" diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index 0bae16b113df..ca74c2371c25 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -23,246 +23,255 @@ pub mod middleware; pub mod utils; -use std::{error::Error as StdError, net::SocketAddr, num::NonZeroU32, sync::Arc, time::Duration}; +use std::{error::Error as StdError, time::Duration}; use jsonrpsee::{ core::BoxError, - server::{ - serve_with_graceful_shutdown, stop_channel, ws, PingConfig, StopHandle, TowerServiceBuilder, - }, + server::{serve_with_graceful_shutdown, stop_channel, ws, PingConfig, StopHandle}, Methods, RpcModule, }; use middleware::NodeHealthProxyLayer; -use tokio::net::TcpListener; use tower::Service; -use utils::{build_rpc_api, format_cors, get_proxy_ip, host_filtering, try_into_cors}; +use utils::{ + build_rpc_api, deny_unsafe, format_listen_addrs, get_proxy_ip, ListenAddrError, RpcSettings, +}; pub use ip_network::IpNetwork; pub use jsonrpsee::{ - core::{ - id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, - traits::IdProvider, - }, + core::id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, server::{middleware::rpc::RpcServiceBuilder, BatchRequestConfig}, }; pub use middleware::{Metrics, MiddlewareLayer, RpcMetrics}; +pub use utils::{RpcEndpoint, RpcMethods}; const MEGABYTE: u32 = 1024 * 1024; /// Type alias for the JSON-RPC server. pub type Server = jsonrpsee::server::ServerHandle; +/// Trait for providing subscription IDs that can be cloned. +pub trait SubscriptionIdProvider: + jsonrpsee::core::traits::IdProvider + dyn_clone::DynClone +{ +} + +dyn_clone::clone_trait_object!(SubscriptionIdProvider); + /// RPC server configuration. #[derive(Debug)] -pub struct Config<'a, M: Send + Sync + 'static> { - /// Socket addresses. - pub addrs: [SocketAddr; 2], - /// CORS. - pub cors: Option<&'a Vec>, - /// Maximum connections. - pub max_connections: u32, - /// Maximum subscriptions per connection. - pub max_subs_per_conn: u32, - /// Maximum rpc request payload size. - pub max_payload_in_mb: u32, - /// Maximum rpc response payload size. - pub max_payload_out_mb: u32, +pub struct Config { + /// RPC interfaces to start. + pub endpoints: Vec, /// Metrics. pub metrics: Option, - /// Message buffer size - pub message_buffer_capacity: u32, /// RPC API. pub rpc_api: RpcModule, /// Subscription ID provider. - pub id_provider: Option>, + pub id_provider: Option>, /// Tokio runtime handle. pub tokio_handle: tokio::runtime::Handle, - /// Batch request config. - pub batch_config: BatchRequestConfig, - /// Rate limit calls per minute. - pub rate_limit: Option, - /// Disable rate limit for certain ips. - pub rate_limit_whitelisted_ips: Vec, - /// Trust proxy headers for rate limiting. - pub rate_limit_trust_proxy_headers: bool, } #[derive(Debug, Clone)] -struct PerConnection { +struct PerConnection { methods: Methods, stop_handle: StopHandle, metrics: Option, tokio_handle: tokio::runtime::Handle, - service_builder: TowerServiceBuilder, - rate_limit_whitelisted_ips: Arc>, } /// Start RPC server listening on given address. -pub async fn start_server( - config: Config<'_, M>, -) -> Result> +pub async fn start_server(config: Config) -> Result> where M: Send + Sync, { - let Config { - addrs, - batch_config, - cors, - max_payload_in_mb, - max_payload_out_mb, - max_connections, - max_subs_per_conn, - metrics, - message_buffer_capacity, - id_provider, - tokio_handle, - rpc_api, - rate_limit, - rate_limit_whitelisted_ips, - rate_limit_trust_proxy_headers, - } = config; - - let listener = TcpListener::bind(addrs.as_slice()).await?; - let local_addr = listener.local_addr().ok(); - let host_filter = host_filtering(cors.is_some(), local_addr); - - let http_middleware = tower::ServiceBuilder::new() - .option_layer(host_filter) - // Proxy `GET /health, /health/readiness` requests to the internal `system_health` method. - .layer(NodeHealthProxyLayer::default()) - .layer(try_into_cors(cors)?); - - let mut builder = jsonrpsee::server::Server::builder() - .max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE)) - .max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE)) - .max_connections(max_connections) - .max_subscriptions_per_connection(max_subs_per_conn) - .enable_ws_ping( - PingConfig::new() - .ping_interval(Duration::from_secs(30)) - .inactive_limit(Duration::from_secs(60)) - .max_failures(3), - ) - .set_http_middleware(http_middleware) - .set_message_buffer_capacity(message_buffer_capacity) - .set_batch_request_config(batch_config) - .custom_tokio_runtime(tokio_handle.clone()); - - if let Some(provider) = id_provider { - builder = builder.set_id_provider(provider); - } else { - builder = builder.set_id_provider(RandomStringIdProvider::new(16)); - }; + let Config { endpoints, metrics, tokio_handle, rpc_api, id_provider } = config; let (stop_handle, server_handle) = stop_channel(); let cfg = PerConnection { methods: build_rpc_api(rpc_api).into(), - service_builder: builder.to_service_builder(), metrics, tokio_handle: tokio_handle.clone(), stop_handle, - rate_limit_whitelisted_ips: Arc::new(rate_limit_whitelisted_ips), }; - tokio_handle.spawn(async move { - loop { - let (sock, remote_addr) = tokio::select! { - res = listener.accept() => { - match res { - Ok(s) => s, - Err(e) => { - log::debug!(target: "rpc", "Failed to accept ipv4 connection: {:?}", e); - continue; + let mut local_addrs = Vec::new(); + + for endpoint in endpoints { + let allowed_to_fail = endpoint.is_optional; + let local_addr = endpoint.listen_addr; + + let mut listener = match endpoint.bind().await { + Ok(l) => l, + Err(e) if allowed_to_fail => { + log::debug!(target: "rpc", "JSON-RPC server failed to bind optional address: {:?}, error: {:?}", local_addr, e); + continue; + }, + Err(e) => return Err(e), + }; + let local_addr = listener.local_addr(); + local_addrs.push(local_addr); + let cfg = cfg.clone(); + + let mut id_provider2 = id_provider.clone(); + + tokio_handle.spawn(async move { + loop { + let (sock, remote_addr, rpc_cfg) = tokio::select! { + res = listener.accept() => { + match res { + Ok(s) => s, + Err(e) => { + log::debug!(target: "rpc", "Failed to accept connection: {:?}", e); + continue; + } } } - } - _ = cfg.stop_handle.clone().shutdown() => break, - }; - - let ip = remote_addr.ip(); - let cfg2 = cfg.clone(); - let svc = tower::service_fn(move |req: http::Request| { - let PerConnection { - methods, - service_builder, - metrics, - tokio_handle, - stop_handle, - rate_limit_whitelisted_ips, - } = cfg2.clone(); - - let proxy_ip = - if rate_limit_trust_proxy_headers { get_proxy_ip(&req) } else { None }; - - let rate_limit_cfg = if rate_limit_whitelisted_ips - .iter() - .any(|ips| ips.contains(proxy_ip.unwrap_or(ip))) - { - log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is trusted, disabling rate-limit", proxy_ip); - None - } else { - if !rate_limit_whitelisted_ips.is_empty() { - log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is not trusted, rate-limit enabled", proxy_ip); - } - rate_limit + _ = cfg.stop_handle.clone().shutdown() => break, }; - let is_websocket = ws::is_upgrade_request(&req); - let transport_label = if is_websocket { "ws" } else { "http" }; - - let middleware_layer = match (metrics, rate_limit_cfg) { - (None, None) => None, - (Some(metrics), None) => Some( - MiddlewareLayer::new().with_metrics(Metrics::new(metrics, transport_label)), - ), - (None, Some(rate_limit)) => - Some(MiddlewareLayer::new().with_rate_limit_per_minute(rate_limit)), - (Some(metrics), Some(rate_limit)) => Some( - MiddlewareLayer::new() - .with_metrics(Metrics::new(metrics, transport_label)) - .with_rate_limit_per_minute(rate_limit), - ), + let RpcSettings { + batch_config, + max_connections, + max_payload_in_mb, + max_payload_out_mb, + max_buffer_capacity_per_connection, + max_subscriptions_per_connection, + rpc_methods, + rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips, + host_filter, + cors, + rate_limit, + } = rpc_cfg; + + let http_middleware = tower::ServiceBuilder::new() + .option_layer(host_filter) + // Proxy `GET /health, /health/readiness` requests to the internal + // `system_health` method. + .layer(NodeHealthProxyLayer::default()) + .layer(cors); + + let mut builder = jsonrpsee::server::Server::builder() + .max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE)) + .max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE)) + .max_connections(max_connections) + .max_subscriptions_per_connection(max_subscriptions_per_connection) + .enable_ws_ping( + PingConfig::new() + .ping_interval(Duration::from_secs(30)) + .inactive_limit(Duration::from_secs(60)) + .max_failures(3), + ) + .set_http_middleware(http_middleware) + .set_message_buffer_capacity(max_buffer_capacity_per_connection) + .set_batch_request_config(batch_config) + .custom_tokio_runtime(cfg.tokio_handle.clone()) + .set_id_provider(RandomStringIdProvider::new(16)); + + if let Some(provider) = id_provider2.take() { + builder = builder.set_id_provider(provider); + } else { + builder = builder.set_id_provider(RandomStringIdProvider::new(16)); }; - let rpc_middleware = RpcServiceBuilder::new() - .rpc_logger(1024) - .option_layer(middleware_layer.clone()); - let mut svc = - service_builder.set_rpc_middleware(rpc_middleware).build(methods, stop_handle); - - async move { - if is_websocket { - let on_disconnect = svc.on_session_closed(); - - // Spawn a task to handle when the connection is closed. - tokio_handle.spawn(async move { - let now = std::time::Instant::now(); - middleware_layer.as_ref().map(|m| m.ws_connect()); - on_disconnect.await; - middleware_layer.as_ref().map(|m| m.ws_disconnect(now)); - }); - } - - // https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred - // to be `Box` so we need to convert it to - // a concrete type as workaround. - svc.call(req).await.map_err(|e| BoxError::from(e)) - } - }); - - cfg.tokio_handle.spawn(serve_with_graceful_shutdown( - sock, - svc, - cfg.stop_handle.clone().shutdown(), - )); - } - }); - - log::info!( - "Running JSON-RPC server: addr={}, allowed origins={}", - local_addr.map_or_else(|| "unknown".to_string(), |a| a.to_string()), - format_cors(cors) - ); + let service_builder = builder.to_service_builder(); + let deny_unsafe = deny_unsafe(&local_addr, &rpc_methods); + + let ip = remote_addr.ip(); + let cfg2 = cfg.clone(); + let service_builder2 = service_builder.clone(); + + let svc = + tower::service_fn(move |mut req: http::Request| { + req.extensions_mut().insert(deny_unsafe); + + let PerConnection { methods, metrics, tokio_handle, stop_handle } = + cfg2.clone(); + let service_builder = service_builder2.clone(); + + let proxy_ip = + if rate_limit_trust_proxy_headers { get_proxy_ip(&req) } else { None }; + + let rate_limit_cfg = if rate_limit_whitelisted_ips + .iter() + .any(|ips| ips.contains(proxy_ip.unwrap_or(ip))) + { + log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is trusted, disabling rate-limit", proxy_ip); + None + } else { + if !rate_limit_whitelisted_ips.is_empty() { + log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is not trusted, rate-limit enabled", proxy_ip); + } + rate_limit + }; + + let is_websocket = ws::is_upgrade_request(&req); + let transport_label = if is_websocket { "ws" } else { "http" }; + + let middleware_layer = match (metrics, rate_limit_cfg) { + (None, None) => None, + (Some(metrics), None) => Some( + MiddlewareLayer::new() + .with_metrics(Metrics::new(metrics, transport_label)), + ), + (None, Some(rate_limit)) => + Some(MiddlewareLayer::new().with_rate_limit_per_minute(rate_limit)), + (Some(metrics), Some(rate_limit)) => Some( + MiddlewareLayer::new() + .with_metrics(Metrics::new(metrics, transport_label)) + .with_rate_limit_per_minute(rate_limit), + ), + }; + + let rpc_middleware = + RpcServiceBuilder::new().option_layer(middleware_layer.clone()); + let mut svc = service_builder + .set_rpc_middleware(rpc_middleware) + .build(methods, stop_handle); + + async move { + if is_websocket { + let on_disconnect = svc.on_session_closed(); + + // Spawn a task to handle when the connection is closed. + tokio_handle.spawn(async move { + let now = std::time::Instant::now(); + middleware_layer.as_ref().map(|m| m.ws_connect()); + on_disconnect.await; + middleware_layer.as_ref().map(|m| m.ws_disconnect(now)); + }); + } + + // https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred + // to be `Box` so we need to + // convert it to a concrete type as workaround. + svc.call(req).await.map_err(|e| BoxError::from(e)) + } + }); + + cfg.tokio_handle.spawn(serve_with_graceful_shutdown( + sock, + svc, + cfg.stop_handle.clone().shutdown(), + )); + } + }); + } + + if local_addrs.is_empty() { + return Err(Box::new(ListenAddrError)); + } + + // The previous logging format was before + // `Running JSON-RPC server: addr=127.0.0.1:9944, allowed origins=["*"]` + // + // The new format is `Running JSON-RPC server: addr=` + // with the exception that for a single address it will be `Running JSON-RPC server: addr=addr,` + // with a trailing comma. + // + // This is to make it work with old scripts/utils that parse the logs. + log::info!("Running JSON-RPC server: addr={}", format_listen_addrs(&local_addrs)); Ok(server_handle) } diff --git a/substrate/client/rpc-servers/src/utils.rs b/substrate/client/rpc-servers/src/utils.rs index d9d943c7c1fb..5b4a4bf22b95 100644 --- a/substrate/client/rpc-servers/src/utils.rs +++ b/substrate/client/rpc-servers/src/utils.rs @@ -18,29 +18,190 @@ //! Substrate RPC server utils. +use crate::BatchRequestConfig; use std::{ error::Error as StdError, net::{IpAddr, SocketAddr}, + num::NonZeroU32, str::FromStr, }; use forwarded_header_value::ForwardedHeaderValue; use http::header::{HeaderName, HeaderValue}; +use ip_network::IpNetwork; use jsonrpsee::{server::middleware::http::HostFilterLayer, RpcModule}; +use sc_rpc_api::DenyUnsafe; use tower_http::cors::{AllowOrigin, CorsLayer}; const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); const X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip"); const FORWARDED: HeaderName = HeaderName::from_static("forwarded"); -pub(crate) fn host_filtering(enabled: bool, addr: Option) -> Option { - // If the local_addr failed, fallback to wildcard. - let port = addr.map_or("*".to_string(), |p| p.port().to_string()); +#[derive(Debug)] +pub(crate) struct ListenAddrError; +impl std::error::Error for ListenAddrError {} + +impl std::fmt::Display for ListenAddrError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "No listen address was successfully bound") + } +} + +/// Available RPC methods. +#[derive(Debug, Copy, Clone)] +pub enum RpcMethods { + /// Allow only a safe subset of RPC methods. + Safe, + /// Expose every RPC method (even potentially unsafe ones). + Unsafe, + /// Automatically determine the RPC methods based on the connection. + Auto, +} + +impl Default for RpcMethods { + fn default() -> Self { + RpcMethods::Auto + } +} + +impl FromStr for RpcMethods { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "safe" => Ok(RpcMethods::Safe), + "unsafe" => Ok(RpcMethods::Unsafe), + "auto" => Ok(RpcMethods::Auto), + invalid => Err(format!("Invalid rpc methods {invalid}")), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct RpcSettings { + pub(crate) batch_config: BatchRequestConfig, + pub(crate) max_connections: u32, + pub(crate) max_payload_in_mb: u32, + pub(crate) max_payload_out_mb: u32, + pub(crate) max_subscriptions_per_connection: u32, + pub(crate) max_buffer_capacity_per_connection: u32, + pub(crate) rpc_methods: RpcMethods, + pub(crate) rate_limit: Option, + pub(crate) rate_limit_trust_proxy_headers: bool, + pub(crate) rate_limit_whitelisted_ips: Vec, + pub(crate) cors: CorsLayer, + pub(crate) host_filter: Option, +} + +/// Represent a single RPC endpoint with its configuration. +#[derive(Debug, Clone)] +pub struct RpcEndpoint { + /// Listen address. + pub listen_addr: SocketAddr, + /// Batch request configuration. + pub batch_config: BatchRequestConfig, + /// Maximum number of connections. + pub max_connections: u32, + /// Maximum inbound payload size in MB. + pub max_payload_in_mb: u32, + /// Maximum outbound payload size in MB. + pub max_payload_out_mb: u32, + /// Maximum number of subscriptions per connection. + pub max_subscriptions_per_connection: u32, + /// Maximum buffer capacity per connection. + pub max_buffer_capacity_per_connection: u32, + /// Rate limit per minute. + pub rate_limit: Option, + /// Whether to trust proxy headers for rate limiting. + pub rate_limit_trust_proxy_headers: bool, + /// Whitelisted IPs for rate limiting. + pub rate_limit_whitelisted_ips: Vec, + /// CORS. + pub cors: Option>, + /// RPC methods to expose. + pub rpc_methods: RpcMethods, + /// Whether it's an optional listening address i.e, it's ignored if it fails to bind. + /// For example substrate tries to bind both ipv4 and ipv6 addresses but some platforms + /// may not support ipv6. + pub is_optional: bool, + /// Whether to retry with a random port if the provided port is already in use. + pub retry_random_port: bool, +} + +impl RpcEndpoint { + /// Binds to the listen address. + pub(crate) async fn bind(self) -> Result> { + let listener = match tokio::net::TcpListener::bind(self.listen_addr).await { + Ok(listener) => listener, + Err(_) if self.retry_random_port => { + let mut addr = self.listen_addr; + addr.set_port(0); + + tokio::net::TcpListener::bind(addr).await? + }, + Err(e) => return Err(e.into()), + }; + let local_addr = listener.local_addr()?; + let host_filter = host_filtering(self.cors.is_some(), local_addr); + let cors = try_into_cors(self.cors)?; + + Ok(Listener { + listener, + local_addr, + cfg: RpcSettings { + batch_config: self.batch_config, + max_connections: self.max_connections, + max_payload_in_mb: self.max_payload_in_mb, + max_payload_out_mb: self.max_payload_out_mb, + max_subscriptions_per_connection: self.max_subscriptions_per_connection, + max_buffer_capacity_per_connection: self.max_buffer_capacity_per_connection, + rpc_methods: self.rpc_methods, + rate_limit: self.rate_limit, + rate_limit_trust_proxy_headers: self.rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: self.rate_limit_whitelisted_ips, + host_filter, + cors, + }, + }) + } +} + +/// TCP socket server with RPC settings. +pub(crate) struct Listener { + listener: tokio::net::TcpListener, + local_addr: SocketAddr, + cfg: RpcSettings, +} + +impl Listener { + /// Accepts a new connection. + pub(crate) async fn accept( + &mut self, + ) -> std::io::Result<(tokio::net::TcpStream, SocketAddr, RpcSettings)> { + let (sock, remote_addr) = self.listener.accept().await?; + Ok((sock, remote_addr, self.cfg.clone())) + } + + /// Returns the local address the listener is bound to. + pub fn local_addr(&self) -> SocketAddr { + self.local_addr + } +} + +pub(crate) fn host_filtering(enabled: bool, addr: SocketAddr) -> Option { if enabled { // NOTE: The listening addresses are whitelisted by default. - let hosts = - [format!("localhost:{port}"), format!("127.0.0.1:{port}"), format!("[::1]:{port}")]; + + let mut hosts = Vec::new(); + + if addr.is_ipv4() { + hosts.push(format!("localhost:{}", addr.port())); + hosts.push(format!("127.0.0.1:{}", addr.port())); + } else { + hosts.push(format!("[::1]:{}", addr.port())); + } + Some(HostFilterLayer::new(hosts).expect("Valid hosts; qed")) } else { None @@ -65,13 +226,15 @@ pub(crate) fn build_rpc_api(mut rpc_api: RpcModule) } pub(crate) fn try_into_cors( - maybe_cors: Option<&Vec>, + maybe_cors: Option>, ) -> Result> { if let Some(cors) = maybe_cors { let mut list = Vec::new(); + for origin in cors { - list.push(HeaderValue::from_str(origin)?); + list.push(HeaderValue::from_str(&origin)?) } + Ok(CorsLayer::new().allow_origin(AllowOrigin::list(list))) } else { // allow all cors @@ -79,14 +242,6 @@ pub(crate) fn try_into_cors( } } -pub(crate) fn format_cors(maybe_cors: Option<&Vec>) -> String { - if let Some(cors) = maybe_cors { - format!("{:?}", cors) - } else { - format!("{:?}", ["*"]) - } -} - /// Extracts the IP addr from the HTTP request. /// /// It is extracted in the following order: @@ -126,6 +281,34 @@ pub(crate) fn get_proxy_ip(req: &http::Request) -> Option { None } +/// Get the `deny_unsafe` setting based on the address and the RPC methods exposed by the interface. +pub fn deny_unsafe(addr: &SocketAddr, methods: &RpcMethods) -> DenyUnsafe { + match (addr.ip().is_loopback(), methods) { + | (_, RpcMethods::Unsafe) | (false, RpcMethods::Auto) => DenyUnsafe::No, + _ => DenyUnsafe::Yes, + } +} + +pub(crate) fn format_listen_addrs(addr: &[SocketAddr]) -> String { + let mut s = String::new(); + + let mut it = addr.iter().peekable(); + + while let Some(addr) = it.next() { + s.push_str(&addr.to_string()); + + if it.peek().is_some() { + s.push(','); + } + } + + if addr.len() == 1 { + s.push(','); + } + + s +} + #[cfg(test)] mod tests { use super::*; diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index 1ec7895e3088..ae21895de38d 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } +jsonrpsee = { workspace = true, features = ["client-core", "macros", "server-core"] } # Internal chain structures for "chain_spec". sc-chain-spec = { workspace = true, default-features = true } # Pool for submitting extrinsics required by "transaction" @@ -45,7 +45,7 @@ rand = { workspace = true, default-features = true } schnellru = { workspace = true } [dev-dependencies] -jsonrpsee = { features = ["server", "ws-client"], workspace = true } +jsonrpsee = { workspace = true, features = ["server", "ws-client"] } serde_json = { workspace = true, default-features = true } tokio = { features = ["macros"], workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index de71ed82a128..078016f5b3e2 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -96,7 +96,7 @@ async fn archive_genesis() { #[tokio::test] async fn archive_body() { - let (mut client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); // Invalid block hash. let invalid_hash = hex_string(&INVALID_HASH); @@ -130,7 +130,7 @@ async fn archive_body() { #[tokio::test] async fn archive_header() { - let (mut client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); // Invalid block hash. let invalid_hash = hex_string(&INVALID_HASH); @@ -176,7 +176,7 @@ async fn archive_finalized_height() { #[tokio::test] async fn archive_hash_by_height() { - let (mut client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); // Genesis height. let hashes: Vec = api.call("archive_unstable_hashByHeight", [0]).await.unwrap(); @@ -282,7 +282,7 @@ async fn archive_hash_by_height() { #[tokio::test] async fn archive_call() { - let (mut client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); let invalid_hash = hex_string(&INVALID_HASH); // Invalid parameter (non-hex). @@ -341,7 +341,7 @@ async fn archive_call() { #[tokio::test] async fn archive_storage_hashes_values() { - let (mut client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); let block = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().genesis_hash) @@ -431,7 +431,7 @@ async fn archive_storage_hashes_values() { #[tokio::test] async fn archive_storage_closest_merkle_value() { - let (mut client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); /// The core of this test. /// @@ -592,7 +592,7 @@ async fn archive_storage_closest_merkle_value() { #[tokio::test] async fn archive_storage_paginate_iterations() { // 1 iteration allowed before pagination kicks in. - let (mut client, api) = setup_api(1, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(1, MAX_QUERIED_LIMIT); // Import a new block with storage changes. let mut builder = BlockBuilderBuilder::new(&*client) @@ -787,7 +787,7 @@ async fn archive_storage_paginate_iterations() { #[tokio::test] async fn archive_storage_discarded_items() { // One query at a time - let (mut client, api) = setup_api(MAX_PAGINATION_LIMIT, 1); + let (client, api) = setup_api(MAX_PAGINATION_LIMIT, 1); // Import a new block with storage changes. let mut builder = BlockBuilderBuilder::new(&*client) diff --git a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs index d4d616f54dc8..14325b4fbb98 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs @@ -890,7 +890,7 @@ mod tests { } fn produce_blocks( - mut client: Arc>>, + client: Arc>>, num_blocks: usize, ) -> Vec<::Hash> { let mut blocks = Vec::with_capacity(num_blocks); diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index b195e05b6649..38f091471f87 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -137,7 +137,7 @@ async fn setup_api() -> ( CHILD_VALUE.to_vec(), ); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -186,7 +186,7 @@ async fn setup_api() -> ( } async fn import_block( - mut client: Arc>, + client: Arc>, parent_hash: ::Hash, parent_number: u64, ) -> Block { @@ -203,7 +203,7 @@ async fn import_block( } async fn import_best_block_with_tx( - mut client: Arc>, + client: Arc>, parent_hash: ::Hash, parent_number: u64, tx: Transfer, @@ -245,7 +245,7 @@ macro_rules! check_new_and_best_block_events { async fn follow_subscription_produces_blocks() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -316,7 +316,7 @@ async fn follow_subscription_produces_blocks() { async fn follow_with_runtime() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -469,7 +469,7 @@ async fn get_header() { #[tokio::test] async fn get_body() { - let (mut client, api, mut block_sub, sub_id, block) = setup_api().await; + let (client, api, mut block_sub, sub_id, block) = setup_api().await; let block_hash = format!("{:?}", block.header.hash()); let invalid_hash = hex_string(&INVALID_HASH); @@ -626,7 +626,7 @@ async fn call_runtime() { async fn call_runtime_without_flag() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -691,7 +691,7 @@ async fn call_runtime_without_flag() { #[tokio::test] async fn get_storage_hash() { - let (mut client, api, mut block_sub, sub_id, block) = setup_api().await; + let (client, api, mut block_sub, sub_id, block) = setup_api().await; let block_hash = format!("{:?}", block.header.hash()); let invalid_hash = hex_string(&INVALID_HASH); let key = hex_string(&KEY); @@ -835,7 +835,7 @@ async fn get_storage_hash() { #[tokio::test] async fn get_storage_multi_query_iter() { - let (mut client, api, mut block_sub, sub_id, block) = setup_api().await; + let (client, api, mut block_sub, sub_id, block) = setup_api().await; let key = hex_string(&KEY); // Import a new block with storage changes. @@ -959,7 +959,7 @@ async fn get_storage_multi_query_iter() { #[tokio::test] async fn get_storage_value() { - let (mut client, api, mut block_sub, sub_id, block) = setup_api().await; + let (client, api, mut block_sub, sub_id, block) = setup_api().await; let block_hash = format!("{:?}", block.hash()); let invalid_hash = hex_string(&INVALID_HASH); let key = hex_string(&KEY); @@ -1287,7 +1287,7 @@ async fn unique_operation_ids() { async fn separate_operation_ids_for_subscriptions() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -1375,7 +1375,7 @@ async fn separate_operation_ids_for_subscriptions() { async fn follow_generates_initial_blocks() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -1533,7 +1533,7 @@ async fn follow_generates_initial_blocks() { async fn follow_exceeding_pinned_blocks() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -1612,7 +1612,7 @@ async fn follow_exceeding_pinned_blocks() { async fn follow_with_unpin() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -1720,7 +1720,7 @@ async fn follow_with_unpin() { async fn unpin_duplicate_hashes() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -1825,7 +1825,7 @@ async fn unpin_duplicate_hashes() { async fn follow_with_multiple_unpin_hashes() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -1972,7 +1972,7 @@ async fn follow_with_multiple_unpin_hashes() { async fn follow_prune_best_block() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -2160,7 +2160,7 @@ async fn follow_prune_best_block() { async fn follow_forks_pruned_block() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -2322,7 +2322,7 @@ async fn follow_forks_pruned_block() { async fn follow_report_multiple_pruned_block() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let api = ChainHead::new( client.clone(), @@ -2559,7 +2559,7 @@ async fn pin_block_references() { ) .unwrap(); - let mut client = Arc::new( + let client = Arc::new( new_in_mem::<_, Block, _, RuntimeApi>( backend.clone(), executor, @@ -2705,7 +2705,7 @@ async fn pin_block_references() { async fn follow_finalized_before_new_block() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); let client_mock = Arc::new(ChainHeadMockClient::new(client.clone())); @@ -2823,7 +2823,7 @@ async fn ensure_operation_limits_works() { CHILD_VALUE.to_vec(), ); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); // Configure the chainHead with maximum 1 ongoing operations. let api = ChainHead::new( @@ -2930,7 +2930,7 @@ async fn check_continue_operation() { CHILD_VALUE.to_vec(), ); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); // Configure the chainHead with maximum 1 item before asking for pagination. let api = ChainHead::new( @@ -3115,7 +3115,7 @@ async fn stop_storage_operation() { CHILD_VALUE.to_vec(), ); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); // Configure the chainHead with maximum 1 item before asking for pagination. let api = ChainHead::new( @@ -3221,7 +3221,7 @@ async fn stop_storage_operation() { #[tokio::test] async fn storage_closest_merkle_value() { - let (mut client, api, mut sub, sub_id, block) = setup_api().await; + let (client, api, mut sub, sub_id, block) = setup_api().await; /// The core of this test. /// @@ -3414,7 +3414,7 @@ async fn storage_closest_merkle_value() { async fn chain_head_stop_all_subscriptions() { let builder = TestClientBuilder::new(); let backend = builder.backend(); - let mut client = Arc::new(builder.build()); + let client = Arc::new(builder.build()); // Configure the chainHead to stop all subscriptions on lagging distance of 5 blocks. let api = ChainHead::new( diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index 4a8f4b3ec630..6fe28a3873e9 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -43,7 +43,6 @@ sp-statement-store = { workspace = true, default-features = true } tokio = { workspace = true, default-features = true } [dev-dependencies] -sp-tracing = { workspace = true } assert_matches = { workspace = true } sc-block-builder = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } @@ -55,7 +54,6 @@ tokio = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } pretty_assertions = { workspace = true } -tracing-subscriber = { features = ["env-filter"], workspace = true } [features] test-helpers = [] diff --git a/substrate/client/rpc/src/author/mod.rs b/substrate/client/rpc/src/author/mod.rs index 2fc21a238bc9..731f4df2f6f3 100644 --- a/substrate/client/rpc/src/author/mod.rs +++ b/substrate/client/rpc/src/author/mod.rs @@ -30,8 +30,8 @@ use crate::{ use codec::{Decode, Encode}; use futures::TryFutureExt; -use jsonrpsee::{core::async_trait, types::ErrorObject, PendingSubscriptionSink}; -use sc_rpc_api::DenyUnsafe; +use jsonrpsee::{core::async_trait, types::ErrorObject, Extensions, PendingSubscriptionSink}; +use sc_rpc_api::check_if_safe; use sc_transaction_pool_api::{ error::IntoPoolError, BlockHash, InPoolTransaction, TransactionFor, TransactionPool, TransactionSource, TxHash, @@ -55,8 +55,6 @@ pub struct Author { pool: Arc

, /// The key store. keystore: KeystorePtr, - /// Whether to deny unsafe calls - deny_unsafe: DenyUnsafe, /// Executor to spawn subscriptions. executor: SubscriptionTaskExecutor, } @@ -67,10 +65,9 @@ impl Author { client: Arc, pool: Arc

, keystore: KeystorePtr, - deny_unsafe: DenyUnsafe, executor: SubscriptionTaskExecutor, ) -> Self { - Author { client, pool, keystore, deny_unsafe, executor } + Author { client, pool, keystore, executor } } } @@ -104,8 +101,14 @@ where }) } - fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<()> { - self.deny_unsafe.check_if_safe()?; + fn insert_key( + &self, + ext: &Extensions, + key_type: String, + suri: String, + public: Bytes, + ) -> Result<()> { + check_if_safe(ext)?; let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?; self.keystore @@ -114,8 +117,8 @@ where Ok(()) } - fn rotate_keys(&self) -> Result { - self.deny_unsafe.check_if_safe()?; + fn rotate_keys(&self, ext: &Extensions) -> Result { + check_if_safe(ext)?; let best_block_hash = self.client.info().best_hash; let mut runtime_api = self.client.runtime_api(); @@ -128,8 +131,8 @@ where .map_err(|api_err| Error::Client(Box::new(api_err)).into()) } - fn has_session_keys(&self, session_keys: Bytes) -> Result { - self.deny_unsafe.check_if_safe()?; + fn has_session_keys(&self, ext: &Extensions, session_keys: Bytes) -> Result { + check_if_safe(ext)?; let best_block_hash = self.client.info().best_hash; let keys = self @@ -142,8 +145,8 @@ where Ok(self.keystore.has_keys(&keys)) } - fn has_key(&self, public_key: Bytes, key_type: String) -> Result { - self.deny_unsafe.check_if_safe()?; + fn has_key(&self, ext: &Extensions, public_key: Bytes, key_type: String) -> Result { + check_if_safe(ext)?; let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?; Ok(self.keystore.has_keys(&[(public_key.to_vec(), key_type)])) @@ -155,9 +158,10 @@ where fn remove_extrinsic( &self, + ext: &Extensions, bytes_or_hash: Vec>>, ) -> Result>> { - self.deny_unsafe.check_if_safe()?; + check_if_safe(ext)?; let hashes = bytes_or_hash .into_iter() .map(|x| match x { diff --git a/substrate/client/rpc/src/author/tests.rs b/substrate/client/rpc/src/author/tests.rs index 6bcb3e7863c0..bde60960eaf4 100644 --- a/substrate/client/rpc/src/author/tests.rs +++ b/substrate/client/rpc/src/author/tests.rs @@ -22,6 +22,7 @@ use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; use codec::Encode; use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError, RpcModule}; +use sc_rpc_api::DenyUnsafe; use sc_transaction_pool::{BasicPool, FullChainApi}; use sc_transaction_pool_api::TransactionStatus; use sp_core::{ @@ -72,26 +73,27 @@ impl Default for TestSetup { } impl TestSetup { - fn author(&self) -> Author> { - Author { + fn to_rpc(&self) -> RpcModule>> { + let mut module = Author { client: self.client.clone(), pool: self.pool.clone(), keystore: self.keystore.clone(), - deny_unsafe: DenyUnsafe::No, executor: test_executor(), } + .into_rpc(); + module.extensions_mut().insert(DenyUnsafe::No); + module } fn into_rpc() -> RpcModule>> { - Self::default().author().into_rpc() + Self::default().to_rpc() } } #[tokio::test] async fn author_submit_transaction_should_not_cause_error() { - sp_tracing::init_for_tests(); - let author = TestSetup::default().author(); - let api = author.into_rpc(); + let api = TestSetup::into_rpc(); + let xt: Bytes = uxt(AccountKeyring::Alice, 1).encode().into(); let extrinsic_hash: H256 = blake2_256(&xt).into(); let response: H256 = api.call("author_submitExtrinsic", [xt.clone()]).await.unwrap(); @@ -179,7 +181,7 @@ async fn author_should_return_pending_extrinsics() { async fn author_should_remove_extrinsics() { const METHOD: &'static str = "author_removeExtrinsic"; let setup = TestSetup::default(); - let api = setup.author().into_rpc(); + let api = setup.to_rpc(); // Submit three extrinsics, then remove two of them (will cause the third to be removed as well, // having a higher nonce) @@ -214,7 +216,7 @@ async fn author_should_remove_extrinsics() { #[tokio::test] async fn author_should_insert_key() { let setup = TestSetup::default(); - let api = setup.author().into_rpc(); + let api = setup.to_rpc(); let suri = "//Alice"; let keypair = ed25519::Pair::from_string(suri, None).expect("generates keypair"); let params: (String, String, Bytes) = ( @@ -231,7 +233,7 @@ async fn author_should_insert_key() { #[tokio::test] async fn author_should_rotate_keys() { let setup = TestSetup::default(); - let api = setup.author().into_rpc(); + let api = setup.to_rpc(); let new_pubkeys: Bytes = api.call("author_rotateKeys", EmptyParams::new()).await.unwrap(); let session_keys = @@ -244,7 +246,6 @@ async fn author_should_rotate_keys() { #[tokio::test] async fn author_has_session_keys() { - // Setup let api = TestSetup::into_rpc(); // Add a valid session key @@ -255,7 +256,7 @@ async fn author_has_session_keys() { // Add a session key in a different keystore let non_existent_pubkeys: Bytes = { - let api2 = TestSetup::default().author().into_rpc(); + let api2 = TestSetup::into_rpc(); api2.call("author_rotateKeys", EmptyParams::new()) .await .expect("Rotates the keys") @@ -279,8 +280,6 @@ async fn author_has_session_keys() { #[tokio::test] async fn author_has_key() { - sp_tracing::init_for_tests(); - let api = TestSetup::into_rpc(); let suri = "//Alice"; let alice_keypair = ed25519::Pair::from_string(suri, None).expect("Generates keypair"); diff --git a/substrate/client/rpc/src/chain/tests.rs b/substrate/client/rpc/src/chain/tests.rs index afb81f709f7d..9d78aa88091d 100644 --- a/substrate/client/rpc/src/chain/tests.rs +++ b/substrate/client/rpc/src/chain/tests.rs @@ -72,7 +72,7 @@ async fn should_return_header() { #[tokio::test] async fn should_return_a_block() { - let mut client = Arc::new(substrate_test_runtime_client::new()); + let client = Arc::new(substrate_test_runtime_client::new()); let api = new_full(client.clone(), test_executor()).into_rpc(); let block = BlockBuilderBuilder::new(&*client) @@ -137,7 +137,7 @@ async fn should_return_a_block() { #[tokio::test] async fn should_return_block_hash() { - let mut client = Arc::new(substrate_test_runtime_client::new()); + let client = Arc::new(substrate_test_runtime_client::new()); let api = new_full(client.clone(), test_executor()).into_rpc(); let res: ListOrValue> = @@ -204,7 +204,7 @@ async fn should_return_block_hash() { #[tokio::test] async fn should_return_finalized_hash() { - let mut client = Arc::new(substrate_test_runtime_client::new()); + let client = Arc::new(substrate_test_runtime_client::new()); let api = new_full(client.clone(), test_executor()).into_rpc(); let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap(); @@ -248,7 +248,7 @@ async fn should_notify_about_finalized_block() { } async fn test_head_subscription(method: &str) { - let mut client = Arc::new(substrate_test_runtime_client::new()); + let client = Arc::new(substrate_test_runtime_client::new()); let mut sub = { let api = new_full(client.clone(), test_executor()).into_rpc(); diff --git a/substrate/client/rpc/src/dev/mod.rs b/substrate/client/rpc/src/dev/mod.rs index 424ef72b694e..3ccda760c3f5 100644 --- a/substrate/client/rpc/src/dev/mod.rs +++ b/substrate/client/rpc/src/dev/mod.rs @@ -22,8 +22,9 @@ #[cfg(test)] mod tests; +use jsonrpsee::Extensions; use sc_client_api::{BlockBackend, HeaderBackend}; -use sc_rpc_api::{dev::error::Error, DenyUnsafe}; +use sc_rpc_api::{check_if_safe, dev::error::Error}; use sp_api::{ApiExt, Core, ProvideRuntimeApi}; use sp_core::Encode; use sp_runtime::{ @@ -42,14 +43,13 @@ type HasherOf = <::Header as Header>::Hashing; /// The Dev API. All methods are unsafe. pub struct Dev { client: Arc, - deny_unsafe: DenyUnsafe, _phantom: PhantomData, } impl Dev { /// Create a new Dev API. - pub fn new(client: Arc, deny_unsafe: DenyUnsafe) -> Self { - Self { client, deny_unsafe, _phantom: PhantomData::default() } + pub fn new(client: Arc) -> Self { + Self { client, _phantom: PhantomData::default() } } } @@ -64,8 +64,12 @@ where + 'static, Client::Api: Core, { - fn block_stats(&self, hash: Block::Hash) -> Result, Error> { - self.deny_unsafe.check_if_safe()?; + fn block_stats( + &self, + ext: &Extensions, + hash: Block::Hash, + ) -> Result, Error> { + check_if_safe(ext)?; let block = { let block = self.client.block(hash).map_err(|e| Error::BlockQueryError(Box::new(e)))?; diff --git a/substrate/client/rpc/src/dev/tests.rs b/substrate/client/rpc/src/dev/tests.rs index e8f9ba4990d2..ff691af7de41 100644 --- a/substrate/client/rpc/src/dev/tests.rs +++ b/substrate/client/rpc/src/dev/tests.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use super::*; +use crate::DenyUnsafe; use sc_block_builder::BlockBuilderBuilder; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; @@ -24,8 +25,9 @@ use substrate_test_runtime_client::{prelude::*, runtime::Block}; #[tokio::test] async fn block_stats_work() { - let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = >::new(client.clone(), DenyUnsafe::No).into_rpc(); + let client = Arc::new(substrate_test_runtime_client::new()); + let mut api = >::new(client.clone()).into_rpc(); + api.extensions_mut().insert(DenyUnsafe::No); let block = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().genesis_hash) @@ -76,8 +78,9 @@ async fn block_stats_work() { #[tokio::test] async fn deny_unsafe_works() { - let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = >::new(client.clone(), DenyUnsafe::Yes).into_rpc(); + let client = Arc::new(substrate_test_runtime_client::new()); + let mut api = >::new(client.clone()).into_rpc(); + api.extensions_mut().insert(DenyUnsafe::Yes); let block = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().genesis_hash) @@ -101,6 +104,6 @@ async fn deny_unsafe_works() { assert_eq!( resp, - r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"# + r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"RPC call is unsafe to be called externally"}}"# ); } diff --git a/substrate/client/rpc/src/lib.rs b/substrate/client/rpc/src/lib.rs index b40d0341e321..33e4ac2f4c89 100644 --- a/substrate/client/rpc/src/lib.rs +++ b/substrate/client/rpc/src/lib.rs @@ -22,12 +22,9 @@ #![warn(missing_docs)] -pub use jsonrpsee::core::{ - id_providers::{ - RandomIntegerIdProvider as RandomIntegerSubscriptionId, - RandomStringIdProvider as RandomStringSubscriptionId, - }, - traits::IdProvider as RpcSubscriptionIdProvider, +pub use jsonrpsee::core::id_providers::{ + RandomIntegerIdProvider as RandomIntegerSubscriptionId, + RandomStringIdProvider as RandomStringSubscriptionId, }; pub use sc_rpc_api::DenyUnsafe; diff --git a/substrate/client/rpc/src/offchain/mod.rs b/substrate/client/rpc/src/offchain/mod.rs index 661673866053..af6bc1ba58c8 100644 --- a/substrate/client/rpc/src/offchain/mod.rs +++ b/substrate/client/rpc/src/offchain/mod.rs @@ -22,11 +22,11 @@ mod tests; use self::error::Error; -use jsonrpsee::core::async_trait; +use jsonrpsee::{core::async_trait, Extensions}; use parking_lot::RwLock; +use sc_rpc_api::check_if_safe; /// Re-export the API for backward compatibility. pub use sc_rpc_api::offchain::*; -use sc_rpc_api::DenyUnsafe; use sp_core::{ offchain::{OffchainStorage, StorageKind}, Bytes, @@ -38,20 +38,25 @@ use std::sync::Arc; pub struct Offchain { /// Offchain storage storage: Arc>, - deny_unsafe: DenyUnsafe, } impl Offchain { /// Create new instance of Offchain API. - pub fn new(storage: T, deny_unsafe: DenyUnsafe) -> Self { - Offchain { storage: Arc::new(RwLock::new(storage)), deny_unsafe } + pub fn new(storage: T) -> Self { + Offchain { storage: Arc::new(RwLock::new(storage)) } } } #[async_trait] impl OffchainApiServer for Offchain { - fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<(), Error> { - self.deny_unsafe.check_if_safe()?; + fn set_local_storage( + &self, + ext: &Extensions, + kind: StorageKind, + key: Bytes, + value: Bytes, + ) -> Result<(), Error> { + check_if_safe(ext)?; let prefix = match kind { StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, @@ -61,8 +66,13 @@ impl OffchainApiServer for Offchain { Ok(()) } - fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result, Error> { - self.deny_unsafe.check_if_safe()?; + fn get_local_storage( + &self, + ext: &Extensions, + kind: StorageKind, + key: Bytes, + ) -> Result, Error> { + check_if_safe(ext)?; let prefix = match kind { StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, diff --git a/substrate/client/rpc/src/offchain/tests.rs b/substrate/client/rpc/src/offchain/tests.rs index 7758fac7fabd..41f22c2dc964 100644 --- a/substrate/client/rpc/src/offchain/tests.rs +++ b/substrate/client/rpc/src/offchain/tests.rs @@ -17,22 +17,25 @@ // along with this program. If not, see . use super::*; +use crate::testing::{allow_unsafe, deny_unsafe}; use assert_matches::assert_matches; use sp_core::{offchain::storage::InMemOffchainStorage, Bytes}; #[test] fn local_storage_should_work() { let storage = InMemOffchainStorage::default(); - let offchain = Offchain::new(storage, DenyUnsafe::No); + let offchain = Offchain::new(storage); let key = Bytes(b"offchain_storage".to_vec()); let value = Bytes(b"offchain_value".to_vec()); + let ext = allow_unsafe(); + assert_matches!( - offchain.set_local_storage(StorageKind::PERSISTENT, key.clone(), value.clone()), + offchain.set_local_storage(&ext, StorageKind::PERSISTENT, key.clone(), value.clone()), Ok(()) ); assert_matches!( - offchain.get_local_storage(StorageKind::PERSISTENT, key), + offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), Ok(Some(ref v)) if *v == value ); } @@ -40,18 +43,20 @@ fn local_storage_should_work() { #[test] fn offchain_calls_considered_unsafe() { let storage = InMemOffchainStorage::default(); - let offchain = Offchain::new(storage, DenyUnsafe::Yes); + let offchain = Offchain::new(storage); let key = Bytes(b"offchain_storage".to_vec()); let value = Bytes(b"offchain_value".to_vec()); + let ext = deny_unsafe(); + assert_matches!( - offchain.set_local_storage(StorageKind::PERSISTENT, key.clone(), value.clone()), + offchain.set_local_storage(&ext, StorageKind::PERSISTENT, key.clone(), value.clone()), Err(Error::UnsafeRpcCalled(e)) => { assert_eq!(e.to_string(), "RPC call is unsafe to be called externally") } ); assert_matches!( - offchain.get_local_storage(StorageKind::PERSISTENT, key), + offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), Err(Error::UnsafeRpcCalled(e)) => { assert_eq!(e.to_string(), "RPC call is unsafe to be called externally") } diff --git a/substrate/client/rpc/src/state/mod.rs b/substrate/client/rpc/src/state/mod.rs index c9a41e25eda8..d8989b3e1bee 100644 --- a/substrate/client/rpc/src/state/mod.rs +++ b/substrate/client/rpc/src/state/mod.rs @@ -25,11 +25,11 @@ mod utils; mod tests; use crate::SubscriptionTaskExecutor; -use jsonrpsee::{core::async_trait, PendingSubscriptionSink}; +use jsonrpsee::{core::async_trait, Extensions, PendingSubscriptionSink}; use sc_client_api::{ Backend, BlockBackend, BlockchainEvents, ExecutorProvider, ProofProvider, StorageProvider, }; -use sc_rpc_api::DenyUnsafe; +use sc_rpc_api::{check_if_safe, DenyUnsafe}; use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi}; use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_core::{ @@ -164,7 +164,6 @@ where pub fn new_full( client: Arc, executor: SubscriptionTaskExecutor, - deny_unsafe: DenyUnsafe, ) -> (State, ChildState) where Block: BlockT + 'static, @@ -187,14 +186,12 @@ where let child_backend = Box::new(self::state_full::FullState::new(client.clone(), executor.clone())); let backend = Box::new(self::state_full::FullState::new(client, executor)); - (State { backend, deny_unsafe }, ChildState { backend: child_backend }) + (State { backend }, ChildState { backend: child_backend }) } /// State API with subscriptions support. pub struct State { backend: Box>, - /// Whether to deny unsafe calls - deny_unsafe: DenyUnsafe, } #[async_trait] @@ -222,10 +219,11 @@ where fn storage_pairs( &self, + ext: &Extensions, key_prefix: StorageKey, block: Option, ) -> Result, Error> { - self.deny_unsafe.check_if_safe()?; + check_if_safe(ext)?; self.backend.storage_pairs(block, key_prefix).map_err(Into::into) } @@ -262,13 +260,15 @@ where async fn storage_size( &self, + ext: &Extensions, key: StorageKey, block: Option, ) -> Result, Error> { - self.backend - .storage_size(block, key, self.deny_unsafe) - .await - .map_err(Into::into) + let deny_unsafe = ext + .get::() + .cloned() + .expect("DenyUnsafe extension is always set by the substrate rpc server; qed"); + self.backend.storage_size(block, key, deny_unsafe).await.map_err(Into::into) } fn metadata(&self, block: Option) -> Result { @@ -281,11 +281,12 @@ where fn query_storage( &self, + ext: &Extensions, keys: Vec, from: Block::Hash, to: Option, ) -> Result>, Error> { - self.deny_unsafe.check_if_safe()?; + check_if_safe(ext)?; self.backend.query_storage(from, to, keys).map_err(Into::into) } @@ -312,12 +313,13 @@ where /// Note: requires runtimes compiled with wasm tracing support, `--features with-tracing`. fn trace_block( &self, + ext: &Extensions, block: Block::Hash, targets: Option, storage_keys: Option, methods: Option, ) -> Result { - self.deny_unsafe.check_if_safe()?; + check_if_safe(ext)?; self.backend .trace_block(block, targets, storage_keys, methods) .map_err(Into::into) @@ -327,8 +329,17 @@ where self.backend.subscribe_runtime_version(pending) } - fn subscribe_storage(&self, pending: PendingSubscriptionSink, keys: Option>) { - self.backend.subscribe_storage(pending, keys, self.deny_unsafe) + fn subscribe_storage( + &self, + pending: PendingSubscriptionSink, + ext: &Extensions, + keys: Option>, + ) { + let deny_unsafe = ext + .get::() + .cloned() + .expect("DenyUnsafe extension is always set by the substrate rpc server; qed"); + self.backend.subscribe_storage(pending, keys, deny_unsafe) } } diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs index dd866e671c50..eef795070343 100644 --- a/substrate/client/rpc/src/state/tests.rs +++ b/substrate/client/rpc/src/state/tests.rs @@ -18,12 +18,11 @@ use self::error::Error; use super::*; -use crate::testing::{test_executor, timeout_secs}; +use crate::testing::{allow_unsafe, test_executor, timeout_secs}; use assert_matches::assert_matches; use futures::executor; use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError}; use sc_block_builder::BlockBuilderBuilder; -use sc_rpc_api::DenyUnsafe; use sp_consensus::BlockOrigin; use sp_core::{hash::H256, storage::ChildInfo}; use std::sync::Arc; @@ -39,14 +38,6 @@ fn prefixed_storage_key() -> PrefixedStorageKey { child_info.prefixed_storage_key() } -fn init_logger() { - use tracing_subscriber::{EnvFilter, FmtSubscriber}; - - let _ = FmtSubscriber::builder() - .with_env_filter(EnvFilter::from_default_env()) - .try_init(); -} - #[tokio::test] async fn should_return_storage() { const KEY: &[u8] = b":mock"; @@ -62,8 +53,9 @@ async fn should_return_storage() { .add_extra_storage(b":map:acc2".to_vec(), vec![1, 2, 3]) .build(); let genesis_hash = client.genesis_hash(); - let (client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No); + let (client, child) = new_full(Arc::new(client), test_executor()); let key = StorageKey(KEY.to_vec()); + let ext = allow_unsafe(); assert_eq!( client @@ -78,11 +70,15 @@ async fn should_return_storage() { Ok(true) ); assert_eq!( - client.storage_size(key.clone(), None).await.unwrap().unwrap() as usize, + client.storage_size(&ext, key.clone(), None).await.unwrap().unwrap() as usize, VALUE.len(), ); assert_eq!( - client.storage_size(StorageKey(b":map".to_vec()), None).await.unwrap().unwrap() as usize, + client + .storage_size(&ext, StorageKey(b":map".to_vec()), None) + .await + .unwrap() + .unwrap() as usize, 2 + 3, ); assert_eq!( @@ -110,7 +106,7 @@ async fn should_return_storage_entries() { .add_extra_child_storage(&child_info, KEY2.to_vec(), CHILD_VALUE2.to_vec()) .build(); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No); + let (_client, child) = new_full(Arc::new(client), test_executor()); let keys = &[StorageKey(KEY1.to_vec()), StorageKey(KEY2.to_vec())]; assert_eq!( @@ -141,7 +137,7 @@ async fn should_return_child_storage() { .build(), ); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No); + let (_client, child) = new_full(client, test_executor()); let child_key = prefixed_storage_key(); let key = StorageKey(b"key".to_vec()); @@ -172,7 +168,7 @@ async fn should_return_child_storage_entries() { .build(), ); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No); + let (_client, child) = new_full(client, test_executor()); let child_key = prefixed_storage_key(); let keys = vec![StorageKey(b"key1".to_vec()), StorageKey(b"key2".to_vec())]; @@ -203,7 +199,7 @@ async fn should_return_child_storage_entries() { async fn should_call_contract() { let client = Arc::new(substrate_test_runtime_client::new()); let genesis_hash = client.genesis_hash(); - let (client, _child) = new_full(client, test_executor(), DenyUnsafe::No); + let (client, _child) = new_full(client, test_executor()); assert_matches!( client.call("balanceOf".into(), Bytes(vec![1, 2, 3]), Some(genesis_hash).into()), @@ -213,13 +209,12 @@ async fn should_call_contract() { #[tokio::test] async fn should_notify_about_storage_changes() { - init_logger(); - let mut sub = { - let mut client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); + let client = Arc::new(substrate_test_runtime_client::new()); + let (api, _child) = new_full(client.clone(), test_executor()); + let mut api_rpc = api.into_rpc(); + api_rpc.extensions_mut().insert(DenyUnsafe::No); - let api_rpc = api.into_rpc(); let sub = api_rpc .subscribe_unbounded("state_subscribeStorage", EmptyParams::new()) .await @@ -253,11 +248,9 @@ async fn should_notify_about_storage_changes() { #[tokio::test] async fn should_send_initial_storage_changes_and_notifications() { - init_logger(); - let mut sub = { - let mut client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); + let client = Arc::new(substrate_test_runtime_client::new()); + let (api, _child) = new_full(client.clone(), test_executor()); let alice_balance_key = [ sp_crypto_hashing::twox_128(b"System"), @@ -270,7 +263,9 @@ async fn should_send_initial_storage_changes_and_notifications() { .cloned() .collect::>(); - let api_rpc = api.into_rpc(); + let mut api_rpc = api.into_rpc(); + api_rpc.extensions_mut().insert(DenyUnsafe::No); + let sub = api_rpc .subscribe_unbounded( "state_subscribeStorage", @@ -304,10 +299,10 @@ async fn should_send_initial_storage_changes_and_notifications() { #[tokio::test] async fn should_query_storage() { - async fn run_tests(mut client: Arc) { - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); + async fn run_tests(client: Arc) { + let (api, _child) = new_full(client.clone(), test_executor()); - let mut add_block = |index| { + let add_block = |index| { let mut builder = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().best_hash) .with_parent_block_number(client.chain_info().best_number) @@ -377,14 +372,16 @@ async fn should_query_storage() { }, ]; + let ext = allow_unsafe(); + // Query changes only up to block1 let keys = (1..6).map(|k| StorageKey(vec![k])).collect::>(); - let result = api.query_storage(keys.clone(), genesis_hash, Some(block1_hash).into()); + let result = api.query_storage(&ext, keys.clone(), genesis_hash, Some(block1_hash).into()); assert_eq!(result.unwrap(), expected); // Query all changes - let result = api.query_storage(keys.clone(), genesis_hash, None.into()); + let result = api.query_storage(&ext, keys.clone(), genesis_hash, None.into()); expected.push(StorageChangeSet { block: block2_hash, @@ -397,13 +394,13 @@ async fn should_query_storage() { assert_eq!(result.unwrap(), expected); // Query changes up to block2. - let result = api.query_storage(keys.clone(), genesis_hash, Some(block2_hash)); + let result = api.query_storage(&ext, keys.clone(), genesis_hash, Some(block2_hash)); assert_eq!(result.unwrap(), expected); // Inverted range. assert_matches!( - api.query_storage(keys.clone(), block1_hash, Some(genesis_hash)), + api.query_storage(&ext, keys.clone(), block1_hash, Some(genesis_hash)), Err(Error::InvalidBlockRange { from, to, details }) if from == format!("1 ({:?})", block1_hash) && to == format!("0 ({:?})", genesis_hash) && details == "from number > to number".to_owned() ); @@ -412,7 +409,7 @@ async fn should_query_storage() { // Invalid second hash. assert_matches!( - api.query_storage(keys.clone(), genesis_hash, Some(random_hash1)), + api.query_storage(&ext, keys.clone(), genesis_hash, Some(random_hash1)), Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", genesis_hash) && to == format!("{:?}", Some(random_hash1)) && details == format!( "UnknownBlock: Header was not found in the database: {:?}", random_hash1 @@ -421,7 +418,7 @@ async fn should_query_storage() { // Invalid first hash with Some other hash. assert_matches!( - api.query_storage(keys.clone(), random_hash1, Some(genesis_hash)), + api.query_storage(&ext, keys.clone(), random_hash1, Some(genesis_hash)), Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(genesis_hash)) && details == format!( "UnknownBlock: Header was not found in the database: {:?}", random_hash1 @@ -430,7 +427,7 @@ async fn should_query_storage() { // Invalid first hash with None. assert_matches!( - api.query_storage(keys.clone(), random_hash1, None), + api.query_storage(&ext, keys.clone(), random_hash1, None), Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(block2_hash)) && details == format!( "UnknownBlock: Header was not found in the database: {:?}", random_hash1 @@ -439,7 +436,7 @@ async fn should_query_storage() { // Both hashes invalid. assert_matches!( - api.query_storage(keys.clone(), random_hash1, Some(random_hash2)), + api.query_storage(&ext, keys.clone(), random_hash1, Some(random_hash2)), Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(random_hash2)) && details == format!( "UnknownBlock: Header was not found in the database: {:?}", random_hash1 @@ -471,7 +468,7 @@ async fn should_query_storage() { #[tokio::test] async fn should_return_runtime_version() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); + let (api, _child) = new_full(client.clone(), test_executor()); // it is basically json-encoded substrate_test_runtime_client::runtime::VERSION let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ @@ -493,9 +490,10 @@ async fn should_return_runtime_version() { async fn should_notify_on_runtime_version_initially() { let mut sub = { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client, test_executor(), DenyUnsafe::No); + let (api, _child) = new_full(client, test_executor()); + let mut api_rpc = api.into_rpc(); + api_rpc.extensions_mut().insert(DenyUnsafe::No); - let api_rpc = api.into_rpc(); let sub = api_rpc .subscribe_unbounded("state_subscribeRuntimeVersion", EmptyParams::new()) .await @@ -518,12 +516,11 @@ fn should_deserialize_storage_key() { #[tokio::test] async fn wildcard_storage_subscriptions_are_rpc_unsafe() { - init_logger(); - let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes); + let (api, _child) = new_full(client, test_executor()); + let mut api_rpc = api.into_rpc(); + api_rpc.extensions_mut().insert(DenyUnsafe::Yes); - let api_rpc = api.into_rpc(); let err = api_rpc.subscribe_unbounded("state_subscribeStorage", EmptyParams::new()).await; assert_matches!(err, Err(RpcError::JsonRpc(e)) if e.message() == "RPC call is unsafe to be called externally"); } @@ -531,8 +528,9 @@ async fn wildcard_storage_subscriptions_are_rpc_unsafe() { #[tokio::test] async fn concrete_storage_subscriptions_are_rpc_safe() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes); - let api_rpc = api.into_rpc(); + let (api, _child) = new_full(client, test_executor()); + let mut api_rpc = api.into_rpc(); + api_rpc.extensions_mut().insert(DenyUnsafe::Yes); let key = StorageKey(STORAGE_KEY.to_vec()); let sub = api_rpc.subscribe_unbounded("state_subscribeStorage", [[key]]).await; diff --git a/substrate/client/rpc/src/statement/mod.rs b/substrate/client/rpc/src/statement/mod.rs index e99135aec38c..81aa50f73430 100644 --- a/substrate/client/rpc/src/statement/mod.rs +++ b/substrate/client/rpc/src/statement/mod.rs @@ -19,10 +19,12 @@ //! Substrate statement store API. use codec::{Decode, Encode}; -use jsonrpsee::core::{async_trait, RpcResult}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + Extensions, +}; /// Re-export the API for backward compatibility. pub use sc_rpc_api::statement::{error::Error, StatementApiServer}; -use sc_rpc_api::DenyUnsafe; use sp_core::Bytes; use sp_statement_store::{StatementSource, SubmitResult}; use std::sync::Arc; @@ -30,23 +32,19 @@ use std::sync::Arc; /// Statement store API pub struct StatementStore { store: Arc, - deny_unsafe: DenyUnsafe, } impl StatementStore { /// Create new instance of Offchain API. - pub fn new( - store: Arc, - deny_unsafe: DenyUnsafe, - ) -> Self { - StatementStore { store, deny_unsafe } + pub fn new(store: Arc) -> Self { + StatementStore { store } } } #[async_trait] impl StatementApiServer for StatementStore { - fn dump(&self) -> RpcResult> { - self.deny_unsafe.check_if_safe()?; + fn dump(&self, ext: &Extensions) -> RpcResult> { + sc_rpc_api::check_if_safe(ext)?; let statements = self.store.statements().map_err(|e| Error::StatementStore(e.to_string()))?; diff --git a/substrate/client/rpc/src/system/mod.rs b/substrate/client/rpc/src/system/mod.rs index 8c7510c64cb5..e0752749bcbd 100644 --- a/substrate/client/rpc/src/system/mod.rs +++ b/substrate/client/rpc/src/system/mod.rs @@ -22,8 +22,11 @@ mod tests; use futures::channel::oneshot; -use jsonrpsee::core::{async_trait, JsonValue}; -use sc_rpc_api::DenyUnsafe; +use jsonrpsee::{ + core::{async_trait, JsonValue}, + Extensions, +}; +use sc_rpc_api::check_if_safe; use sc_tracing::logging; use sc_utils::mpsc::TracingUnboundedSender; use sp_runtime::traits::{self, Header as HeaderT}; @@ -35,7 +38,6 @@ pub use sc_rpc_api::system::*; pub struct System { info: SystemInfo, send_back: TracingUnboundedSender>, - deny_unsafe: DenyUnsafe, } /// Request to be processed. @@ -68,12 +70,8 @@ impl System { /// /// The `send_back` will be used to transmit some of the requests. The user is responsible for /// reading from that channel and answering the requests. - pub fn new( - info: SystemInfo, - send_back: TracingUnboundedSender>, - deny_unsafe: DenyUnsafe, - ) -> Self { - System { info, send_back, deny_unsafe } + pub fn new(info: SystemInfo, send_back: TracingUnboundedSender>) -> Self { + System { info, send_back } } } @@ -119,22 +117,23 @@ impl SystemApiServer::Number> async fn system_peers( &self, + ext: &Extensions, ) -> Result::Number>>, Error> { - self.deny_unsafe.check_if_safe()?; + check_if_safe(ext)?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::Peers(tx)); rx.await.map_err(|e| Error::Internal(e.to_string())) } - async fn system_network_state(&self) -> Result { - self.deny_unsafe.check_if_safe()?; + async fn system_network_state(&self, ext: &Extensions) -> Result { + check_if_safe(ext)?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkState(tx)); rx.await.map_err(|e| Error::Internal(e.to_string())) } - async fn system_add_reserved_peer(&self, peer: String) -> Result<(), Error> { - self.deny_unsafe.check_if_safe()?; + async fn system_add_reserved_peer(&self, ext: &Extensions, peer: String) -> Result<(), Error> { + check_if_safe(ext)?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkAddReservedPeer(peer, tx)); match rx.await { @@ -144,8 +143,12 @@ impl SystemApiServer::Number> } } - async fn system_remove_reserved_peer(&self, peer: String) -> Result<(), Error> { - self.deny_unsafe.check_if_safe()?; + async fn system_remove_reserved_peer( + &self, + ext: &Extensions, + peer: String, + ) -> Result<(), Error> { + check_if_safe(ext)?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkRemoveReservedPeer(peer, tx)); match rx.await { @@ -173,15 +176,15 @@ impl SystemApiServer::Number> rx.await.map_err(|e| Error::Internal(e.to_string())) } - fn system_add_log_filter(&self, directives: String) -> Result<(), Error> { - self.deny_unsafe.check_if_safe()?; + fn system_add_log_filter(&self, ext: &Extensions, directives: String) -> Result<(), Error> { + check_if_safe(ext)?; logging::add_directives(&directives); logging::reload_filter().map_err(|e| Error::Internal(e)) } - fn system_reset_log_filter(&self) -> Result<(), Error> { - self.deny_unsafe.check_if_safe()?; + fn system_reset_log_filter(&self, ext: &Extensions) -> Result<(), Error> { + check_if_safe(ext)?; logging::reset_log_filter().map_err(|e| Error::Internal(e)) } } diff --git a/substrate/client/rpc/src/system/tests.rs b/substrate/client/rpc/src/system/tests.rs index 03967c63523c..bfef0e429ceb 100644 --- a/substrate/client/rpc/src/system/tests.rs +++ b/substrate/client/rpc/src/system/tests.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use super::{helpers::SyncState, *}; +use crate::DenyUnsafe; use assert_matches::assert_matches; use futures::prelude::*; use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError, RpcModule}; @@ -127,7 +128,7 @@ fn api>>(sync: T) -> RpcModule> { future::ready(()) })) }); - System::new( + let mut module = System::new( SystemInfo { impl_name: "testclient".into(), impl_version: "0.2.0".into(), @@ -136,9 +137,11 @@ fn api>>(sync: T) -> RpcModule> { chain_type: Default::default(), }, tx, - sc_rpc_api::DenyUnsafe::No, ) - .into_rpc() + .into_rpc(); + + module.extensions_mut().insert(DenyUnsafe::No); + module } #[tokio::test] diff --git a/substrate/client/rpc/src/testing.rs b/substrate/client/rpc/src/testing.rs index e04f80a7b9e6..990f81ba551e 100644 --- a/substrate/client/rpc/src/testing.rs +++ b/substrate/client/rpc/src/testing.rs @@ -20,6 +20,9 @@ use std::{future::Future, sync::Arc}; +use jsonrpsee::Extensions; +use sc_rpc_api::DenyUnsafe; + /// A task executor that can be used for running RPC tests. /// /// Warning: the tokio runtime must be initialized before calling this. @@ -70,3 +73,17 @@ pub fn test_executor() -> Arc { pub fn timeout_secs>(s: u64, f: F) -> tokio::time::Timeout { tokio::time::timeout(std::time::Duration::from_secs(s), f) } + +/// Helper to create an extension that denies unsafe calls. +pub fn deny_unsafe() -> Extensions { + let mut ext = Extensions::new(); + ext.insert(DenyUnsafe::Yes); + ext +} + +/// Helper to create an extension that allows unsafe calls. +pub fn allow_unsafe() -> Extensions { + let mut ext = Extensions::new(); + ext.insert(DenyUnsafe::No); + ext +} diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index d7fb7918481c..9a6c3fde37d9 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -55,7 +55,7 @@ use sc_network_sync::{ block_relay_protocol::BlockRelayParams, block_request_handler::BlockRequestHandler, engine::SyncingEngine, service::network::NetworkServiceProvider, state_request_handler::StateRequestHandler, - warp_request_handler::RequestHandler as WarpSyncRequestHandler, SyncingService, WarpSyncParams, + warp_request_handler::RequestHandler as WarpSyncRequestHandler, SyncingService, WarpSyncConfig, }; use sc_rpc::{ author::AuthorApiServer, @@ -248,10 +248,7 @@ where offchain_worker_enabled: config.offchain_worker.enabled, offchain_indexing_api: config.offchain_worker.indexing_enabled, wasm_runtime_overrides: config.wasm_runtime_overrides.clone(), - no_genesis: matches!( - config.network.sync_mode, - SyncMode::LightState { .. } | SyncMode::Warp { .. } - ), + no_genesis: config.no_genesis(), wasm_runtime_substitutes, enable_import_proof_recording, }, @@ -364,8 +361,7 @@ pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> { /// A shared transaction pool. pub transaction_pool: Arc, /// Builds additional [`RpcModule`]s that should be added to the server - pub rpc_builder: - Box Result, Error>>, + pub rpc_builder: Box Result, Error>>, /// A shared network instance. pub network: Arc, /// A Sender for RPC requests. @@ -494,9 +490,8 @@ where let rpc_id_provider = config.rpc_id_provider.take(); // jsonrpsee RPC - let gen_rpc_module = |deny_unsafe: DenyUnsafe| { + let gen_rpc_module = || { gen_rpc_module( - deny_unsafe, task_manager.spawn_handle(), client.clone(), transaction_pool.clone(), @@ -508,8 +503,14 @@ where ) }; - let rpc = start_rpc_servers(&config, gen_rpc_module, rpc_id_provider)?; - let rpc_handlers = RpcHandlers::new(Arc::new(gen_rpc_module(sc_rpc::DenyUnsafe::No)?.into())); + let rpc_server_handle = start_rpc_servers(&config, gen_rpc_module, rpc_id_provider)?; + let in_memory_rpc = { + let mut module = gen_rpc_module()?; + module.extensions_mut().insert(DenyUnsafe::No); + module + }; + + let in_memory_rpc_handle = RpcHandlers::new(Arc::new(in_memory_rpc)); // Spawn informant task spawn_handle.spawn( @@ -518,9 +519,9 @@ where sc_informant::build(client.clone(), network, sync_service.clone()), ); - task_manager.keep_alive((config.base_path, rpc)); + task_manager.keep_alive((config.base_path, rpc_server_handle)); - Ok(rpc_handlers) + Ok(in_memory_rpc_handle) } /// Returns a future that forwards imported transactions to the transaction networking protocol. @@ -593,7 +594,6 @@ where /// Generate RPC module using provided configuration pub fn gen_rpc_module( - deny_unsafe: DenyUnsafe, spawn_handle: SpawnTaskHandle, client: Arc, transaction_pool: Arc, @@ -601,7 +601,7 @@ pub fn gen_rpc_module( system_rpc_tx: TracingUnboundedSender>, config: &Configuration, backend: Arc, - rpc_builder: &(dyn Fn(DenyUnsafe, SubscriptionTaskExecutor) -> Result, Error>), + rpc_builder: &(dyn Fn(SubscriptionTaskExecutor) -> Result, Error>), ) -> Result, Error> where TBl: BlockT, @@ -636,8 +636,7 @@ where let (chain, state, child_state) = { let chain = sc_rpc::chain::new_full(client.clone(), task_executor.clone()).into_rpc(); - let (state, child_state) = - sc_rpc::state::new_full(client.clone(), task_executor.clone(), deny_unsafe); + let (state, child_state) = sc_rpc::state::new_full(client.clone(), task_executor.clone()); let state = state.into_rpc(); let child_state = child_state.into_rpc(); @@ -701,15 +700,14 @@ where client.clone(), transaction_pool, keystore, - deny_unsafe, task_executor.clone(), ) .into_rpc(); - let system = sc_rpc::system::System::new(system_info, system_rpc_tx, deny_unsafe).into_rpc(); + let system = sc_rpc::system::System::new(system_info, system_rpc_tx).into_rpc(); if let Some(storage) = backend.offchain_storage() { - let offchain = sc_rpc::offchain::Offchain::new(storage, deny_unsafe).into_rpc(); + let offchain = sc_rpc::offchain::Offchain::new(storage).into_rpc(); rpc_api.merge(offchain).map_err(|e| Error::Application(e.into()))?; } @@ -729,7 +727,7 @@ where rpc_api.merge(state).map_err(|e| Error::Application(e.into()))?; rpc_api.merge(child_state).map_err(|e| Error::Application(e.into()))?; // Additional [`RpcModule`]s defined in the node to fit the specific blockchain - let extra_rpcs = rpc_builder(deny_unsafe, task_executor.clone())?; + let extra_rpcs = rpc_builder(task_executor.clone())?; rpc_api.merge(extra_rpcs).map_err(|e| Error::Application(e.into()))?; Ok(rpc_api) @@ -759,8 +757,8 @@ pub struct BuildNetworkParams< /// A block announce validator builder. pub block_announce_validator_builder: Option) -> Box + Send> + Send>>, - /// Optional warp sync params. - pub warp_sync_params: Option>, + /// Optional warp sync config. + pub warp_sync_config: Option>, /// User specified block relay params. If not specified, the default /// block request handler will be used. pub block_relay: Option>, @@ -804,12 +802,12 @@ where spawn_handle, import_queue, block_announce_validator_builder, - warp_sync_params, + warp_sync_config, block_relay, metrics, } = params; - if warp_sync_params.is_none() && config.network.sync_mode.is_warp() { + if warp_sync_config.is_none() && config.network.sync_mode.is_warp() { return Err("Warp sync enabled, but no warp sync provider configured.".into()) } @@ -872,8 +870,8 @@ where (protocol_config, config_name) }; - let (warp_sync_protocol_config, warp_request_protocol_name) = match warp_sync_params.as_ref() { - Some(WarpSyncParams::WithProvider(warp_with_provider)) => { + let (warp_sync_protocol_config, warp_request_protocol_name) = match warp_sync_config.as_ref() { + Some(WarpSyncConfig::WithProvider(warp_with_provider)) => { // Allow both outgoing and incoming requests. let (handler, protocol_config) = WarpSyncRequestHandler::new::<_, TNet>( protocol_id.clone(), @@ -942,7 +940,7 @@ where protocol_id.clone(), &config.chain_spec.fork_id().map(ToOwned::to_owned), block_announce_validator, - warp_sync_params, + warp_sync_config, chain_sync_network_handle, import_queue.service(), block_downloader, diff --git a/substrate/client/service/src/client/client.rs b/substrate/client/service/src/client/client.rs index a2c9212f7b9c..22defd7c5514 100644 --- a/substrate/client/service/src/client/client.rs +++ b/substrate/client/service/src/client/client.rs @@ -1754,7 +1754,7 @@ where /// If you are not sure that there are no BlockImport objects provided by the consensus /// algorithm, don't use this function. async fn import_block( - &mut self, + &self, mut import_block: BlockImportParams, ) -> Result { let span = tracing::span!(tracing::Level::DEBUG, "import_block"); @@ -1854,19 +1854,19 @@ where { type Error = ConsensusError; - async fn import_block( - &mut self, - import_block: BlockImportParams, - ) -> Result { - (&*self).import_block(import_block).await - } - async fn check_block( &self, block: BlockCheckParams, ) -> Result { (&self).check_block(block).await } + + async fn import_block( + &self, + import_block: BlockImportParams, + ) -> Result { + (&self).import_block(import_block).await + } } impl Finalizer for Client diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs index f8d8511f2718..8387f818e68b 100644 --- a/substrate/client/service/src/config.rs +++ b/substrate/client/service/src/config.rs @@ -33,7 +33,9 @@ pub use sc_network::{ }, Multiaddr, }; -pub use sc_rpc_server::IpNetwork; +pub use sc_rpc_server::{ + IpNetwork, RpcEndpoint, RpcMethods, SubscriptionIdProvider as RpcSubscriptionIdProvider, +}; pub use sc_telemetry::TelemetryEndpoints; pub use sc_transaction_pool::Options as TransactionPoolOptions; use sp_core::crypto::SecretString; @@ -82,8 +84,8 @@ pub struct Configuration { /// over on-chain runtimes when the spec version matches. Set to `None` to /// disable overrides (default). pub wasm_runtime_overrides: Option, - /// JSON-RPC server binding address. - pub rpc_addr: Option, + /// JSON-RPC server endpoints. + pub rpc_addr: Option>, /// Maximum number of connections for JSON-RPC server. pub rpc_max_connections: u32, /// CORS settings for HTTP & WS servers. `None` if all origins are allowed. @@ -97,7 +99,7 @@ pub struct Configuration { /// Custom JSON-RPC subscription ID provider. /// /// Default: [`crate::RandomStringSubscriptionId`]. - pub rpc_id_provider: Option>, + pub rpc_id_provider: Option>, /// Maximum allowed subscriptions per rpc connection pub rpc_max_subs_per_conn: u32, /// JSON-RPC server default port. @@ -255,24 +257,6 @@ impl Configuration { } } -/// Available RPC methods. -#[derive(Debug, Copy, Clone)] -pub enum RpcMethods { - /// Expose every RPC method only when RPC is listening on `localhost`, - /// otherwise serve only safe RPC methods. - Auto, - /// Allow only a safe subset of RPC methods. - Safe, - /// Expose every RPC method (even potentially unsafe ones). - Unsafe, -} - -impl Default for RpcMethods { - fn default() -> RpcMethods { - RpcMethods::Auto - } -} - #[static_init::dynamic(drop, lazy)] static mut BASE_PATH_TEMP: Option = None; diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 89d563001cd6..9e8aed39c897 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -34,7 +34,10 @@ mod client; mod metrics; mod task_manager; -use std::{collections::HashMap, net::SocketAddr}; +use std::{ + collections::HashMap, + net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, +}; use codec::{Decode, Encode}; use futures::{pin_mut, FutureExt, StreamExt}; @@ -82,12 +85,10 @@ pub use sc_chain_spec::{ pub use sc_consensus::ImportQueue; pub use sc_executor::NativeExecutionDispatch; -pub use sc_network_sync::WarpSyncParams; +pub use sc_network_sync::WarpSyncConfig; #[doc(hidden)] pub use sc_network_transactions::config::{TransactionImport, TransactionImportFuture}; -pub use sc_rpc::{ - RandomIntegerSubscriptionId, RandomStringSubscriptionId, RpcSubscriptionIdProvider, -}; +pub use sc_rpc::{RandomIntegerSubscriptionId, RandomStringSubscriptionId}; pub use sc_tracing::TracingReceiver; pub use sc_transaction_pool::Options as TransactionPoolOptions; pub use sc_transaction_pool_api::{error::IntoPoolError, InPoolTransaction, TransactionPool}; @@ -341,7 +342,7 @@ pub async fn build_system_rpc_future< sc_rpc::system::Request::SyncState(sender) => { use sc_rpc::system::SyncState; - match sync_service.best_seen_block().await { + match sync_service.status().await.map(|status| status.best_seen_block) { Ok(best_seen_block) => { let best_number = client.info().best_number; let _ = sender.send(SyncState { @@ -377,45 +378,64 @@ mod waiting { pub fn start_rpc_servers( config: &Configuration, gen_rpc_module: R, - rpc_id_provider: Option>, + rpc_id_provider: Option>, ) -> Result, error::Error> where - R: Fn(sc_rpc::DenyUnsafe) -> Result, Error>, + R: Fn() -> Result, Error>, { - fn deny_unsafe(addr: SocketAddr, methods: &RpcMethods) -> sc_rpc::DenyUnsafe { - let is_exposed_addr = !addr.ip().is_loopback(); - match (is_exposed_addr, methods) { - | (_, RpcMethods::Unsafe) | (false, RpcMethods::Auto) => sc_rpc::DenyUnsafe::No, - _ => sc_rpc::DenyUnsafe::Yes, - } - } - - // if binding the specified port failed then a random port is assigned by the OS. - let backup_port = |mut addr: SocketAddr| { - addr.set_port(0); - addr + let endpoints: Vec = if let Some(endpoints) = + config.rpc_addr.as_ref() + { + endpoints.clone() + } else { + let ipv6 = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, config.rpc_port, 0, 0)); + let ipv4 = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, config.rpc_port)); + + vec![ + sc_rpc_server::RpcEndpoint { + batch_config: config.rpc_batch_config, + cors: config.rpc_cors.clone(), + listen_addr: ipv4, + max_buffer_capacity_per_connection: config.rpc_message_buffer_capacity, + max_connections: config.rpc_max_connections, + max_payload_in_mb: config.rpc_max_request_size, + max_payload_out_mb: config.rpc_max_response_size, + max_subscriptions_per_connection: config.rpc_max_subs_per_conn, + rpc_methods: config.rpc_methods.into(), + rate_limit: config.rpc_rate_limit, + rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(), + retry_random_port: true, + is_optional: false, + }, + sc_rpc_server::RpcEndpoint { + batch_config: config.rpc_batch_config, + cors: config.rpc_cors.clone(), + listen_addr: ipv6, + max_buffer_capacity_per_connection: config.rpc_message_buffer_capacity, + max_connections: config.rpc_max_connections, + max_payload_in_mb: config.rpc_max_request_size, + max_payload_out_mb: config.rpc_max_response_size, + max_subscriptions_per_connection: config.rpc_max_subs_per_conn, + rpc_methods: config.rpc_methods.into(), + rate_limit: config.rpc_rate_limit, + rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(), + retry_random_port: true, + is_optional: true, + }, + ] }; - let addr = config.rpc_addr.unwrap_or_else(|| ([127, 0, 0, 1], config.rpc_port).into()); - let backup_addr = backup_port(addr); let metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?; + let rpc_api = gen_rpc_module()?; let server_config = sc_rpc_server::Config { - addrs: [addr, backup_addr], - batch_config: config.rpc_batch_config, - max_connections: config.rpc_max_connections, - max_payload_in_mb: config.rpc_max_request_size, - max_payload_out_mb: config.rpc_max_response_size, - max_subs_per_conn: config.rpc_max_subs_per_conn, - message_buffer_capacity: config.rpc_message_buffer_capacity, - rpc_api: gen_rpc_module(deny_unsafe(addr, &config.rpc_methods))?, + endpoints, + rpc_api, metrics, id_provider: rpc_id_provider, - cors: config.rpc_cors.as_ref(), tokio_handle: config.tokio_handle.clone(), - rate_limit: config.rpc_rate_limit, - rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(), - rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers, }; // TODO: https://github.com/paritytech/substrate/issues/13773 diff --git a/substrate/client/service/test/src/client/mod.rs b/substrate/client/service/test/src/client/mod.rs index bd48fae63444..13e63962fe8f 100644 --- a/substrate/client/service/test/src/client/mod.rs +++ b/substrate/client/service/test/src/client/mod.rs @@ -238,7 +238,7 @@ fn client_initializes_from_genesis_ok() { #[test] fn block_builder_works_with_no_transactions() { - let mut client = substrate_test_runtime_client::new(); + let client = substrate_test_runtime_client::new(); let block = BlockBuilderBuilder::new(&client) .on_parent_block(client.chain_info().genesis_hash) @@ -256,7 +256,7 @@ fn block_builder_works_with_no_transactions() { #[test] fn block_builder_works_with_transactions() { - let mut client = substrate_test_runtime_client::new(); + let client = substrate_test_runtime_client::new(); let mut builder = BlockBuilderBuilder::new(&client) .on_parent_block(client.chain_info().genesis_hash) @@ -316,7 +316,7 @@ fn block_builder_works_with_transactions() { #[test] fn block_builder_does_not_include_invalid() { - let mut client = substrate_test_runtime_client::new(); + let client = substrate_test_runtime_client::new(); let mut builder = BlockBuilderBuilder::new(&client) .on_parent_block(client.chain_info().genesis_hash) .with_parent_block_number(0) @@ -390,7 +390,7 @@ fn best_containing_with_genesis_block() { fn uncles_with_only_ancestors() { // block tree: // G -> A1 -> A2 - let mut client = substrate_test_runtime_client::new(); + let client = substrate_test_runtime_client::new(); // G -> A1 let a1 = BlockBuilderBuilder::new(&client) @@ -424,7 +424,7 @@ fn uncles_with_multiple_forks() { // A1 -> B2 -> B3 -> B4 // B2 -> C3 // A1 -> D2 - let mut client = substrate_test_runtime_client::new(); + let client = substrate_test_runtime_client::new(); // G -> A1 let a1 = BlockBuilderBuilder::new(&client) @@ -584,7 +584,7 @@ fn finality_target_on_longest_chain_with_single_chain_3_blocks() { // block tree: // G -> A1 -> A2 - let (mut client, longest_chain_select) = TestClientBuilder::new().build_with_longest_chain(); + let (client, longest_chain_select) = TestClientBuilder::new().build_with_longest_chain(); // G -> A1 let a1 = BlockBuilderBuilder::new(&client) @@ -625,7 +625,7 @@ fn finality_target_on_longest_chain_with_multiple_forks() { // A1 -> B2 -> B3 -> B4 // B2 -> C3 // A1 -> D2 - let (mut client, longest_chain_select) = TestClientBuilder::new().build_with_longest_chain(); + let (client, longest_chain_select) = TestClientBuilder::new().build_with_longest_chain(); // G -> A1 let a1 = BlockBuilderBuilder::new(&client) @@ -877,7 +877,7 @@ fn finality_target_on_longest_chain_with_max_depth_higher_than_best() { // block tree: // G -> A1 -> A2 - let (mut client, chain_select) = TestClientBuilder::new().build_with_longest_chain(); + let (client, chain_select) = TestClientBuilder::new().build_with_longest_chain(); let chain = client.chain_info(); // G -> A1 @@ -914,7 +914,7 @@ fn finality_target_with_best_not_on_longest_chain() { // -> B2 -> (B3) -> B4 // ^best - let (mut client, chain_select) = TestClientBuilder::new().build_with_longest_chain(); + let (client, chain_select) = TestClientBuilder::new().build_with_longest_chain(); let chain = client.chain_info(); // G -> A1 @@ -1045,7 +1045,7 @@ fn finality_target_with_best_not_on_longest_chain() { fn import_with_justification() { // block tree: // G -> A1 -> A2 -> A3 - let mut client = substrate_test_runtime_client::new(); + let client = substrate_test_runtime_client::new(); let mut finality_notifications = client.finality_notification_stream(); @@ -1099,7 +1099,7 @@ fn import_with_justification() { #[test] fn importing_diverged_finalized_block_should_trigger_reorg() { - let mut client = substrate_test_runtime_client::new(); + let client = substrate_test_runtime_client::new(); // G -> A1 -> A2 // \ @@ -1160,7 +1160,7 @@ fn importing_diverged_finalized_block_should_trigger_reorg() { #[test] fn finalizing_diverged_block_should_trigger_reorg() { - let (mut client, select_chain) = TestClientBuilder::new().build_with_longest_chain(); + let (client, select_chain) = TestClientBuilder::new().build_with_longest_chain(); // G -> A1 -> A2 // \ @@ -1257,7 +1257,7 @@ fn finalizing_diverged_block_should_trigger_reorg() { #[test] fn finality_notifications_content() { sp_tracing::try_init_simple(); - let (mut client, _select_chain) = TestClientBuilder::new().build_with_longest_chain(); + let (client, _select_chain) = TestClientBuilder::new().build_with_longest_chain(); // -> D3 -> D4 // G -> A1 -> A2 -> A3 @@ -1410,7 +1410,7 @@ fn get_hash_by_block_number_doesnt_panic() { #[test] fn state_reverted_on_reorg() { sp_tracing::try_init_simple(); - let mut client = substrate_test_runtime_client::new(); + let client = substrate_test_runtime_client::new(); let current_balance = |client: &substrate_test_runtime_client::TestClient| { client @@ -1492,7 +1492,7 @@ fn doesnt_import_blocks_that_revert_finality() { .unwrap(), ); - let mut client = TestClientBuilder::with_backend(backend).build(); + let client = TestClientBuilder::with_backend(backend).build(); let mut finality_notifications = client.finality_notification_stream(); @@ -1619,7 +1619,7 @@ fn respects_block_rules() { known_bad: &mut HashSet, fork_rules: &mut Vec<(u64, H256)>, ) { - let mut client = if record_only { + let client = if record_only { TestClientBuilder::new().build() } else { TestClientBuilder::new() @@ -1771,7 +1771,7 @@ fn returns_status_for_pruned_blocks() { .unwrap(), ); - let mut client = TestClientBuilder::with_backend(backend).build(); + let client = TestClientBuilder::with_backend(backend).build(); let a1 = BlockBuilderBuilder::new(&client) .on_parent_block(client.chain_info().genesis_hash) @@ -2160,7 +2160,7 @@ fn cleans_up_closed_notification_sinks_on_block_import() { /// Test that ensures that we always send an import notification for re-orgs. #[test] fn reorg_triggers_a_notification_even_for_sources_that_should_not_trigger_notifications() { - let mut client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().build(); let mut notification_stream = futures::executor::block_on_stream(client.import_notification_stream()); @@ -2232,7 +2232,7 @@ fn use_dalek_ext_works() { sp_core::ed25519::Signature::default() } - let mut client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().build(); client.execution_extensions().set_extensions_factory( sc_client_api::execution_extensions::ExtensionBeforeBlock::::new( @@ -2263,7 +2263,7 @@ fn use_dalek_ext_works() { #[test] fn finalize_after_best_block_updates_best() { - let mut client = substrate_test_runtime_client::new(); + let client = substrate_test_runtime_client::new(); // G -> A1 let a1 = BlockBuilderBuilder::new(&client) diff --git a/substrate/client/transaction-pool/tests/pool.rs b/substrate/client/transaction-pool/tests/pool.rs index 49bd2203c12b..6d70b6ce67ec 100644 --- a/substrate/client/transaction-pool/tests/pool.rs +++ b/substrate/client/transaction-pool/tests/pool.rs @@ -980,7 +980,7 @@ fn ready_set_should_eventually_resolve_when_block_update_arrives() { #[test] fn import_notification_to_pool_maintain_works() { - let mut client = Arc::new(substrate_test_runtime_client::new()); + let client = Arc::new(substrate_test_runtime_client::new()); let best_hash = client.info().best_hash; let finalized_hash = client.info().finalized_hash; diff --git a/substrate/docs/SECURITY.md b/substrate/docs/SECURITY.md index 0d2064863d85..f52da0327132 100644 --- a/substrate/docs/SECURITY.md +++ b/substrate/docs/SECURITY.md @@ -8,7 +8,7 @@ required to address security issues. ## Reporting a Vulnerability Security vulnerabilities in Parity software should be reported by email to security@parity.io. If you think your report -might be eligible for the Parity Bug Bounty Program, your email should be send to bugbounty@parity.io. +might be eligible for the Parity Bug Bounty Program, your email should be sent to bugbounty@parity.io. Your report should include the following: diff --git a/substrate/frame/assets/Cargo.toml b/substrate/frame/assets/Cargo.toml index fcb151298f09..e20b576d0836 100644 --- a/substrate/frame/assets/Cargo.toml +++ b/substrate/frame/assets/Cargo.toml @@ -30,7 +30,6 @@ frame-benchmarking = { optional = true, workspace = true } sp-core = { workspace = true } [dev-dependencies] -sp-std = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 6f8ad0c29393..e909932bfc82 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -801,8 +801,6 @@ pub mod pallet { /// /// - `id`: The identifier of the asset to be destroyed. This must identify an existing /// asset. - /// - /// The asset class must be frozen before calling `start_destroy`. #[pallet::call_index(2)] pub fn start_destroy(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { diff --git a/substrate/frame/balances/src/benchmarking.rs b/substrate/frame/balances/src/benchmarking.rs index 5740f8081c07..c825300218d4 100644 --- a/substrate/frame/balances/src/benchmarking.rs +++ b/substrate/frame/balances/src/benchmarking.rs @@ -31,6 +31,14 @@ const SEED: u32 = 0; // existential deposit multiplier const ED_MULTIPLIER: u32 = 10; +fn minimum_balance, I: 'static>() -> T::Balance { + if cfg!(feature = "insecure_zero_ed") { + 100u32.into() + } else { + T::ExistentialDeposit::get() + } +} + #[instance_benchmarks] mod benchmarks { use super::*; @@ -40,7 +48,7 @@ mod benchmarks { // * Transfer will create the recipient account. #[benchmark] fn transfer_allow_death() { - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let caller = whitelisted_caller(); // Give some multiple of the existential deposit @@ -75,7 +83,7 @@ mod benchmarks { as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); // Give the recipient account existential deposit (thus their account already exists). - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let _ = as Currency<_>>::make_free_balance_be(&recipient, existential_deposit); let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); @@ -98,7 +106,7 @@ mod benchmarks { // Give the sender account max funds, thus a transfer will not kill account. let _ = as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); #[extrinsic_call] @@ -115,7 +123,7 @@ mod benchmarks { let user_lookup = T::Lookup::unlookup(user.clone()); // Give the user some initial balance. - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); @@ -132,7 +140,7 @@ mod benchmarks { let user_lookup = T::Lookup::unlookup(user.clone()); // Give the user some initial balance. - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); @@ -147,7 +155,7 @@ mod benchmarks { // * Transfer will create the recipient account. #[benchmark] fn force_transfer() { - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let source: T::AccountId = account("source", 0, SEED); let source_lookup = T::Lookup::unlookup(source.clone()); @@ -175,7 +183,7 @@ mod benchmarks { #[benchmark(extra)] fn transfer_increasing_users(u: Linear<0, 1_000>) { // 1_000 is not very much, but this upper bound can be controlled by the CLI. - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let caller = whitelisted_caller(); // Give some multiple of the existential deposit @@ -214,7 +222,7 @@ mod benchmarks { let recipient_lookup = T::Lookup::unlookup(recipient.clone()); // Give some multiple of the existential deposit - let existential_deposit = T::ExistentialDeposit::get(); + let existential_deposit: T::Balance = minimum_balance::(); let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); let _ = as Currency<_>>::make_free_balance_be(&caller, balance); @@ -231,7 +239,7 @@ mod benchmarks { let user_lookup = T::Lookup::unlookup(user.clone()); // Give some multiple of the existential deposit - let ed = T::ExistentialDeposit::get(); + let ed = minimum_balance::(); let balance = ed + ed; let _ = as Currency<_>>::make_free_balance_be(&user, balance); @@ -257,8 +265,8 @@ mod benchmarks { .map(|i| -> T::AccountId { let user = account("old_user", i, SEED); let account = AccountData { - free: T::ExistentialDeposit::get(), - reserved: T::ExistentialDeposit::get(), + free: minimum_balance::(), + reserved: minimum_balance::(), frozen: Zero::zero(), flags: ExtraFlags::old_logic(), }; diff --git a/substrate/frame/balances/src/tests/fungible_tests.rs b/substrate/frame/balances/src/tests/fungible_tests.rs index 1a09303a6590..ac373a351d34 100644 --- a/substrate/frame/balances/src/tests/fungible_tests.rs +++ b/substrate/frame/balances/src/tests/fungible_tests.rs @@ -25,7 +25,7 @@ use frame_support::traits::{ Preservation::{Expendable, Preserve, Protect}, Restriction::Free, }, - Consideration, Footprint, LinearStoragePrice, + Consideration, Footprint, LinearStoragePrice, MaybeConsideration, }; use fungible::{ FreezeConsideration, HoldConsideration, Inspect, InspectFreeze, InspectHold, @@ -518,24 +518,28 @@ fn freeze_consideration_works() { let who = 4; // freeze amount taken somewhere outside of our (Consideration) scope. let extend_freeze = 15; + let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(zero_ticket.is_none()); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap().unwrap(); + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap().unwrap(); + let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4); assert_ok!(Balances::increase_frozen(&TestId::Foo, &who, extend_freeze)); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4 + extend_freeze); - let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap().unwrap(); + let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap(); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 8 + extend_freeze); - assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), None); + let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(ticket.is_none()); + assert_eq!(ticket, zero_ticket); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0 + extend_freeze); - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap().unwrap(); + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10 + extend_freeze); let _ = ticket.drop(&who).unwrap(); @@ -560,24 +564,29 @@ fn hold_consideration_works() { let who = 4; // hold amount taken somewhere outside of our (Consideration) scope. let extend_hold = 15; + + let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(zero_ticket.is_none()); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap().unwrap(); + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap().unwrap(); + let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4); assert_ok!(Balances::hold(&TestId::Foo, &who, extend_hold)); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4 + extend_hold); - let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap().unwrap(); + let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap(); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 8 + extend_hold); - assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), None); + let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(ticket.is_none()); + assert_eq!(ticket, zero_ticket); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0 + extend_hold); - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap().unwrap(); + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10 + extend_hold); let _ = ticket.drop(&who).unwrap(); @@ -600,21 +609,22 @@ fn lone_freeze_consideration_works() { >; let who = 4; + let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap().unwrap(); + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); assert_ok!(Balances::increase_frozen(&TestId::Foo, &who, 5)); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 15); - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap().unwrap(); + let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4); - assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), None); + assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), zero_ticket); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap().unwrap(); + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); let _ = ticket.drop(&who).unwrap(); @@ -637,21 +647,22 @@ fn lone_hold_consideration_works() { >; let who = 4; + let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap().unwrap(); + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); assert_ok!(Balances::hold(&TestId::Foo, &who, 5)); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 15); - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap().unwrap(); + let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4); - assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), None); + assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), zero_ticket); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap().unwrap(); + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); let _ = ticket.drop(&who).unwrap(); diff --git a/substrate/frame/beefy-mmr/Cargo.toml b/substrate/frame/beefy-mmr/Cargo.toml index 672a7a68bc7e..d67ac20ee922 100644 --- a/substrate/frame/beefy-mmr/Cargo.toml +++ b/substrate/frame/beefy-mmr/Cargo.toml @@ -18,6 +18,7 @@ log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, workspace = true, default-features = true } binary-merkle-tree = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-beefy = { workspace = true } @@ -40,6 +41,7 @@ std = [ "array-bytes", "binary-merkle-tree/std", "codec/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", "log/std", @@ -65,6 +67,7 @@ try-runtime = [ "sp-runtime/try-runtime", ] runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", diff --git a/substrate/frame/beefy-mmr/src/benchmarking.rs b/substrate/frame/beefy-mmr/src/benchmarking.rs new file mode 100644 index 000000000000..135f95eabb99 --- /dev/null +++ b/substrate/frame/beefy-mmr/src/benchmarking.rs @@ -0,0 +1,129 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Beefy pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use crate::Pallet as BeefyMmr; +use codec::Encode; +use frame_benchmarking::v2::*; +use frame_support::traits::Hooks; +use frame_system::{Config as SystemConfig, Pallet as System}; +use pallet_mmr::{Nodes, Pallet as Mmr}; +use sp_consensus_beefy::Payload; +use sp_runtime::traits::One; + +pub trait Config: + pallet_mmr::Config + crate::Config +{ +} + +impl Config for T where + T: pallet_mmr::Config + crate::Config +{ +} + +fn init_block(block_num: u32) { + let block_num = block_num.into(); + System::::initialize(&block_num, &::Hash::default(), &Default::default()); + Mmr::::on_initialize(block_num); +} + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn extract_validation_context() { + if !cfg!(feature = "test") { + pallet_mmr::UseLocalStorage::::set(true); + } + + init_block::(1); + let header = System::::finalize(); + frame_system::BlockHash::::insert(BlockNumberFor::::one(), header.hash()); + + let validation_context; + #[block] + { + validation_context = + as AncestryHelper>>::extract_validation_context(header); + } + + assert!(validation_context.is_some()); + } + + #[benchmark] + fn read_peak() { + if !cfg!(feature = "test") { + pallet_mmr::UseLocalStorage::::set(true); + } + + init_block::(1); + + let peak; + #[block] + { + peak = Nodes::::get(0) + } + + assert!(peak.is_some()); + } + + /// Generate ancestry proofs with `n` nodes and benchmark the verification logic. + /// These proofs are inflated, containing all the leafs, so we won't read any peak during + /// the verification. We need to account for the peaks separately. + #[benchmark] + fn n_items_proof_is_non_canonical(n: Linear<2, 512>) { + if !cfg!(feature = "test") { + pallet_mmr::UseLocalStorage::::set(true); + } + + for block_num in 1..=n { + init_block::(block_num); + } + let proof = Mmr::::generate_mock_ancestry_proof().unwrap(); + assert_eq!(proof.items.len(), n as usize); + + let is_non_canonical; + #[block] + { + is_non_canonical = as AncestryHelper>>::is_non_canonical( + &Commitment { + payload: Payload::from_single_entry( + known_payloads::MMR_ROOT_ID, + MerkleRootOf::::default().encode(), + ), + block_number: n.into(), + validator_set_id: 0, + }, + proof, + Mmr::::mmr_root(), + ); + }; + + assert_eq!(is_non_canonical, true); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(Default::default()), + crate::mock::Test + ); +} diff --git a/substrate/frame/beefy-mmr/src/lib.rs b/substrate/frame/beefy-mmr/src/lib.rs index 195bbfbf2f29..73119c3faa9b 100644 --- a/substrate/frame/beefy-mmr/src/lib.rs +++ b/substrate/frame/beefy-mmr/src/lib.rs @@ -35,27 +35,34 @@ extern crate alloc; -use sp_runtime::traits::{Convert, Header, Member}; +use sp_runtime::{ + generic::OpaqueDigestItemId, + traits::{Convert, Header, Member}, + SaturatedConversion, +}; use alloc::vec::Vec; use codec::Decode; -use pallet_mmr::{primitives::AncestryProof, LeafDataProvider, ParentNumberAndHash}; +use pallet_mmr::{primitives::AncestryProof, LeafDataProvider, NodesUtils, ParentNumberAndHash}; use sp_consensus_beefy::{ known_payloads, mmr::{BeefyAuthoritySet, BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion}, - AncestryHelper, Commitment, ConsensusLog, ValidatorSet as BeefyValidatorSet, + AncestryHelper, AncestryHelperWeightInfo, Commitment, ConsensusLog, + ValidatorSet as BeefyValidatorSet, }; -use frame_support::{crypto::ecdsa::ECDSAExt, traits::Get}; +use frame_support::{crypto::ecdsa::ECDSAExt, pallet_prelude::Weight, traits::Get}; use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; pub use pallet::*; -use sp_runtime::generic::OpaqueDigestItemId; +pub use weights::WeightInfo; +mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +mod weights; /// A BEEFY consensus digest item with MMR root hash. pub struct DepositBeefyDigest(core::marker::PhantomData); @@ -126,6 +133,8 @@ pub mod pallet { /// Retrieve arbitrary data that should be added to the mmr leaf type BeefyDataProvider: BeefyDataProvider; + + type WeightInfo: WeightInfo; } /// Details of current BEEFY authority set. @@ -263,6 +272,30 @@ where } } +impl AncestryHelperWeightInfo> for Pallet +where + T: pallet_mmr::Config, +{ + fn extract_validation_context() -> Weight { + ::WeightInfo::extract_validation_context() + } + + fn is_non_canonical(proof: &>>::Proof) -> Weight { + let mmr_utils = NodesUtils::new(proof.leaf_count); + let num_peaks = mmr_utils.number_of_peaks(); + + // The approximated cost of verifying an ancestry proof with `n` nodes. + // We add the previous peaks to the total number of nodes, + // since they have to be processed as well. + ::WeightInfo::n_items_proof_is_non_canonical( + proof.items.len().saturating_add(proof.prev_peaks.len()).saturated_into(), + ) + // `n_items_proof_is_non_canonical()` uses inflated proofs that contain all the leafs, + // where no peak needs to be read. So we need to also add the cost of reading the peaks. + .saturating_add(::WeightInfo::read_peak().saturating_mul(num_peaks)) + } +} + impl Pallet { /// Return the currently active BEEFY authority set proof. pub fn authority_set_proof() -> BeefyAuthoritySet> { diff --git a/substrate/frame/beefy-mmr/src/mock.rs b/substrate/frame/beefy-mmr/src/mock.rs index 1102f9677aaa..6756c618d706 100644 --- a/substrate/frame/beefy-mmr/src/mock.rs +++ b/substrate/frame/beefy-mmr/src/mock.rs @@ -37,6 +37,7 @@ use crate as pallet_beefy_mmr; pub use sp_consensus_beefy::{ ecdsa_crypto::AuthorityId as BeefyId, mmr::BeefyDataProvider, ConsensusLog, BEEFY_ENGINE_ID, }; +use sp_core::offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}; impl_opaque_keys! { pub struct MockSessionKeys { @@ -122,6 +123,7 @@ impl pallet_beefy_mmr::Config for Test { type LeafExtra = Vec; type BeefyDataProvider = DummyDataProvider; + type WeightInfo = (); } pub struct DummyDataProvider; @@ -191,5 +193,10 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<(u64, BeefyId)>) -> TestExt .assimilate_storage(&mut t) .unwrap(); - t.into() + let mut ext: TestExternalities = t.into(); + let (offchain, _offchain_state) = TestOffchainExt::with_offchain_db(ext.offchain_db()); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + ext.register_extension(OffchainWorkerExt::new(offchain)); + + ext } diff --git a/substrate/frame/beefy-mmr/src/tests.rs b/substrate/frame/beefy-mmr/src/tests.rs index f99835a1dc0a..b126a01012b4 100644 --- a/substrate/frame/beefy-mmr/src/tests.rs +++ b/substrate/frame/beefy-mmr/src/tests.rs @@ -24,10 +24,7 @@ use sp_consensus_beefy::{ AncestryHelper, Commitment, Payload, ValidatorSet, }; -use sp_core::{ - offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, - H256, -}; +use sp_core::H256; use sp_io::TestExternalities; use sp_runtime::{traits::Keccak256, DigestItem}; @@ -40,8 +37,6 @@ fn init_block(block: u64, maybe_parent_hash: Option) { System::initialize(&block, &parent_hash, &Default::default()); Session::on_initialize(block); Mmr::on_initialize(block); - Beefy::on_initialize(block); - BeefyMmr::on_initialize(block); } pub fn beefy_log(log: ConsensusLog) -> DigestItem { @@ -211,11 +206,6 @@ fn should_update_authorities() { fn extract_validation_context_should_work_correctly() { let mut ext = new_test_ext(vec![1, 2]); - // Register offchain ext. - let (offchain, _offchain_state) = TestOffchainExt::with_offchain_db(ext.offchain_db()); - ext.register_extension(OffchainDbExt::new(offchain.clone())); - ext.register_extension(OffchainWorkerExt::new(offchain)); - ext.execute_with(|| { init_block(1, None); let h1 = System::finalize(); @@ -262,13 +252,8 @@ fn is_non_canonical_should_work_correctly() { }); ext.persist_offchain_overlay(); - // Register offchain ext. - let (offchain, _offchain_state) = TestOffchainExt::with_offchain_db(ext.offchain_db()); - ext.register_extension(OffchainDbExt::new(offchain.clone())); - ext.register_extension(OffchainWorkerExt::new(offchain)); - ext.execute_with(|| { - let valid_proof = Mmr::generate_ancestry_proof(250, None).unwrap(); + let valid_proof = BeefyMmr::generate_proof(250, None).unwrap(); let mut invalid_proof = valid_proof.clone(); invalid_proof.items.push((300, Default::default())); @@ -343,7 +328,7 @@ fn is_non_canonical_should_work_correctly() { // - should return false, if the commitment is targeting the canonical chain // - should return true if the commitment is NOT targeting the canonical chain for prev_block_number in 1usize..=500 { - let proof = Mmr::generate_ancestry_proof(prev_block_number as u64, None).unwrap(); + let proof = BeefyMmr::generate_proof(prev_block_number as u64, None).unwrap(); assert_eq!( BeefyMmr::is_non_canonical( diff --git a/substrate/frame/beefy-mmr/src/weights.rs b/substrate/frame/beefy-mmr/src/weights.rs new file mode 100644 index 000000000000..c292f25400cc --- /dev/null +++ b/substrate/frame/beefy-mmr/src/weights.rs @@ -0,0 +1,134 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_beefy_mmr` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_beefy_mmr +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/beefy-mmr/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_beefy_mmr`. +pub trait WeightInfo { + fn extract_validation_context() -> Weight; + fn read_peak() -> Weight; + fn n_items_proof_is_non_canonical(n: u32, ) -> Weight; +} + +/// Weights for `pallet_beefy_mmr` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `System::BlockHash` (r:1 w:0) + /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + fn extract_validation_context() -> Weight { + // Proof Size summary in bytes: + // Measured: `92` + // Estimated: `3509` + // Minimum execution time: 7_461_000 picoseconds. + Weight::from_parts(7_669_000, 3509) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Mmr::Nodes` (r:1 w:0) + /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + fn read_peak() -> Weight { + // Proof Size summary in bytes: + // Measured: `333` + // Estimated: `3505` + // Minimum execution time: 6_137_000 picoseconds. + Weight::from_parts(6_423_000, 3505) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Mmr::RootHash` (r:1 w:0) + /// Proof: `Mmr::RootHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `Mmr::NumberOfLeaves` (r:1 w:0) + /// Proof: `Mmr::NumberOfLeaves` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 512]`. + fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `325` + // Estimated: `1517` + // Minimum execution time: 10_687_000 picoseconds. + Weight::from_parts(14_851_626, 1517) + // Standard Error: 1_455 + .saturating_add(Weight::from_parts(961_703, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `System::BlockHash` (r:1 w:0) + /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + fn extract_validation_context() -> Weight { + // Proof Size summary in bytes: + // Measured: `92` + // Estimated: `3509` + // Minimum execution time: 7_461_000 picoseconds. + Weight::from_parts(7_669_000, 3509) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Mmr::Nodes` (r:1 w:0) + /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + fn read_peak() -> Weight { + // Proof Size summary in bytes: + // Measured: `333` + // Estimated: `3505` + // Minimum execution time: 6_137_000 picoseconds. + Weight::from_parts(6_423_000, 3505) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Mmr::RootHash` (r:1 w:0) + /// Proof: `Mmr::RootHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) + /// Storage: `Mmr::NumberOfLeaves` (r:1 w:0) + /// Proof: `Mmr::NumberOfLeaves` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// The range of component `n` is `[2, 512]`. + fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `325` + // Estimated: `1517` + // Minimum execution time: 10_687_000 picoseconds. + Weight::from_parts(14_851_626, 1517) + // Standard Error: 1_455 + .saturating_add(Weight::from_parts(961_703, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } +} diff --git a/substrate/frame/beefy/src/default_weights.rs b/substrate/frame/beefy/src/default_weights.rs index 70dd3bb02bf1..6b83015459d6 100644 --- a/substrate/frame/beefy/src/default_weights.rs +++ b/substrate/frame/beefy/src/default_weights.rs @@ -57,11 +57,6 @@ impl crate::WeightInfo for () { .saturating_add(DbWeight::get().reads(2)) } - // TODO: Calculate - fn report_fork_voting(_validator_count: u32, _max_nominators_per_validator: u32) -> Weight { - Weight::MAX - } - fn set_new_genesis() -> Weight { DbWeight::get().writes(1) } diff --git a/substrate/frame/beefy/src/lib.rs b/substrate/frame/beefy/src/lib.rs index 353ba876c7ed..cf690a9df339 100644 --- a/substrate/frame/beefy/src/lib.rs +++ b/substrate/frame/beefy/src/lib.rs @@ -19,21 +19,33 @@ extern crate alloc; +mod default_weights; +mod equivocation; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + use alloc::{boxed::Box, vec::Vec}; use codec::{Encode, MaxEncodedLen}; +use log; use frame_support::{ dispatch::{DispatchResultWithPostInfo, Pays}, pallet_prelude::*, traits::{Get, OneSessionHandler}, - weights::Weight, + weights::{constants::RocksDbWeight as DbWeight, Weight}, BoundedSlice, BoundedVec, Parameter, }; use frame_system::{ ensure_none, ensure_signed, pallet_prelude::{BlockNumberFor, HeaderFor, OriginFor}, }; -use log; +use sp_consensus_beefy::{ + AncestryHelper, AncestryHelperWeightInfo, AuthorityIndex, BeefyAuthorityId, ConsensusLog, + DoubleVotingProof, ForkVotingProof, FutureBlockVotingProof, OnNewValidatorSet, ValidatorSet, + BEEFY_ENGINE_ID, GENESIS_AUTHORITY_SET_ID, +}; use sp_runtime::{ generic::DigestItem, traits::{IsMember, Member, One}, @@ -42,24 +54,10 @@ use sp_runtime::{ use sp_session::{GetSessionNumber, GetValidatorCount}; use sp_staking::{offence::OffenceReportSystem, SessionIndex}; -use sp_consensus_beefy::{ - AncestryHelper, AuthorityIndex, BeefyAuthorityId, ConsensusLog, DoubleVotingProof, - ForkVotingProof, FutureBlockVotingProof, OnNewValidatorSet, ValidatorSet, BEEFY_ENGINE_ID, - GENESIS_AUTHORITY_SET_ID, -}; - -mod default_weights; -mod equivocation; -#[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; - +use crate::equivocation::EquivocationEvidenceFor; pub use crate::equivocation::{EquivocationOffence, EquivocationReportSystem, TimeSlot}; pub use pallet::*; -use crate::equivocation::EquivocationEvidenceFor; - const LOG_TARGET: &str = "runtime::beefy"; #[frame_support::pallet] @@ -102,7 +100,8 @@ pub mod pallet { type OnNewValidatorSet: OnNewValidatorSet<::BeefyId>; /// Hook for checking commitment canonicity. - type AncestryHelper: AncestryHelper>; + type AncestryHelper: AncestryHelper> + + AncestryHelperWeightInfo>; /// Weights for this pallet. type WeightInfo: WeightInfo; @@ -295,9 +294,10 @@ pub mod pallet { /// and validate the given key ownership proof against the extracted offender. /// If both are valid, the offence will be reported. #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::report_fork_voting( + #[pallet::weight(T::WeightInfo::report_fork_voting::( key_owner_proof.validator_count(), T::MaxNominators::get(), + &equivocation_proof.ancestry_proof ))] pub fn report_fork_voting( origin: OriginFor, @@ -329,9 +329,10 @@ pub mod pallet { /// if the block author is defined it will be defined as the equivocation /// reporter. #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::report_fork_voting( + #[pallet::weight(T::WeightInfo::report_fork_voting::( key_owner_proof.validator_count(), T::MaxNominators::get(), + &equivocation_proof.ancestry_proof ))] pub fn report_fork_voting_unsigned( origin: OriginFor, @@ -358,7 +359,7 @@ pub mod pallet { /// and validate the given key ownership proof against the extracted offender. /// If both are valid, the offence will be reported. #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::report_fork_voting( + #[pallet::weight(T::WeightInfo::report_future_block_voting( key_owner_proof.validator_count(), T::MaxNominators::get(), ))] @@ -389,7 +390,7 @@ pub mod pallet { /// if the block author is defined it will be defined as the equivocation /// reporter. #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::report_fork_voting( + #[pallet::weight(T::WeightInfo::report_future_block_voting( key_owner_proof.validator_count(), T::MaxNominators::get(), ))] @@ -740,15 +741,52 @@ pub trait WeightInfo { validator_count: u32, max_nominators_per_validator: u32, ) -> Weight; + + fn set_new_genesis() -> Weight; +} + +pub(crate) trait WeightInfoExt: WeightInfo { fn report_double_voting(validator_count: u32, max_nominators_per_validator: u32) -> Weight { Self::report_voting_equivocation(2, validator_count, max_nominators_per_validator) } - fn report_fork_voting(validator_count: u32, max_nominators_per_validator: u32) -> Weight; + + fn report_fork_voting( + validator_count: u32, + max_nominators_per_validator: u32, + ancestry_proof: &>>::Proof, + ) -> Weight { + let _weight = >>::extract_validation_context() + .saturating_add( + >>::is_non_canonical( + ancestry_proof, + ), + ) + .saturating_add(Self::report_voting_equivocation( + 1, + validator_count, + max_nominators_per_validator, + )); + + // TODO: https://github.com/paritytech/polkadot-sdk/issues/4523 - return `_weight` here. + // We return `Weight::MAX` currently in order to disallow this extrinsic for the moment. + // We need to check that the proof is optimal. + Weight::MAX + } + fn report_future_block_voting( validator_count: u32, max_nominators_per_validator: u32, ) -> Weight { - Self::report_voting_equivocation(1, validator_count, max_nominators_per_validator) + // checking if the report is for a future block + DbWeight::get() + .reads(1) + // check and report the equivocated vote + .saturating_add(Self::report_voting_equivocation( + 1, + validator_count, + max_nominators_per_validator, + )) } - fn set_new_genesis() -> Weight; } + +impl WeightInfoExt for T where T: WeightInfo {} diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs index b423fa0bda89..5c79d8f7d7d7 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -21,12 +21,13 @@ use std::vec; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, - onchain, SequentialPhragmen, + onchain, SequentialPhragmen, Weight, }; use frame_support::{ construct_runtime, derive_impl, parameter_types, traits::{ConstU32, ConstU64, KeyOwnerProofSystem, OnFinalize, OnInitialize}, }; +use frame_system::pallet_prelude::HeaderFor; use pallet_session::historical as pallet_session_historical; use sp_core::{crypto::KeyTypeId, ConstU128}; use sp_runtime::{ @@ -43,7 +44,7 @@ use sp_state_machine::BasicExternalities; use crate as pallet_beefy; pub use sp_consensus_beefy::{ecdsa_crypto::AuthorityId as BeefyId, ConsensusLog, BEEFY_ENGINE_ID}; -use sp_consensus_beefy::{AncestryHelper, Commitment}; +use sp_consensus_beefy::{AncestryHelper, AncestryHelperWeightInfo, Commitment}; impl_opaque_keys! { pub struct MockSessionKeys { @@ -131,6 +132,16 @@ impl AncestryHelper

for MockAncestryHelper { } } +impl AncestryHelperWeightInfo
for MockAncestryHelper { + fn extract_validation_context() -> Weight { + unimplemented!() + } + + fn is_non_canonical(_proof: &>>::Proof) -> Weight { + unimplemented!() + } +} + impl pallet_beefy::Config for Test { type BeefyId = BeefyId; type MaxAuthorities = ConstU32<100>; diff --git a/substrate/frame/beefy/src/tests.rs b/substrate/frame/beefy/src/tests.rs index a63b3532b698..d75237205cac 100644 --- a/substrate/frame/beefy/src/tests.rs +++ b/substrate/frame/beefy/src/tests.rs @@ -35,7 +35,7 @@ use sp_consensus_beefy::{ use sp_runtime::DigestItem; use sp_session::MembershipProof; -use crate::{self as beefy, mock::*, Call, Config, Error, WeightInfo}; +use crate::{self as beefy, mock::*, Call, Config, Error, WeightInfoExt}; fn init_block(block: u64) { System::set_block_number(block); @@ -765,7 +765,9 @@ fn report_double_voting_has_valid_weight() { // the weight depends on the size of the validator set, // but there's a lower bound of 100 validators. assert!((1..=100) - .map(|validators| ::WeightInfo::report_double_voting(validators, 1000)) + .map(|validators| <::WeightInfo as WeightInfoExt>::report_double_voting( + validators, 1000 + )) .collect::>() .windows(2) .all(|w| w[0] == w[1])); @@ -773,7 +775,9 @@ fn report_double_voting_has_valid_weight() { // after 100 validators the weight should keep increasing // with every extra validator. assert!((100..=1000) - .map(|validators| ::WeightInfo::report_double_voting(validators, 1000)) + .map(|validators| <::WeightInfo as WeightInfoExt>::report_double_voting( + validators, 1000 + )) .collect::>() .windows(2) .all(|w| w[0].ref_time() < w[1].ref_time())); diff --git a/substrate/frame/contracts/fixtures/Cargo.toml b/substrate/frame/contracts/fixtures/Cargo.toml index 6b0751571cc9..6cb6447d8fd7 100644 --- a/substrate/frame/contracts/fixtures/Cargo.toml +++ b/substrate/frame/contracts/fixtures/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] frame-system = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -anyhow = { workspace = true } +anyhow = { workspace = true, default-features = true } [build-dependencies] parity-wasm = { workspace = true } @@ -21,7 +21,7 @@ tempfile = { workspace = true } toml = { workspace = true } twox-hash = { workspace = true, default-features = true } polkavm-linker = { workspace = true, optional = true } -anyhow = { workspace = true } +anyhow = { workspace = true, default-features = true } [features] riscv = ["polkavm-linker"] diff --git a/substrate/frame/delegated-staking/Cargo.toml b/substrate/frame/delegated-staking/Cargo.toml index 1bd17c59f1e7..8d5ccd342b6b 100644 --- a/substrate/frame/delegated-staking/Cargo.toml +++ b/substrate/frame/delegated-staking/Cargo.toml @@ -18,6 +18,8 @@ frame-system = { workspace = true } scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } +sp-io = { workspace = true } +log = { workspace = true } [dev-dependencies] sp-core = { workspace = true, default-features = true } @@ -38,6 +40,7 @@ std = [ "frame-election-provider-support/std", "frame-support/std", "frame-system/std", + "log/std", "pallet-balances/std", "pallet-nomination-pools/std", "pallet-staking/std", diff --git a/substrate/frame/delegated-staking/src/impls.rs b/substrate/frame/delegated-staking/src/impls.rs index f8df9dfe7b46..4e6812dee249 100644 --- a/substrate/frame/delegated-staking/src/impls.rs +++ b/substrate/frame/delegated-staking/src/impls.rs @@ -19,7 +19,7 @@ //! Implementations of public traits, namely [`DelegationInterface`] and [`OnStakingUpdate`]. use super::*; -use sp_staking::{Agent, DelegationInterface, DelegationMigrator, Delegator, OnStakingUpdate}; +use sp_staking::{DelegationInterface, DelegationMigrator, OnStakingUpdate}; impl DelegationInterface for Pallet { type Balance = BalanceOf; @@ -32,28 +32,34 @@ impl DelegationInterface for Pallet { .ok() } + fn agent_transferable_balance(agent: Agent) -> Option { + AgentLedgerOuter::::get(&agent.get()) + .map(|a| a.ledger.unclaimed_withdrawals) + .ok() + } + fn delegator_balance(delegator: Delegator) -> Option { Delegation::::get(&delegator.get()).map(|d| d.amount) } /// Delegate funds to an `Agent`. - fn delegate( - who: Delegator, + fn register_agent( agent: Agent, reward_account: &Self::AccountId, - amount: Self::Balance, ) -> DispatchResult { Pallet::::register_agent( RawOrigin::Signed(agent.clone().get()).into(), reward_account.clone(), - )?; + ) + } - // Delegate the funds from who to the `Agent` account. - Pallet::::delegate_to_agent(RawOrigin::Signed(who.get()).into(), agent.get(), amount) + /// Remove `Agent` registration. + fn remove_agent(agent: Agent) -> DispatchResult { + Pallet::::remove_agent(RawOrigin::Signed(agent.clone().get()).into()) } /// Add more delegation to the `Agent` account. - fn delegate_extra( + fn delegate( who: Delegator, agent: Agent, amount: Self::Balance, @@ -118,7 +124,7 @@ impl DelegationMigrator for Pallet { /// Only used for testing. #[cfg(feature = "runtime-benchmarks")] - fn drop_agent(agent: Agent) { + fn migrate_to_direct_staker(agent: Agent) { >::remove(agent.clone().get()); >::iter() .filter(|(_, delegation)| delegation.agent == agent.clone().get()) diff --git a/substrate/frame/delegated-staking/src/lib.rs b/substrate/frame/delegated-staking/src/lib.rs index 8203f7513305..7b8d14b0a611 100644 --- a/substrate/frame/delegated-staking/src/lib.rs +++ b/substrate/frame/delegated-staking/src/lib.rs @@ -126,6 +126,7 @@ #![deny(rustdoc::broken_intra_doc_links)] mod impls; +pub mod migration; #[cfg(test)] mod mock; #[cfg(test)] @@ -148,16 +149,29 @@ use frame_support::{ }, Balanced, Inspect as FunInspect, Mutate as FunMutate, }, - tokens::{fungible::Credit, Fortitude, Precision, Preservation}, + tokens::{fungible::Credit, Fortitude, Precision, Preservation, Restriction}, Defensive, DefensiveOption, Imbalance, OnUnbalanced, }, }; +use sp_io::hashing::blake2_256; use sp_runtime::{ - traits::{AccountIdConversion, CheckedAdd, CheckedSub, Zero}, + traits::{CheckedAdd, CheckedSub, TrailingZeroInput, Zero}, ArithmeticError, DispatchResult, Perbill, RuntimeDebug, Saturating, }; use sp_staking::{Agent, Delegator, EraIndex, StakingInterface, StakingUnchecked}; +/// The log target of this pallet. +pub const LOG_TARGET: &str = "runtime::delegated-staking"; +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: $crate::LOG_TARGET, + concat!("[{:?}] 🏊‍♂️ ", $patter), >::block_number() $(, $values)* + ) + }; +} pub type BalanceOf = <::Currency as FunInspect<::AccountId>>::Balance; @@ -304,6 +318,25 @@ pub mod pallet { Ok(()) } + /// Remove an account from being an `Agent`. + /// + /// This can only be called if the agent has no delegated funds, no pending slashes and no + /// unclaimed withdrawals. + pub fn remove_agent(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let ledger = AgentLedger::::get(&who).ok_or(Error::::NotAgent)?; + + ensure!( + ledger.total_delegated == Zero::zero() && + ledger.pending_slash == Zero::zero() && + ledger.unclaimed_withdrawals == Zero::zero(), + Error::::NotAllowed + ); + + AgentLedger::::remove(&who); + Ok(()) + } + /// Migrate from a `Nominator` account to `Agent` account. /// /// The origin needs to @@ -371,9 +404,6 @@ pub mod pallet { ) -> DispatchResult { let agent = ensure_signed(origin)?; - // Ensure they have minimum delegation. - ensure!(amount >= T::Currency::minimum_balance(), Error::::NotEnoughFunds); - // Ensure delegator is sane. ensure!(!Self::is_agent(&delegator), Error::::NotAllowed); ensure!(!Self::is_delegator(&delegator), Error::::NotAllowed); @@ -447,7 +477,9 @@ impl Pallet { /// Derive a (keyless) pot account from the given agent account and account type. fn sub_account(account_type: AccountType, acc: T::AccountId) -> T::AccountId { - T::PalletId::get().into_sub_account_truncating((account_type, acc.clone())) + let entropy = (T::PalletId::get(), acc, account_type).using_encoded(blake2_256); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") } /// Held balance of a delegator. @@ -472,13 +504,8 @@ impl Pallet { /// Registers a new agent in the system. fn do_register_agent(who: &T::AccountId, reward_account: &T::AccountId) { + // TODO: Consider taking a deposit for being an agent. AgentLedger::::new(reward_account).update(who); - - // Agent does not hold balance of its own but this pallet will provide for this to exist. - // This is expected to be a keyless account and not created by any user directly so safe. - // TODO: Someday if we allow anyone to be an agent, we should take a deposit for - // being a delegator. - frame_system::Pallet::::inc_providers(who); } /// Migrate existing staker account `who` to an `Agent` account. @@ -489,9 +516,6 @@ impl Pallet { // transferred to actual delegator. let proxy_delegator = Self::generate_proxy_delegator(Agent::from(who.clone())); - // Keep proxy delegator alive until all funds are migrated. - frame_system::Pallet::::inc_providers(&proxy_delegator.clone().get()); - // Get current stake let stake = T::CoreStaking::stake(who)?; @@ -513,7 +537,6 @@ impl Pallet { T::CoreStaking::set_payee(who, reward_account)?; // delegate all transferred funds back to agent. Self::do_delegate(proxy_delegator, Agent::from(who.clone()), amount_to_transfer)?; - // if the transferred/delegated amount was greater than the stake, mark the extra as // unclaimed withdrawal. let unclaimed_withdraws = amount_to_transfer @@ -557,21 +580,23 @@ impl Pallet { let delegator = delegator.get(); let mut ledger = AgentLedger::::get(&agent).ok_or(Error::::NotAgent)?; + + if let Some(mut existing_delegation) = Delegation::::get(&delegator) { + ensure!(existing_delegation.agent == agent, Error::::InvalidDelegation); + // update amount and return the updated delegation. + existing_delegation.amount = existing_delegation + .amount + .checked_add(&amount) + .ok_or(ArithmeticError::Overflow)?; + existing_delegation + } else { + Delegation::::new(&agent, amount) + } + .update(&delegator); + // try to hold the funds. T::Currency::hold(&HoldReason::StakingDelegation.into(), &delegator, amount)?; - let new_delegation_amount = - if let Some(existing_delegation) = Delegation::::get(&delegator) { - ensure!(existing_delegation.agent == agent, Error::::InvalidDelegation); - existing_delegation - .amount - .checked_add(&amount) - .ok_or(ArithmeticError::Overflow)? - } else { - amount - }; - - Delegation::::new(&agent, new_delegation_amount).update_or_kill(&delegator); ledger.total_delegated = ledger.total_delegated.checked_add(&amount).ok_or(ArithmeticError::Overflow)?; ledger.update(&agent); @@ -599,52 +624,24 @@ impl Pallet { ensure!(delegation.agent == agent, Error::::NotAgent); ensure!(delegation.amount >= amount, Error::::NotEnoughFunds); - // if we do not already have enough funds to be claimed, try withdraw some more. - // keep track if we killed the staker in the process. - let stash_killed = if agent_ledger.ledger.unclaimed_withdrawals < amount { + // if we do not already have enough funds to be claimed, try to withdraw some more. + if agent_ledger.ledger.unclaimed_withdrawals < amount { // withdraw account. - let killed = T::CoreStaking::withdraw_unbonded(agent.clone(), num_slashing_spans) + let _ = T::CoreStaking::withdraw_unbonded(agent.clone(), num_slashing_spans) .map_err(|_| Error::::WithdrawFailed)?; // reload agent from storage since withdrawal might have changed the state. agent_ledger = agent_ledger.reload()?; - Some(killed) - } else { - None - }; + } // if we still do not have enough funds to release, abort. ensure!(agent_ledger.ledger.unclaimed_withdrawals >= amount, Error::::NotEnoughFunds); + agent_ledger.remove_unclaimed_withdraw(amount)?.update(); - // Claim withdraw from agent. Kill agent if no delegation left. - // TODO: Ideally if there is a register, there should be an unregister that should - // clean up the agent. Can be improved in future. - if agent_ledger.remove_unclaimed_withdraw(amount)?.update_or_kill()? { - match stash_killed { - Some(killed) => { - // this implies we did a `CoreStaking::withdraw` before release. Ensure - // we killed the staker as well. - ensure!(killed, Error::::BadState); - }, - None => { - // We did not do a `CoreStaking::withdraw` before release. Ensure staker is - // already killed in `CoreStaking`. - ensure!(T::CoreStaking::status(&agent).is_err(), Error::::BadState); - }, - } - - // Remove provider reference for `who`. - let _ = frame_system::Pallet::::dec_providers(&agent).defensive(); - } - - // book keep delegation delegation.amount = delegation .amount .checked_sub(&amount) .defensive_ok_or(ArithmeticError::Overflow)?; - // remove delegator if nothing delegated anymore - delegation.update_or_kill(&delegator); - let released = T::Currency::release( &HoldReason::StakingDelegation.into(), &delegator, @@ -654,6 +651,9 @@ impl Pallet { defensive_assert!(released == amount, "hold should have been released fully"); + // update delegation. + delegation.update(&delegator); + Self::deposit_event(Event::::Released { agent, delegator, amount }); Ok(()) @@ -672,49 +672,34 @@ impl Pallet { let mut source_delegation = Delegators::::get(&source_delegator).defensive_ok_or(Error::::BadState)?; - // some checks that must have already been checked before. + // ensure source has enough funds to migrate. ensure!(source_delegation.amount >= amount, Error::::NotEnoughFunds); debug_assert!( !Self::is_delegator(&destination_delegator) && !Self::is_agent(&destination_delegator) ); let agent = source_delegation.agent.clone(); - // update delegations - Delegation::::new(&agent, amount).update_or_kill(&destination_delegator); + // create a new delegation for destination delegator. + Delegation::::new(&agent, amount).update(&destination_delegator); source_delegation.amount = source_delegation .amount .checked_sub(&amount) .defensive_ok_or(Error::::BadState)?; - source_delegation.update_or_kill(&source_delegator); - - // release funds from source - let released = T::Currency::release( + // transfer the held amount in `source_delegator` to `destination_delegator`. + let _ = T::Currency::transfer_on_hold( &HoldReason::StakingDelegation.into(), - &source_delegator, - amount, - Precision::BestEffort, - )?; - - defensive_assert!(released == amount, "hold should have been released fully"); - - // transfer the released amount to `destination_delegator`. - let post_balance = T::Currency::transfer( &source_delegator, &destination_delegator, amount, - Preservation::Expendable, - ) - .map_err(|_| Error::::BadState)?; - - // if balance is zero, clear provider for source (proxy) delegator. - if post_balance == Zero::zero() { - let _ = frame_system::Pallet::::dec_providers(&source_delegator).defensive(); - } + Precision::Exact, + Restriction::OnHold, + Fortitude::Polite, + )?; - // hold the funds again in the new delegator account. - T::Currency::hold(&HoldReason::StakingDelegation.into(), &destination_delegator, amount)?; + // update source delegation. + source_delegation.update(&source_delegator); Self::deposit_event(Event::::MigratedDelegation { agent, @@ -756,7 +741,7 @@ impl Pallet { agent_ledger.remove_slash(actual_slash).save(); delegation.amount = delegation.amount.checked_sub(&actual_slash).ok_or(ArithmeticError::Overflow)?; - delegation.update_or_kill(&delegator); + delegation.update(&delegator); if let Some(reporter) = maybe_reporter { let reward_payout: BalanceOf = T::SlashRewardFraction::get() * actual_slash; @@ -805,18 +790,21 @@ impl Pallet { ledgers: BTreeMap>, ) -> Result<(), sp_runtime::TryRuntimeError> { for (agent, ledger) in ledgers { - ensure!( - matches!( - T::CoreStaking::status(&agent).expect("agent should be bonded"), - sp_staking::StakerStatus::Nominator(_) | sp_staking::StakerStatus::Idle - ), - "agent should be bonded and not validator" - ); + let staked_value = ledger.stakeable_balance(); + + if !staked_value.is_zero() { + ensure!( + matches!( + T::CoreStaking::status(&agent).expect("agent should be bonded"), + sp_staking::StakerStatus::Nominator(_) | sp_staking::StakerStatus::Idle + ), + "agent should be bonded and not validator" + ); + } ensure!( ledger.stakeable_balance() >= - T::CoreStaking::total_stake(&agent) - .expect("agent should exist as a nominator"), + T::CoreStaking::total_stake(&agent).unwrap_or_default(), "Cannot stake more than balance" ); } diff --git a/substrate/frame/delegated-staking/src/migration.rs b/substrate/frame/delegated-staking/src/migration.rs new file mode 100644 index 000000000000..8bc7312c4eab --- /dev/null +++ b/substrate/frame/delegated-staking/src/migration.rs @@ -0,0 +1,107 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use frame_support::traits::OnRuntimeUpgrade; + +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +pub mod unversioned { + use super::*; + #[cfg(feature = "try-runtime")] + use alloc::vec::Vec; + use sp_runtime::traits::AccountIdConversion; + + /// Migrates `ProxyDelegator` accounts with better entropy than the old logic which didn't take + /// into account all the bytes of the agent account ID. + pub struct ProxyDelegatorMigration(PhantomData<(T, MaxAgents)>); + + impl> OnRuntimeUpgrade for ProxyDelegatorMigration { + fn on_runtime_upgrade() -> Weight { + let mut weight = Weight::zero(); + let old_proxy_delegator = |agent: T::AccountId| { + T::PalletId::get() + .into_sub_account_truncating((AccountType::ProxyDelegator, agent.clone())) + }; + + Agents::::iter_keys().take(MaxAgents::get() as usize).for_each(|agent| { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); + let old_proxy = old_proxy_delegator(agent.clone()); + + // if delegation does not exist, it does not need to be migrated. + if let Some(delegation) = Delegation::::get(&old_proxy) { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); + + let new_proxy = + Pallet::::generate_proxy_delegator(Agent::from(agent.clone())); + + // accrue read writes for `do_migrate_delegation` + weight.saturating_accrue(T::DbWeight::get().reads_writes(8, 8)); + let _ = Pallet::::do_migrate_delegation( + Delegator::from(old_proxy.clone()), + new_proxy.clone(), + delegation.amount, + ) + .map_err(|e| { + log!( + error, + "Failed to migrate old proxy delegator {:?} to new proxy {:?} for agent {:?} with error: {:?}", + old_proxy, + new_proxy, + agent, + e, + ); + }); + }; + }); + + log!(info, "Finished migrating old proxy delegator accounts to new ones"); + weight + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_data: Vec) -> Result<(), TryRuntimeError> { + let mut unmigrated_count = 0; + let old_proxy_delegator = |agent: T::AccountId| { + T::PalletId::get() + .into_sub_account_truncating((AccountType::ProxyDelegator, agent.clone())) + }; + + Agents::::iter_keys().take(MaxAgents::get() as usize).for_each(|agent| { + let old_proxy: T::AccountId = old_proxy_delegator(agent.clone()); + let held_balance = Pallet::::held_balance_of(Delegator::from(old_proxy.clone())); + let delegation = Delegation::::get(&old_proxy); + if delegation.is_some() || !held_balance.is_zero() { + log!( + error, + "Old proxy delegator {:?} for agent {:?} is not migrated.", + old_proxy, + agent, + ); + unmigrated_count += 1; + } + }); + + if unmigrated_count > 0 { + Err(TryRuntimeError::Other("Some old proxy delegator accounts are not migrated.")) + } else { + Ok(()) + } + } + } +} diff --git a/substrate/frame/delegated-staking/src/tests.rs b/substrate/frame/delegated-staking/src/tests.rs index ade0872dd390..2c965e18b1b3 100644 --- a/substrate/frame/delegated-staking/src/tests.rs +++ b/substrate/frame/delegated-staking/src/tests.rs @@ -334,6 +334,36 @@ fn apply_pending_slash() { }); } +#[test] +fn allow_full_amount_to_be_delegated() { + ExtBuilder::default().build_and_execute(|| { + let agent: AccountId = 200; + let reward_acc: AccountId = 201; + let delegator: AccountId = 300; + + // set intention to accept delegation. + fund(&agent, 1000); + assert_ok!(DelegatedStaking::register_agent(RawOrigin::Signed(agent).into(), reward_acc)); + + // delegate to this account + fund(&delegator, 1000); + assert_ok!(DelegatedStaking::delegate_to_agent( + RawOrigin::Signed(delegator).into(), + agent, + 1000 + )); + + // verify + assert!(DelegatedStaking::is_agent(&agent)); + assert_eq!(DelegatedStaking::stakeable_balance(Agent::from(agent)), 1000); + assert_eq!( + Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &delegator), + 1000 + ); + assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(delegator)), 1000); + }); +} + /// Integration tests with pallet-staking. mod staking_integration { use super::*; @@ -625,32 +655,42 @@ mod staking_integration { #[test] fn migration_works() { ExtBuilder::default().build_and_execute(|| { + // initial era + start_era(1); + // add a nominator let staked_amount = 4000; let agent_amount = 5000; - fund(&200, agent_amount); + let agent = 200; + fund(&agent, agent_amount); assert_ok!(Staking::bond( - RuntimeOrigin::signed(200), + RuntimeOrigin::signed(agent), staked_amount, RewardDestination::Account(201) )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(200), vec![GENESIS_VALIDATOR],)); - let init_stake = Staking::stake(&200).unwrap(); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(agent), vec![GENESIS_VALIDATOR],)); + let init_stake = Staking::stake(&agent).unwrap(); // scenario: 200 is a pool account, and the stake comes from its 4 delegators (300..304) // in equal parts. lets try to migrate this nominator into delegate based stake. // all balance currently is in 200 - assert_eq!(Balances::free_balance(200), agent_amount); + assert_eq!(Balances::free_balance(agent), agent_amount); // to migrate, nominator needs to set an account as a proxy delegator where staked funds // will be moved and delegated back to this old nominator account. This should be funded // with at least ED. let proxy_delegator = - DelegatedStaking::generate_proxy_delegator(Agent::from(200)).get(); + DelegatedStaking::generate_proxy_delegator(Agent::from(agent)).get(); - assert_ok!(DelegatedStaking::migrate_to_agent(RawOrigin::Signed(200).into(), 201)); + assert_ok!(DelegatedStaking::migrate_to_agent(RawOrigin::Signed(agent).into(), 201)); + // after migration, funds are moved to proxy delegator, still a provider exists. + assert_eq!(System::providers(&agent), 1); + assert_eq!(Balances::free_balance(agent), 0); + // proxy delegator has one provider as well with no free balance. + assert_eq!(System::providers(&proxy_delegator), 1); + assert_eq!(Balances::free_balance(proxy_delegator), 0); // verify all went well let mut expected_proxy_delegated_amount = agent_amount; @@ -659,12 +699,12 @@ mod staking_integration { expected_proxy_delegated_amount ); // stake amount is transferred from delegate to proxy delegator account. - assert_eq!(Balances::free_balance(200), 0); - assert_eq!(Staking::stake(&200).unwrap(), init_stake); - assert_eq!(get_agent_ledger(&200).ledger.effective_balance(), agent_amount); - assert_eq!(get_agent_ledger(&200).available_to_bond(), 0); + assert_eq!(Balances::free_balance(agent), 0); + assert_eq!(Staking::stake(&agent).unwrap(), init_stake); + assert_eq!(get_agent_ledger(&agent).ledger.effective_balance(), agent_amount); + assert_eq!(get_agent_ledger(&agent).available_to_bond(), 0); assert_eq!( - get_agent_ledger(&200).ledger.unclaimed_withdrawals, + get_agent_ledger(&agent).ledger.unclaimed_withdrawals, agent_amount - staked_amount ); @@ -672,14 +712,15 @@ mod staking_integration { let delegator_share = agent_amount / 4; for delegator in 300..304 { assert_eq!(Balances::free_balance(delegator), 0); - // fund them with ED - fund(&delegator, ExistentialDeposit::get()); - // migrate 1/4th amount into each delegator + assert_eq!(System::providers(&delegator), 0); + + // No pre-balance needed to migrate delegator. assert_ok!(DelegatedStaking::migrate_delegation( - RawOrigin::Signed(200).into(), + RawOrigin::Signed(agent).into(), delegator, delegator_share )); + assert_eq!(System::providers(&delegator), 1); assert_eq!( Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &delegator), delegator_share @@ -694,20 +735,123 @@ mod staking_integration { ); // delegate stake is unchanged. - assert_eq!(Staking::stake(&200).unwrap(), init_stake); - assert_eq!(get_agent_ledger(&200).ledger.effective_balance(), agent_amount); - assert_eq!(get_agent_ledger(&200).available_to_bond(), 0); + assert_eq!(Staking::stake(&agent).unwrap(), init_stake); + assert_eq!(get_agent_ledger(&agent).ledger.effective_balance(), agent_amount); + assert_eq!(get_agent_ledger(&agent).available_to_bond(), 0); assert_eq!( - get_agent_ledger(&200).ledger.unclaimed_withdrawals, + get_agent_ledger(&agent).ledger.unclaimed_withdrawals, agent_amount - staked_amount ); } // cannot use migrate delegator anymore assert_noop!( - DelegatedStaking::migrate_delegation(RawOrigin::Signed(200).into(), 305, 1), + DelegatedStaking::migrate_delegation(RawOrigin::Signed(agent).into(), 305, 1), Error::::NotEnoughFunds ); + + // no provider left on proxy delegator since all funds are migrated + assert_eq!(System::providers(&proxy_delegator), 0); + + // withdraw all delegations from delegators + assert_ok!(Staking::chill(RuntimeOrigin::signed(agent))); + assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), staked_amount)); + start_era(4); + assert_ok!(Staking::withdraw_unbonded(RawOrigin::Signed(agent).into(), 0)); + for delegator in 300..304 { + assert_ok!(DelegatedStaking::release_delegation( + RawOrigin::Signed(agent).into(), + delegator, + delegator_share, + 0 + )); + // delegator is cleaned up from storage. + assert!(!Delegators::::contains_key(delegator)); + // has free balance now + assert_eq!(Balances::free_balance(delegator), delegator_share); + // and only one provider as delegator_share > ED + assert_eq!(System::providers(&delegator), 1); + } + + // Agent can be removed now. + assert_ok!(DelegatedStaking::remove_agent(RawOrigin::Signed(agent).into())); + // agent is correctly removed. + assert!(!Agents::::contains_key(agent)); + // and no provider left. + assert_eq!(System::providers(&agent), 0); + }); + } + + #[test] + fn accounts_are_cleaned_up() { + ExtBuilder::default().build_and_execute(|| { + let agent: AccountId = 200; + let reward_acc: AccountId = 201; + let delegator: AccountId = 300; + + // set intention to accept delegation. + fund(&agent, 1000); + + // Agent is provided since it has balance > ED. + assert_eq!(System::providers(&agent), 1); + assert_ok!(DelegatedStaking::register_agent( + RawOrigin::Signed(agent).into(), + reward_acc + )); + // becoming an agent adds another provider. + assert_eq!(System::providers(&agent), 2); + + // delegate to this account + fund(&delegator, 1000); + // account has one provider since its funded. + assert_eq!(System::providers(&delegator), 1); + assert_ok!(DelegatedStaking::delegate_to_agent( + RawOrigin::Signed(delegator).into(), + agent, + 500 + )); + // delegator has an extra provider now. + assert_eq!(System::providers(&delegator), 2); + // all 1000 tokens including ED can be held. + assert_ok!(DelegatedStaking::delegate_to_agent( + RawOrigin::Signed(delegator).into(), + agent, + 500 + )); + // free balance dropping below ED will reduce a provider, but it still has one provider + // left. + assert_eq!(System::providers(&delegator), 1); + + // withdraw all delegation + assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 1000)); + start_era(4); + assert_ok!(Staking::withdraw_unbonded(RawOrigin::Signed(agent).into(), 0)); + + // Since delegations are still left, agents cannot be removed yet from storage. + assert_noop!( + DelegatedStaking::remove_agent(RawOrigin::Signed(agent).into()), + Error::::NotAllowed + ); + + assert_ok!(DelegatedStaking::release_delegation( + RawOrigin::Signed(agent).into(), + delegator, + 1000, + 0 + )); + + // now agents can be removed. + assert_ok!(DelegatedStaking::remove_agent(RawOrigin::Signed(agent).into())); + + // agent and delegator provider is decremented. + assert_eq!(System::providers(&delegator), 1); + assert_eq!(System::providers(&agent), 1); + + // if we transfer all funds, providers are removed. + assert_ok!(Balances::transfer_all(RawOrigin::Signed(delegator).into(), 1337, false)); + assert_ok!(Balances::transfer_all(RawOrigin::Signed(agent).into(), 1337, false)); + assert_eq!(System::providers(&delegator), 0); + assert_eq!(System::providers(&agent), 0); }); } } @@ -943,7 +1087,7 @@ mod pool_integration { vec![ PoolsEvent::Withdrawn { member: 302, pool_id, balance: 100, points: 100 }, PoolsEvent::Withdrawn { member: 303, pool_id, balance: 200, points: 200 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 303 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 303, released_balance: 0 }, ] ); @@ -1055,7 +1199,7 @@ mod pool_integration { balance: creator_stake, points: creator_stake, }, - PoolsEvent::MemberRemoved { pool_id, member: creator }, + PoolsEvent::MemberRemoved { pool_id, member: creator, released_balance: 0 }, PoolsEvent::Destroyed { pool_id }, ] ); diff --git a/substrate/frame/delegated-staking/src/types.rs b/substrate/frame/delegated-staking/src/types.rs index 24b457356544..a78aa3f55906 100644 --- a/substrate/frame/delegated-staking/src/types.rs +++ b/substrate/frame/delegated-staking/src/types.rs @@ -64,15 +64,25 @@ impl Delegation { ) } - /// Save self to storage. If the delegation amount is zero, remove the delegation. - pub(crate) fn update_or_kill(self, key: &T::AccountId) { - // Clean up if no delegation left. - if self.amount == Zero::zero() { - >::remove(key); - return + /// Save self to storage. + /// + /// If the delegation amount is zero, remove the delegation. Also adds and removes provider + /// reference as needed. + pub(crate) fn update(self, key: &T::AccountId) { + if >::contains_key(key) { + // Clean up if no delegation left. + if self.amount == Zero::zero() { + >::remove(key); + // Remove provider if no delegation left. + let _ = frame_system::Pallet::::dec_providers(key).defensive(); + return + } + } else { + // this is a new delegation. Provide for this account. + frame_system::Pallet::::inc_providers(key); } - >::insert(key, self) + >::insert(key, self); } } @@ -118,10 +128,24 @@ impl AgentLedger { } /// Save self to storage with the given key. + /// + /// Increments provider count if this is a new agent. pub(crate) fn update(self, key: &T::AccountId) { + if !>::contains_key(key) { + // This is a new agent. Provide for this account. + frame_system::Pallet::::inc_providers(key); + } >::insert(key, self) } + /// Remove self from storage. + pub(crate) fn remove(key: &T::AccountId) { + debug_assert!(>::contains_key(key), "Agent should exist in storage"); + >::remove(key); + // Remove provider reference. + let _ = frame_system::Pallet::::dec_providers(key).defensive(); + } + /// Effective total balance of the `Agent`. /// /// This takes into account any slashes reported to `Agent` but unapplied. @@ -251,25 +275,10 @@ impl AgentLedgerOuter { self.ledger.update(&key) } - /// Save self and remove if no delegation left. - /// - /// Returns: - /// - true if agent killed. - /// - error if the delegate is in an unexpected state. - pub(crate) fn update_or_kill(self) -> Result { + /// Update agent ledger. + pub(crate) fn update(self) { let key = self.key; - // see if delegate can be killed - if self.ledger.total_delegated == Zero::zero() { - ensure!( - self.ledger.unclaimed_withdrawals == Zero::zero() && - self.ledger.pending_slash == Zero::zero(), - Error::::BadState - ); - >::remove(key); - return Ok(true) - } self.ledger.update(&key); - Ok(false) } /// Reloads self from storage. diff --git a/substrate/frame/examples/multi-block-migrations/src/migrations/v1/tests.rs b/substrate/frame/examples/multi-block-migrations/src/migrations/v1/tests.rs index 838ba29a6212..3d2360d63a73 100644 --- a/substrate/frame/examples/multi-block-migrations/src/migrations/v1/tests.rs +++ b/substrate/frame/examples/multi-block-migrations/src/migrations/v1/tests.rs @@ -27,6 +27,7 @@ use crate::{ System, }, }; +use codec::Decode; use frame_support::traits::OnRuntimeUpgrade; use pallet_migrations::WeightInfo as _; @@ -51,10 +52,18 @@ fn lazy_migration_works() { let mut last_decodable = 0; for block in 2..=65 { run_to_block(block); + let mut decodable = 0; for i in 0..1024 { - if crate::MyMap::::get(i).is_some() { + let key = crate::MyMap::::hashed_key_for(i); + let value = + frame_support::storage::unhashed::get_raw(&key[..]).expect("value exists"); + + if let Ok(value) = u64::decode(&mut &value[..]) { + assert_eq!(value, i as u64); decodable += 1; + } else { + assert_eq!(u32::decode(&mut &value[..]).expect("not migrated yet"), i); } } diff --git a/substrate/frame/identity/README.md b/substrate/frame/identity/README.md index 0203656eff46..94b2ae0231d7 100644 --- a/substrate/frame/identity/README.md +++ b/substrate/frame/identity/README.md @@ -1,9 +1,11 @@ -# Identity Module +# pallet-identity -- [`identity::Config`](https://docs.rs/pallet-identity/latest/pallet_identity/trait.Config.html) -- [`Call`](https://docs.rs/pallet-identity/latest/pallet_identity/enum.Call.html) +## Identity Pallet -## Overview +- [`Config`] +- [`Call`] + +### Overview A federated naming system, allowing for multiple registrars to be added from a specified origin. Registrars can set a fee to provide identity-verification service. Anyone can put forth a @@ -23,32 +25,53 @@ by definition, these have equivalent ownership and each has an individual name. The number of registrars should be limited, and the deposit made sufficiently large, to ensure no state-bloat attack is viable. -## Interface +#### Usernames + +The pallet provides functionality for username authorities to issue usernames. When an account +receives a username, they get a default instance of `IdentityInfo`. Usernames also serve as a +reverse lookup from username to account. + +Username authorities are given an allocation by governance to prevent state bloat. Usernames +impose no cost or deposit on the user. -### Dispatchable Functions +Users can have multiple usernames that map to the same `AccountId`, however one `AccountId` can +only map to a single username, known as the *primary*. -#### For general users +### Interface + +#### Dispatchable Functions + +##### For General Users - `set_identity` - Set the associated identity of an account; a small deposit is reserved if not already taken. - `clear_identity` - Remove an account's associated identity; the deposit is returned. - `request_judgement` - Request a judgement from a registrar, paying a fee. - `cancel_request` - Cancel the previous request for a judgement. +- `accept_username` - Accept a username issued by a username authority. +- `remove_expired_approval` - Remove a username that was issued but never accepted. +- `set_primary_username` - Set a given username as an account's primary. +- `remove_dangling_username` - Remove a username that maps to an account without an identity. -#### For general users with sub-identities +##### For General Users with Sub-Identities - `set_subs` - Set the sub-accounts of an identity. - `add_sub` - Add a sub-identity to an identity. - `remove_sub` - Remove a sub-identity of an identity. - `rename_sub` - Rename a sub-identity of an identity. - `quit_sub` - Remove a sub-identity of an identity (called by the sub-identity). -#### For registrars +##### For Registrars - `set_fee` - Set the fee required to be paid for a judgement to be given by the registrar. - `set_fields` - Set the fields that a registrar cares about in their judgements. - `provide_judgement` - Provide a judgement to an identity. -#### For super-users +##### For Username Authorities +- `set_username_for` - Set a username for a given account. The account must approve it. + +##### For Superusers - `add_registrar` - Add a new registrar to the system. - `kill_identity` - Forcibly remove the associated identity; the deposit is lost. +- `add_username_authority` - Add an account with the ability to issue usernames. +- `remove_username_authority` - Remove an account with the ability to issue usernames. [`Call`]: ./enum.Call.html [`Config`]: ./trait.Config.html diff --git a/substrate/frame/merkle-mountain-range/src/lib.rs b/substrate/frame/merkle-mountain-range/src/lib.rs index 0ab44711bcf5..7dfe95c83361 100644 --- a/substrate/frame/merkle-mountain-range/src/lib.rs +++ b/substrate/frame/merkle-mountain-range/src/lib.rs @@ -240,6 +240,11 @@ pub mod pallet { pub type Nodes, I: 'static = ()> = StorageMap<_, Identity, NodeIndex, HashOf, OptionQuery>; + /// Helper flag used in the runtime benchmarks for the initial setup. + #[cfg(feature = "runtime-benchmarks")] + #[pallet::storage] + pub type UseLocalStorage = StorageValue<_, bool, ValueQuery>; + #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { @@ -439,6 +444,14 @@ impl, I: 'static> Pallet { mmr.generate_ancestry_proof(prev_leaf_count) } + #[cfg(feature = "runtime-benchmarks")] + pub fn generate_mock_ancestry_proof() -> Result>, Error> + { + let leaf_count = Self::block_num_to_leaf_count(>::block_number())?; + let mmr: ModuleMmr = mmr::Mmr::new(leaf_count); + mmr.generate_mock_ancestry_proof() + } + pub fn verify_ancestry_proof( root: HashOf, ancestry_proof: primitives::AncestryProof>, diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs index 2b46357c5072..f9a4580b9bb3 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -156,7 +156,7 @@ where } /// Return the internal size of the MMR (number of nodes). - #[cfg(test)] + #[cfg(any(test, feature = "runtime-benchmarks"))] pub fn size(&self) -> NodeIndex { self.mmr.mmr_size() } @@ -252,4 +252,48 @@ where .collect(), }) } + + /// Generate an inflated ancestry proof for the latest leaf in the MMR. + /// + /// The generated proof contains all the leafs in the MMR, so this way we can generate a proof + /// with exactly `leaf_count` items. + #[cfg(feature = "runtime-benchmarks")] + pub fn generate_mock_ancestry_proof( + &self, + ) -> Result>, Error> { + use crate::ModuleMmr; + use alloc::vec; + use sp_mmr_primitives::mmr_lib::helper; + + let mmr: ModuleMmr = Mmr::new(self.leaves); + let store = >::default(); + + let mut prev_peaks = vec![]; + for peak_pos in helper::get_peaks(mmr.size()) { + let peak = store + .get_elem(peak_pos) + .map_err(|_| Error::GenerateProof)? + .ok_or(Error::GenerateProof)? + .hash(); + prev_peaks.push(peak); + } + + let mut proof_items = vec![]; + for leaf_idx in 0..self.leaves { + let leaf_pos = NodesUtils::leaf_index_to_leaf_node_index(leaf_idx); + let leaf = store + .get_elem(leaf_pos) + .map_err(|_| Error::GenerateProof)? + .ok_or(Error::GenerateProof)? + .hash(); + proof_items.push((leaf_pos, leaf)); + } + + Ok(sp_mmr_primitives::AncestryProof { + prev_peaks, + prev_leaf_count: self.leaves, + leaf_count: self.leaves, + items: proof_items, + }) + } } diff --git a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs index a39089801484..02852388b417 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs @@ -22,7 +22,6 @@ use codec::Encode; use core::iter::Peekable; use log::{debug, trace}; use sp_core::offchain::StorageKind; -use sp_io::offchain_index; use sp_mmr_primitives::{mmr_lib, mmr_lib::helper, utils::NodesUtils}; use crate::{ @@ -47,6 +46,26 @@ pub struct RuntimeStorage; /// DOES NOT support adding new items to the MMR. pub struct OffchainStorage; +impl OffchainStorage { + fn get(key: &[u8]) -> Option> { + sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) + } + + #[cfg(not(feature = "runtime-benchmarks"))] + fn set, I: 'static>(key: &[u8], value: &[u8]) { + sp_io::offchain_index::set(key, value); + } + + #[cfg(feature = "runtime-benchmarks")] + fn set, I: 'static>(key: &[u8], value: &[u8]) { + if crate::pallet::UseLocalStorage::::get() { + sp_io::offchain::local_storage_set(StorageKind::PERSISTENT, key, value); + } else { + sp_io::offchain_index::set(key, value); + } + } +} + /// A storage layer for MMR. /// /// There are two different implementations depending on the use case. @@ -78,7 +97,7 @@ where pos, ancestor_leaf_idx, key ); // Try to retrieve the element from Off-chain DB. - if let Some(elem) = sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) { + if let Some(elem) = OffchainStorage::get(&key) { return Ok(codec::Decode::decode(&mut &*elem).ok()) } @@ -93,8 +112,7 @@ where pos, ancestor_leaf_idx, ancestor_parent_hash, temp_key ); // Retrieve the element from Off-chain DB. - Ok(sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &temp_key) - .and_then(|v| codec::Decode::decode(&mut &*v).ok())) + Ok(OffchainStorage::get(&temp_key).and_then(|v| codec::Decode::decode(&mut &*v).ok())) } } @@ -203,8 +221,7 @@ where target: "runtime::mmr::offchain", "offchain db set: pos {} parent_hash {:?} key {:?}", pos, parent_hash, temp_key ); - // Indexing API is used to store the full node content. - offchain_index::set(&temp_key, &encoded_node); + OffchainStorage::set::(&temp_key, &encoded_node); } } diff --git a/substrate/frame/nfts/src/types.rs b/substrate/frame/nfts/src/types.rs index 1687a03520af..60d7c639c88c 100644 --- a/substrate/frame/nfts/src/types.rs +++ b/substrate/frame/nfts/src/types.rs @@ -193,13 +193,13 @@ pub struct ItemMetadata> { #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct ItemTip { /// The collection of the item. - pub(super) collection: CollectionId, + pub collection: CollectionId, /// An item of which the tip is sent for. - pub(super) item: ItemId, + pub item: ItemId, /// A sender of the tip. - pub(super) receiver: AccountId, + pub receiver: AccountId, /// An amount the sender is willing to tip. - pub(super) amount: Amount, + pub amount: Amount, } /// Information about the pending swap. @@ -246,9 +246,9 @@ pub enum PriceDirection { #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct PriceWithDirection { /// An amount. - pub(super) amount: Amount, + pub amount: Amount, /// A direction (send or receive). - pub(super) direction: PriceDirection, + pub direction: PriceDirection, } /// Support for up to 64 user-enabled features on a collection. @@ -518,31 +518,31 @@ impl_codec_bitflags!(CollectionRoles, u8, CollectionRole); #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct PreSignedMint { /// A collection of the item to be minted. - pub(super) collection: CollectionId, + pub collection: CollectionId, /// Item's ID. - pub(super) item: ItemId, + pub item: ItemId, /// Additional item's key-value attributes. - pub(super) attributes: Vec<(Vec, Vec)>, + pub attributes: Vec<(Vec, Vec)>, /// Additional item's metadata. - pub(super) metadata: Vec, + pub metadata: Vec, /// Restrict the claim to a particular account. - pub(super) only_account: Option, + pub only_account: Option, /// A deadline for the signature. - pub(super) deadline: Deadline, + pub deadline: Deadline, /// An optional price the claimer would need to pay for the mint. - pub(super) mint_price: Option, + pub mint_price: Option, } #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct PreSignedAttributes { /// Collection's ID. - pub(super) collection: CollectionId, + pub collection: CollectionId, /// Item's ID. - pub(super) item: ItemId, + pub item: ItemId, /// Key-value attributes. - pub(super) attributes: Vec<(Vec, Vec)>, + pub attributes: Vec<(Vec, Vec)>, /// Attributes' namespace. - pub(super) namespace: AttributeNamespace, + pub namespace: AttributeNamespace, /// A deadline for the signature. - pub(super) deadline: Deadline, + pub deadline: Deadline, } diff --git a/substrate/frame/nomination-pools/runtime-api/src/lib.rs b/substrate/frame/nomination-pools/runtime-api/src/lib.rs index 67627e0acb13..d81ad1dd4954 100644 --- a/substrate/frame/nomination-pools/runtime-api/src/lib.rs +++ b/substrate/frame/nomination-pools/runtime-api/src/lib.rs @@ -63,5 +63,11 @@ sp_api::decl_runtime_apis! { /// [`migrate_delegation`](pallet_nomination_pools::Call::migrate_delegation) /// to migrate the funds of the pool member. fn member_needs_delegate_migration(member: AccountId) -> bool; + + /// Returns the total contribution of a pool member including any balance that is unbonding. + fn member_total_balance(who: AccountId) -> Balance; + + /// Total balance contributed to the pool. + fn pool_balance(pool_id: PoolId) -> Balance; } } diff --git a/substrate/frame/nomination-pools/src/adapter.rs b/substrate/frame/nomination-pools/src/adapter.rs index 4d571855e4fe..272b3b60612b 100644 --- a/substrate/frame/nomination-pools/src/adapter.rs +++ b/substrate/frame/nomination-pools/src/adapter.rs @@ -106,9 +106,11 @@ pub trait StakeStrategy { /// Balance that can be transferred from pool account to member. /// - /// This is part of the pool balance that is not actively staked. That is, tokens that are - /// in unbonding period or unbonded. - fn transferable_balance(pool_account: Pool) -> Self::Balance; + /// This is part of the pool balance that can be withdrawn. + fn transferable_balance( + pool_account: Pool, + member_account: Member, + ) -> Self::Balance; /// Total balance of the pool including amount that is actively staked. fn total_balance(pool_account: Pool) -> Option; @@ -181,6 +183,9 @@ pub trait StakeStrategy { num_slashing_spans: u32, ) -> DispatchResult; + /// Dissolve the pool account. + fn dissolve(pool_account: Pool) -> DispatchResult; + /// Check if there is any pending slash for the pool. fn pending_slash(pool_account: Pool) -> Self::Balance; @@ -253,12 +258,15 @@ impl, AccountId = T: StakeStrategyType::Transfer } - fn transferable_balance(pool_account: Pool) -> BalanceOf { + fn transferable_balance( + pool_account: Pool, + _: Member, + ) -> BalanceOf { T::Currency::balance(&pool_account.0).saturating_sub(Self::active_stake(pool_account)) } fn total_balance(pool_account: Pool) -> Option> { - Some(T::Currency::total_balance(&pool_account.0)) + Some(T::Currency::total_balance(&pool_account.get())) } fn member_delegation_balance( @@ -300,6 +308,17 @@ impl, AccountId = T: Ok(()) } + fn dissolve(pool_account: Pool) -> DispatchResult { + defensive_assert!( + T::Currency::total_balance(&pool_account.clone().get()).is_zero(), + "dissolving pool should not have any balance" + ); + + // Defensively force set balance to zero. + T::Currency::set_balance(&pool_account.get(), Zero::zero()); + Ok(()) + } + fn pending_slash(_: Pool) -> Self::Balance { // for transfer stake strategy, slashing is greedy and never deferred. Zero::zero() @@ -360,11 +379,14 @@ impl< StakeStrategyType::Delegate } - fn transferable_balance(pool_account: Pool) -> BalanceOf { - Delegation::agent_balance(pool_account.clone().into()) + fn transferable_balance( + pool_account: Pool, + member_account: Member, + ) -> BalanceOf { + Delegation::agent_transferable_balance(pool_account.clone().into()) // pool should always be an agent. .defensive_unwrap_or_default() - .saturating_sub(Self::active_stake(pool_account)) + .min(Delegation::delegator_balance(member_account.into()).unwrap_or_default()) } fn total_balance(pool_account: Pool) -> Option> { @@ -384,12 +406,13 @@ impl< ) -> DispatchResult { match bond_type { BondType::Create => { - // first delegation - Delegation::delegate(who.into(), pool_account.into(), reward_account, amount) + // first delegation. Register agent first. + Delegation::register_agent(pool_account.clone().into(), reward_account)?; + Delegation::delegate(who.into(), pool_account.into(), amount) }, BondType::Extra => { // additional delegation - Delegation::delegate_extra(who.into(), pool_account.into(), amount) + Delegation::delegate(who.into(), pool_account.into(), amount) }, } } @@ -403,6 +426,10 @@ impl< Delegation::withdraw_delegation(who.into(), pool_account.into(), amount, num_slashing_spans) } + fn dissolve(pool_account: Pool) -> DispatchResult { + Delegation::remove_agent(pool_account.into()) + } + fn pending_slash(pool_account: Pool) -> Self::Balance { Delegation::pending_slash(pool_account.into()).defensive_unwrap_or_default() } @@ -433,6 +460,6 @@ impl< #[cfg(feature = "runtime-benchmarks")] fn remove_as_agent(pool: Pool) { - Delegation::drop_agent(pool.into()) + Delegation::migrate_to_direct_staker(pool.into()) } } diff --git a/substrate/frame/nomination-pools/src/lib.rs b/substrate/frame/nomination-pools/src/lib.rs index 70ad06e2a4da..44e3463dc9f2 100644 --- a/substrate/frame/nomination-pools/src/lib.rs +++ b/substrate/frame/nomination-pools/src/lib.rs @@ -1831,7 +1831,9 @@ pub mod pallet { /// A member has been removed from a pool. /// /// The removal can be voluntary (withdrawn all unbonded funds) or involuntary (kicked). - MemberRemoved { pool_id: PoolId, member: T::AccountId }, + /// Any funds that are still delegated (i.e. dangling delegation) are released and are + /// represented by `released_balance`. + MemberRemoved { pool_id: PoolId, member: T::AccountId, released_balance: BalanceOf }, /// The roles of a pool have been updated to the given new roles. Note that the depositor /// can never change. RolesUpdated { @@ -2008,6 +2010,8 @@ pub mod pallet { pool_id: PoolId, ) -> DispatchResult { let who = ensure_signed(origin)?; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); // If a member already exists that means they already belong to a pool @@ -2070,6 +2074,13 @@ pub mod pallet { )] pub fn bond_extra(origin: OriginFor, extra: BondExtra>) -> DispatchResult { let who = ensure_signed(origin)?; + + // ensure who is not in an un-migrated state. + ensure!( + !Self::api_member_needs_delegate_migration(who.clone()), + Error::::NotMigrated + ); + Self::do_bond_extra(who.clone(), who, extra) } @@ -2085,6 +2096,12 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::claim_payout())] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let signer = ensure_signed(origin)?; + // ensure signer is not in an un-migrated state. + ensure!( + !Self::api_member_needs_delegate_migration(signer.clone()), + Error::::NotMigrated + ); + Self::do_claim_payout(signer.clone(), signer) } @@ -2128,6 +2145,12 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; let member_account = T::Lookup::lookup(member_account)?; + // ensure member is not in an un-migrated state. + ensure!( + !Self::api_member_needs_delegate_migration(member_account.clone()), + Error::::NotMigrated + ); + let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&member_account)?; @@ -2211,6 +2234,9 @@ pub mod pallet { num_slashing_spans: u32, ) -> DispatchResult { let _ = ensure_signed(origin)?; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); + let pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; // For now we only allow a pool to withdraw unbonded if its not destroying. If the pool @@ -2257,6 +2283,12 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let caller = ensure_signed(origin)?; let member_account = T::Lookup::lookup(member_account)?; + // ensure member is not in an un-migrated state. + ensure!( + !Self::api_member_needs_delegate_migration(member_account.clone()), + Error::::NotMigrated + ); + let mut member = PoolMembers::::get(&member_account).ok_or(Error::::PoolMemberNotFound)?; let current_era = T::StakeAdapter::current_era(); @@ -2342,9 +2374,10 @@ pub mod pallet { // don't exist. This check is also defensive in cases where the unbond pool does not // update its balance (e.g. a bug in the slashing hook.) We gracefully proceed in // order to ensure members can leave the pool and it can be destroyed. - .min(T::StakeAdapter::transferable_balance(Pool::from( - bonded_pool.bonded_account(), - ))); + .min(T::StakeAdapter::transferable_balance( + Pool::from(bonded_pool.bonded_account()), + Member::from(member_account.clone()), + )); // this can fail if the pool uses `DelegateStake` strategy and the member delegation // is not claimed yet. See `Call::migrate_delegation()`. @@ -2368,9 +2401,27 @@ pub mod pallet { // member being reaped. PoolMembers::::remove(&member_account); + + // Ensure any dangling delegation is withdrawn. + let dangling_withdrawal = match T::StakeAdapter::member_delegation_balance( + Member::from(member_account.clone()), + ) { + Some(dangling_delegation) => { + T::StakeAdapter::member_withdraw( + Member::from(member_account.clone()), + Pool::from(bonded_pool.bonded_account()), + dangling_delegation, + num_slashing_spans, + )?; + dangling_delegation + }, + None => Zero::zero(), + }; + Self::deposit_event(Event::::MemberRemoved { pool_id: member.pool_id, member: member_account.clone(), + released_balance: dangling_withdrawal, }); if member_account == bonded_pool.roles.depositor { @@ -2472,6 +2523,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); let depositor_points = PoolMembers::::get(&bonded_pool.roles.depositor) @@ -2506,6 +2559,8 @@ pub mod pallet { let who = ensure_signed(origin)?; let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; ensure!(bonded_pool.state != PoolState::Destroying, Error::::CanNotChangeState); + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); if bonded_pool.can_toggle_state(&who) { bonded_pool.set_state(state); @@ -2541,6 +2596,8 @@ pub mod pallet { .can_set_metadata(&who), Error::::DoesNotHavePermission ); + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); Metadata::::mutate(pool_id, |pool_meta| *pool_meta = metadata); @@ -2617,6 +2674,9 @@ pub mod pallet { }, }; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); + match new_root { ConfigOp::Noop => (), ConfigOp::Remove => bonded_pool.roles.root = None, @@ -2663,8 +2723,9 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::chill())] pub fn chill(origin: OriginFor, pool_id: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; - let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); let depositor_points = PoolMembers::::get(&bonded_pool.roles.depositor) .ok_or(Error::::PoolMemberNotFound)? @@ -2699,7 +2760,14 @@ pub mod pallet { extra: BondExtra>, ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::do_bond_extra(who, T::Lookup::lookup(member)?, extra) + let member_account = T::Lookup::lookup(member)?; + // ensure member is not in an un-migrated state. + ensure!( + !Self::api_member_needs_delegate_migration(member_account.clone()), + Error::::NotMigrated + ); + + Self::do_bond_extra(who, member_account, extra) } /// Allows a pool member to set a claim permission to allow or disallow permissionless @@ -2716,9 +2784,14 @@ pub mod pallet { permission: ClaimPermission, ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(PoolMembers::::contains_key(&who), Error::::PoolMemberNotFound); + // ensure member is not in an un-migrated state. + ensure!( + !Self::api_member_needs_delegate_migration(who.clone()), + Error::::NotMigrated + ); + ClaimPermissions::::mutate(who, |source| { *source = permission; }); @@ -2734,6 +2807,12 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::claim_payout())] pub fn claim_payout_other(origin: OriginFor, other: T::AccountId) -> DispatchResult { let signer = ensure_signed(origin)?; + // ensure member is not in an un-migrated state. + ensure!( + !Self::api_member_needs_delegate_migration(other.clone()), + Error::::NotMigrated + ); + Self::do_claim_payout(signer, other) } @@ -2752,6 +2831,9 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); + ensure!(bonded_pool.can_manage_commission(&who), Error::::DoesNotHavePermission); let mut reward_pool = RewardPools::::get(pool_id) @@ -2788,6 +2870,9 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); + ensure!(bonded_pool.can_manage_commission(&who), Error::::DoesNotHavePermission); bonded_pool.commission.try_update_max(pool_id, max_commission)?; @@ -2810,6 +2895,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); ensure!(bonded_pool.can_manage_commission(&who), Error::::DoesNotHavePermission); bonded_pool.commission.try_update_change_rate(change_rate)?; @@ -2831,6 +2918,9 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::claim_commission())] pub fn claim_commission(origin: OriginFor, pool_id: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); + Self::do_claim_commission(who, pool_id) } @@ -2845,6 +2935,9 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::adjust_pool_deposit())] pub fn adjust_pool_deposit(origin: OriginFor, pool_id: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); + Self::do_adjust_pool_deposit(who, pool_id) } @@ -2861,6 +2954,8 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + // ensure pool is not in an un-migrated state. + ensure!(!Self::api_pool_needs_delegate_migration(pool_id), Error::::NotMigrated); ensure!(bonded_pool.can_manage_commission(&who), Error::::DoesNotHavePermission); bonded_pool.commission.claim_permission = permission.clone(); @@ -2935,9 +3030,12 @@ pub mod pallet { ); let pool_contribution = member.total_balance(); - ensure!(pool_contribution >= MinJoinBond::::get(), Error::::MinimumBondNotMet); - // the member must have some contribution to be migrated. - ensure!(pool_contribution > Zero::zero(), Error::::AlreadyMigrated); + // ensure the pool contribution is greater than the existential deposit otherwise we + // cannot transfer funds to member account. + ensure!( + pool_contribution >= T::Currency::minimum_balance(), + Error::::MinimumBondNotMet + ); let delegation = T::StakeAdapter::member_delegation_balance(Member::from(member_account.clone())); @@ -3078,16 +3176,11 @@ impl Pallet { T::Currency::total_balance(&reward_account) == Zero::zero(), "could not transfer all amount to depositor while dissolving pool" ); - defensive_assert!( - T::StakeAdapter::total_balance(Pool::from(bonded_pool.bonded_account())) - .unwrap_or_default() == - Zero::zero(), - "dissolving pool should not have any balance" - ); // NOTE: Defensively force set balance to zero. T::Currency::set_balance(&reward_account, Zero::zero()); - // NOTE: With `DelegateStake` strategy, this won't do anything. - T::Currency::set_balance(&bonded_pool.bonded_account(), Zero::zero()); + + // dissolve pool account. + let _ = T::StakeAdapter::dissolve(Pool::from(bonded_account)).defensive(); Self::deposit_event(Event::::Destroyed { pool_id: bonded_pool.id }); // Remove bonded pool metadata. @@ -3443,6 +3536,7 @@ impl Pallet { fn do_adjust_pool_deposit(who: T::AccountId, pool: PoolId) -> DispatchResult { let bonded_pool = BondedPool::::get(pool).ok_or(Error::::PoolNotFound)?; + let reward_acc = &bonded_pool.reward_account(); let pre_frozen_balance = T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), reward_acc); @@ -3525,13 +3619,8 @@ impl Pallet { // this is their balance in the pool let expected_balance = pool_member.total_balance(); - defensive_assert!( - actual_balance >= expected_balance, - "actual balance should always be greater or equal to the expected" - ); - // return the amount to be slashed. - Ok(actual_balance.defensive_saturating_sub(expected_balance)) + Ok(actual_balance.saturating_sub(expected_balance)) } /// Apply freeze on reward account to restrict it from going below ED. @@ -3869,7 +3958,13 @@ impl Pallet { return false } + // if pool does not exist, return false. + if !BondedPools::::contains_key(pool_id) { + return false + } + let pool_account = Self::generate_bonded_account(pool_id); + // true if pool is still not migrated to `DelegateStake`. T::StakeAdapter::pool_strategy(Pool::from(pool_account)) != adapter::StakeStrategyType::Delegate @@ -3903,6 +3998,22 @@ impl Pallet { }) .unwrap_or_default() } + + /// Contribution of the member in the pool. + /// + /// Includes balance that is unbonded from staking but not claimed yet from the pool, therefore + /// this balance can be higher than the staked funds. + pub fn api_member_total_balance(who: T::AccountId) -> BalanceOf { + PoolMembers::::get(who.clone()) + .map(|m| m.total_balance()) + .unwrap_or_default() + } + + /// Total balance contributed to the pool. + pub fn api_pool_balance(pool_id: PoolId) -> BalanceOf { + T::StakeAdapter::total_balance(Pool::from(Self::generate_bonded_account(pool_id))) + .unwrap_or_default() + } } impl sp_staking::OnStakingUpdate> for Pallet { diff --git a/substrate/frame/nomination-pools/src/tests.rs b/substrate/frame/nomination-pools/src/tests.rs index 28063c2ecaec..06261699a5b2 100644 --- a/substrate/frame/nomination-pools/src/tests.rs +++ b/substrate/frame/nomination-pools/src/tests.rs @@ -2364,10 +2364,10 @@ mod claim_payout { Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 3 }, Event::Withdrawn { member: 20, pool_id: 1, balance: 20, points: 20 }, - Event::MemberRemoved { pool_id: 1, member: 20 }, + Event::MemberRemoved { pool_id: 1, member: 20, released_balance: 0 }, Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 6 }, Event::Withdrawn { member: 10, pool_id: 1, balance: 10, points: 10 }, - Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, Event::Destroyed { pool_id: 1 } ] ); @@ -2939,9 +2939,9 @@ mod unbond { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 40, pool_id: 1, points: 6, balance: 6 }, - Event::MemberRemoved { pool_id: 1, member: 40 }, + Event::MemberRemoved { pool_id: 1, member: 40, released_balance: 0 }, Event::Withdrawn { member: 550, pool_id: 1, points: 92, balance: 92 }, - Event::MemberRemoved { pool_id: 1, member: 550 }, + Event::MemberRemoved { pool_id: 1, member: 550, released_balance: 0 }, Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2, era: 6 } ] @@ -3688,7 +3688,7 @@ mod withdraw_unbonded { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 550 }, - Event::MemberRemoved { pool_id: 1, member: 550 } + Event::MemberRemoved { pool_id: 1, member: 550, released_balance: 0 } ] ); assert_eq!( @@ -3709,7 +3709,7 @@ mod withdraw_unbonded { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 40 }, - Event::MemberRemoved { pool_id: 1, member: 40 } + Event::MemberRemoved { pool_id: 1, member: 40, released_balance: 0 } ] ); assert_eq!( @@ -3731,7 +3731,7 @@ mod withdraw_unbonded { vec![ Event::Unbonded { member: 10, pool_id: 1, balance: 5, points: 5, era: 9 }, Event::Withdrawn { member: 10, pool_id: 1, balance: 5, points: 5 }, - Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, Event::Destroyed { pool_id: 1 } ] ); @@ -3806,7 +3806,7 @@ mod withdraw_unbonded { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 20 }, - Event::MemberRemoved { pool_id: 1, member: 40 } + Event::MemberRemoved { pool_id: 1, member: 40, released_balance: 0 } ] ); @@ -3827,7 +3827,7 @@ mod withdraw_unbonded { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 275 }, - Event::MemberRemoved { pool_id: 1, member: 550 } + Event::MemberRemoved { pool_id: 1, member: 550, released_balance: 0 } ] ); assert!(SubPoolsStorage::::get(1).unwrap().with_era.is_empty()); @@ -3862,7 +3862,7 @@ mod withdraw_unbonded { vec![ Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5, era: 6 }, Event::Withdrawn { member: 10, pool_id: 1, points: 5, balance: 5 }, - Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, Event::Destroyed { pool_id: 1 } ] ); @@ -4018,9 +4018,9 @@ mod withdraw_unbonded { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 }, - Event::MemberRemoved { pool_id: 1, member: 100 }, + Event::MemberRemoved { pool_id: 1, member: 100, released_balance: 0 }, Event::Withdrawn { member: 200, pool_id: 1, points: 200, balance: 200 }, - Event::MemberRemoved { pool_id: 1, member: 200 } + Event::MemberRemoved { pool_id: 1, member: 200, released_balance: 0 } ] ); }); @@ -4070,7 +4070,7 @@ mod withdraw_unbonded { Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100, era: 3 }, Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 }, - Event::MemberRemoved { pool_id: 1, member: 100 } + Event::MemberRemoved { pool_id: 1, member: 100, released_balance: 0 } ] ); }); @@ -4310,7 +4310,7 @@ mod withdraw_unbonded { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 100, pool_id: 1, points: 25, balance: 25 }, - Event::MemberRemoved { pool_id: 1, member: 100 } + Event::MemberRemoved { pool_id: 1, member: 100, released_balance: 0 } ] ); }) @@ -4548,7 +4548,7 @@ mod withdraw_unbonded { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 10, pool_id: 1, points: 13, balance: 13 }, - Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, Event::Destroyed { pool_id: 1 }, ] ); @@ -4588,7 +4588,7 @@ mod withdraw_unbonded { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 20, pool_id: 1, balance: 20, points: 20 }, - Event::MemberRemoved { pool_id: 1, member: 20 } + Event::MemberRemoved { pool_id: 1, member: 20, released_balance: 0 } ] ); @@ -4626,7 +4626,7 @@ mod withdraw_unbonded { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 10, pool_id: 1, points: 10, balance: 10 }, - Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, Event::Destroyed { pool_id: 1 }, ] ); @@ -4672,7 +4672,7 @@ mod withdraw_unbonded { pool_events_since_last_call(), vec![ Event::Withdrawn { member: 10, pool_id: 1, points: 10, balance: 10 }, - Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, Event::Destroyed { pool_id: 1 }, ] ); diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs index 51f6470f90d0..7fee2a0bdb23 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs @@ -25,16 +25,16 @@ use frame_support::{ }; use mock::*; use pallet_nomination_pools::{ - BondExtra, BondedPools, Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember, - PoolMembers, PoolState, + BondExtra, BondedPools, CommissionChangeRate, ConfigOp, Error as PoolsError, + Event as PoolsEvent, LastPoolId, PoolMember, PoolMembers, PoolState, }; use pallet_staking::{ CurrentEra, Error as StakingError, Event as StakingEvent, Payee, RewardDestination, }; -use pallet_delegated_staking::{Error as DelegatedStakingError, Event as DelegatedStakingEvent}; +use pallet_delegated_staking::Event as DelegatedStakingEvent; -use sp_runtime::{bounded_btree_map, traits::Zero}; +use sp_runtime::{bounded_btree_map, traits::Zero, Perbill}; use sp_staking::Agent; #[test] @@ -152,9 +152,9 @@ fn pool_lifecycle_e2e() { pool_events_since_last_call(), vec![ PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20, released_balance: 0 }, PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 21 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 21, released_balance: 0 }, ] ); @@ -193,7 +193,7 @@ fn pool_lifecycle_e2e() { pool_events_since_last_call(), vec![ PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, PoolsEvent::Destroyed { pool_id: 1 } ] ); @@ -476,10 +476,10 @@ fn pool_slash_e2e() { vec![ // 20 had unbonded 10 safely, and 10 got slashed by half. PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20, released_balance: 0 }, // 21 unbonded all of it after the slash PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 21 } + PoolsEvent::MemberRemoved { pool_id: 1, member: 21, released_balance: 0 } ] ); assert_eq!( @@ -525,7 +525,7 @@ fn pool_slash_e2e() { pool_events_since_last_call(), vec![ PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, PoolsEvent::Destroyed { pool_id: 1 } ] ); @@ -999,7 +999,6 @@ fn pool_migration_e2e() { LegacyAdapter::set(false); // cannot migrate the member delegation unless pool is migrated first. - assert!(!Pools::api_member_needs_delegate_migration(20)); assert_noop!( Pools::migrate_delegation(RuntimeOrigin::signed(10), 20), PoolsError::::NotMigrated @@ -1031,13 +1030,17 @@ fn pool_migration_e2e() { // move to era 5 when 20 can withdraw unbonded funds. CurrentEra::::set(Some(5)); - // Unbond works even without claiming delegation. Lets unbond 22. - assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, 5)); + + // Cannot unbond without claiming delegation. Lets unbond 22. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(22), 22, 5), + PoolsError::::NotMigrated + ); // withdraw fails for 20 before claiming delegation assert_noop!( Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 10), - DelegatedStakingError::::NotDelegator + PoolsError::::NotMigrated ); let pre_claim_balance_20 = Balances::total_balance(&20); @@ -1060,17 +1063,11 @@ fn pool_migration_e2e() { assert_eq!( staking_events_since_last_call(), - vec![ - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }, - StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 5 } - ] + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 5 }] ); assert_eq!( pool_events_since_last_call(), - vec![ - PoolsEvent::Unbonded { member: 22, pool_id: 1, balance: 5, points: 5, era: 8 }, - PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 5, points: 5 }, - ] + vec![PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 5, points: 5 },] ); assert_eq!( delegated_staking_events_since_last_call(), @@ -1113,8 +1110,9 @@ fn pool_migration_e2e() { assert_eq!(Balances::total_balance(&21), pre_migrate_balance_21 + 10); // MIGRATE 22 - let pre_migrate_balance_22 = Balances::total_balance(&22); assert_eq!(Balances::total_balance_on_hold(&22), 0); + // make balance of 22 as 0. + let _ = Balances::make_free_balance_be(&22, 0); // migrate delegation for 22. assert!(Pools::api_member_needs_delegate_migration(22)); @@ -1128,17 +1126,20 @@ fn pool_migration_e2e() { ); // tokens moved to 22's account and held there. - assert_eq!(Balances::total_balance(&22), pre_migrate_balance_22 + 10); + assert_eq!(Balances::total_balance(&22), 10); assert_eq!(Balances::total_balance_on_hold(&22), 10); - // withdraw fails since 22 only unbonds at era 8. + // unbond 22 should work now + assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, 5)); + + // withdraw fails since 22 only unbonds after era 9. assert_noop!( Pools::withdraw_unbonded(RuntimeOrigin::signed(22), 22, 5), PoolsError::::CannotWithdrawAny ); // go to era when 22 can unbond - CurrentEra::::set(Some(10)); + CurrentEra::::set(Some(9)); // withdraw works now assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(22), 22, 10)); @@ -1151,6 +1152,7 @@ fn pool_migration_e2e() { staking_events_since_last_call(), vec![ StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }, StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 5 } ] ); @@ -1160,7 +1162,8 @@ fn pool_migration_e2e() { vec![ PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 10, points: 10 }, // 21 was fully unbonding and removed from pool. - PoolsEvent::MemberRemoved { member: 21, pool_id: 1 }, + PoolsEvent::MemberRemoved { member: 21, pool_id: 1, released_balance: 0 }, + PoolsEvent::Unbonded { member: 22, pool_id: 1, balance: 5, points: 5, era: 9 }, PoolsEvent::Withdrawn { member: 22, pool_id: 1, balance: 5, points: 5 }, ] ); @@ -1183,3 +1186,491 @@ fn pool_migration_e2e() { ); }) } + +#[test] +fn disable_pool_operations_on_non_migrated() { + new_test_ext().execute_with(|| { + LegacyAdapter::set(true); + assert_eq!(Balances::minimum_balance(), 5); + assert_eq!(Staking::current_era(), None); + + // create the pool with TransferStake strategy. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + // have the pool nominate. + assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + ] + ); + + let pre_20 = Balances::free_balance(20); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); + + // verify members balance is moved to pool. + assert_eq!(Balances::free_balance(20), pre_20 - 10); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },] + ); + + // we reset the adapter to `DelegateStake`. + LegacyAdapter::set(false); + + // pool is pending migration. + assert!(Pools::api_pool_needs_delegate_migration(1)); + + // ensure pool mutation is not allowed until pool is migrated. + assert_noop!( + Pools::join(RuntimeOrigin::signed(21), 10, 1), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::pool_withdraw_unbonded(RuntimeOrigin::signed(10), 1, 0), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Blocked), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::set_metadata(RuntimeOrigin::signed(10), 1, vec![1, 1]), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::update_roles( + RuntimeOrigin::signed(10), + 1, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + ), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::chill(RuntimeOrigin::signed(10), 1), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::set_commission(RuntimeOrigin::signed(10), 1, None), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::set_commission_max(RuntimeOrigin::signed(10), 1, Zero::zero()), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::set_commission_change_rate( + RuntimeOrigin::signed(10), + 1, + CommissionChangeRate { max_increase: Perbill::from_percent(1), min_delay: 2_u64 } + ), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::claim_commission(RuntimeOrigin::signed(10), 1), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::adjust_pool_deposit(RuntimeOrigin::signed(10), 1), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::set_commission_claim_permission(RuntimeOrigin::signed(10), 1, None), + PoolsError::::NotMigrated + ); + + // migrate the pool. + assert_ok!(Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1)); + assert_eq!( + delegated_staking_events_since_last_call(), + vec![DelegatedStakingEvent::Delegated { + agent: POOL1_BONDED, + delegator: DelegatedStaking::generate_proxy_delegator(Agent::from(POOL1_BONDED)) + .get(), + amount: 50 + 10 + },] + ); + + // member is pending migration. + assert!(Pools::api_member_needs_delegate_migration(20)); + + // ensure member mutation is not allowed until member's delegation is migrated. + assert_noop!( + Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::FreeBalance(5)), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::bond_extra_other(RuntimeOrigin::signed(10), 20, BondExtra::Rewards), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::claim_payout(RuntimeOrigin::signed(20)), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(20), 20, 5), + PoolsError::::NotMigrated + ); + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0), + PoolsError::::NotMigrated + ); + + // migrate 20 + assert_ok!(Pools::migrate_delegation(RuntimeOrigin::signed(10), 20)); + // now `bond_extra` for 20 works. + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::FreeBalance(5))); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 5 },] + ); + + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 5, joined: false },] + ); + + assert_eq!( + delegated_staking_events_since_last_call(), + vec![ + DelegatedStakingEvent::MigratedDelegation { + agent: POOL1_BONDED, + delegator: 20, + amount: 10 + }, + DelegatedStakingEvent::Delegated { agent: POOL1_BONDED, delegator: 20, amount: 5 }, + ] + ); + }) +} + +#[test] +fn pool_no_dangling_delegation() { + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + // pool creator + let alice = 10; + let bob = 20; + let charlie = 21; + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(alice), 40, alice, alice, alice)); + assert_eq!(LastPoolId::::get(), 1); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: alice, pool_id: 1 }, + PoolsEvent::Bonded { member: alice, pool_id: 1, bonded: 40, joined: true }, + ] + ); + assert_eq!( + delegated_staking_events_since_last_call(), + vec![DelegatedStakingEvent::Delegated { + agent: POOL1_BONDED, + + delegator: alice, + amount: 40 + },] + ); + + assert_eq!( + Payee::::get(POOL1_BONDED), + Some(RewardDestination::Account(POOL1_REWARD)) + ); + + // have two members join + assert_ok!(Pools::join(RuntimeOrigin::signed(bob), 20, 1)); + assert_ok!(Pools::join(RuntimeOrigin::signed(charlie), 20, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 } + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: bob, pool_id: 1, bonded: 20, joined: true }, + PoolsEvent::Bonded { member: charlie, pool_id: 1, bonded: 20, joined: true }, + ] + ); + assert_eq!( + delegated_staking_events_since_last_call(), + vec![ + DelegatedStakingEvent::Delegated { + agent: POOL1_BONDED, + delegator: bob, + amount: 20 + }, + DelegatedStakingEvent::Delegated { + agent: POOL1_BONDED, + delegator: charlie, + amount: 20 + }, + ] + ); + + // now let's progress a bit. + CurrentEra::::set(Some(1)); + + // bob is completely unbonding + assert_ok!(Pools::unbond(RuntimeOrigin::signed(bob), 20, 20)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 20 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: bob, pool_id: 1, balance: 20, points: 20, era: 4 }] + ); + + // this era will get slashed + CurrentEra::::set(Some(2)); + + assert_ok!(Pools::unbond(RuntimeOrigin::signed(alice), 10, 10)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(charlie), 21, 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + ] + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: alice, pool_id: 1, balance: 10, points: 10, era: 5 }, + PoolsEvent::Unbonded { + member: charlie, + pool_id: 1, + balance: 10, + points: 10, + era: 5 + }, + ] + ); + + // At this point, bob's 20 that is unlocking is safe from slash, 10 (alice) + 10 (charlie) + // are also unlocking but vulnerable to slash, and another 40 are active and vulnerable to + // slash. Let's slash half of them. + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 30, + &mut Default::default(), + &mut Default::default(), + 2, // slash era 2, affects chunks at era 5 onwards. + ); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }] + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + // unbonding pool of 20 for era 5 has been slashed to 10 + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 10 }, + // active stake of 40 has been slashed to half + PoolsEvent::PoolSlashed { pool_id: 1, balance: 20 } /* no slash to era 4 + * unbonding pool */ + ] + ); + + // alice's initial stake of 40 is reduced to half + assert_eq!(PoolMembers::::get(alice).unwrap().total_balance(), 20); + // bob unbonded in era 1 and is safe from slash + assert_eq!(PoolMembers::::get(bob).unwrap().total_balance(), 20); + // charlie's initial stake of 20 is slashed to half + assert_eq!(PoolMembers::::get(charlie).unwrap().total_balance(), 10); + + // apply pending slash to alice. + assert_eq!(Pools::api_member_pending_slash(alice), 20); + assert_ok!(Pools::apply_slash(RuntimeOrigin::signed(10), alice)); + // apply pending slash to charlie. + assert_eq!(Pools::api_member_pending_slash(charlie), 10); + assert_ok!(Pools::apply_slash(RuntimeOrigin::signed(10), charlie)); + // no pending slash for bob + assert_eq!(Pools::api_member_pending_slash(bob), 0); + + assert_eq!( + delegated_staking_events_since_last_call(), + vec![ + DelegatedStakingEvent::Slashed { + agent: POOL1_BONDED, + delegator: alice, + amount: 20 + }, + DelegatedStakingEvent::Slashed { + agent: POOL1_BONDED, + delegator: charlie, + amount: 10 + }, + ] + ); + + // go forward to an era after PostUnbondingPoolsWindow = 10 ends for era 5. + CurrentEra::::set(Some(15)); + // At this point subpools will all be merged in no-era causing Bob to lose some value while + // Alice and Charlie will gain some value. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(charlie), charlie, 10)); + + // Now alice and charlie has less balance locked than their contribution. + assert_eq!(Balances::total_balance_on_hold(&alice), 20); + assert_eq!(PoolMembers::::get(alice).unwrap().total_balance(), 22); + assert_eq!(Balances::total_balance_on_hold(&charlie), 10); + assert_eq!(PoolMembers::::get(charlie).unwrap().total_balance(), 12); + + // and bob has more balance locked than his contribution. + assert_eq!(Balances::total_balance_on_hold(&bob), 20); + assert_eq!(PoolMembers::::get(bob).unwrap().total_balance(), 15); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: charlie, + pool_id: 1, + balance: 5, + points: 5, + era: 18 + }] + ); + + // When bob withdraws all, he gets all his locked funds back. + let bob_pre_withdraw_balance = Balances::free_balance(&bob); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(bob), bob, 0)); + assert_eq!(Balances::free_balance(&bob), bob_pre_withdraw_balance + 20); + assert_eq!(Balances::total_balance_on_hold(&bob), 0); + assert!(!PoolMembers::::contains_key(bob)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 30 },] + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { pool_id: 1, member: bob, balance: 15, points: 20 }, + // dangling delegation of 5 is released + PoolsEvent::MemberRemoved { pool_id: 1, member: bob, released_balance: 5 }, + ] + ); + assert_eq!( + delegated_staking_events_since_last_call(), + vec![ + DelegatedStakingEvent::Released { agent: POOL1_BONDED, delegator: bob, amount: 15 }, + // the second release is the dangling delegation when member is removed. + DelegatedStakingEvent::Released { agent: POOL1_BONDED, delegator: bob, amount: 5 }, + ] + ); + + // Charlie can withdraw as much as he has locked. + CurrentEra::::set(Some(18)); + let charlie_pre_withdraw_balance = Balances::free_balance(&charlie); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(charlie), charlie, 0)); + // Charlie's total balance was 12, but we don't have enough funds to unlock. We try the best + // effort and unlock 10. + assert_eq!(Balances::free_balance(&charlie), charlie_pre_withdraw_balance + 10); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 5 },] + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { pool_id: 1, member: charlie, balance: 10, points: 15 }, + PoolsEvent::MemberRemoved { member: charlie, pool_id: 1, released_balance: 0 } + ] + ); + assert_eq!( + delegated_staking_events_since_last_call(), + vec![DelegatedStakingEvent::Released { + agent: POOL1_BONDED, + delegator: charlie, + amount: 10 + },] + ); + + // Set pools to destroying so alice can withdraw + assert_ok!(Pools::set_state(RuntimeOrigin::signed(alice), 1, PoolState::Destroying)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(alice), alice, 30)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 15 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + PoolsEvent::Unbonded { + member: alice, + pool_id: 1, + points: 15, + balance: 15, + era: 21 + } + ] + ); + + CurrentEra::::set(Some(21)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(alice), alice, 0)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 15 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: alice, pool_id: 1, balance: 20, points: 25 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: alice, released_balance: 0 }, + PoolsEvent::Destroyed { pool_id: 1 } + ] + ); + + // holds for all members are released. + assert_eq!(Balances::total_balance_on_hold(&alice), 0); + assert_eq!(Balances::total_balance_on_hold(&bob), 0); + assert_eq!(Balances::total_balance_on_hold(&charlie), 0); + }); +} diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index ed47932a323b..d1bc4ef8ff28 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -154,11 +154,14 @@ impl pallet_nomination_pools::adapter::StakeStrategy for MockAdapter { } DelegateStake::strategy_type() } - fn transferable_balance(pool_account: Pool) -> Self::Balance { + fn transferable_balance( + pool_account: Pool, + member_account: Member, + ) -> Self::Balance { if LegacyAdapter::get() { - return TransferStake::transferable_balance(pool_account) + return TransferStake::transferable_balance(pool_account, member_account) } - DelegateStake::transferable_balance(pool_account) + DelegateStake::transferable_balance(pool_account, member_account) } fn total_balance(pool_account: Pool) -> Option { @@ -200,6 +203,13 @@ impl pallet_nomination_pools::adapter::StakeStrategy for MockAdapter { DelegateStake::member_withdraw(who, pool_account, amount, num_slashing_spans) } + fn dissolve(pool_account: Pool) -> DispatchResult { + if LegacyAdapter::get() { + return TransferStake::dissolve(pool_account) + } + DelegateStake::dissolve(pool_account) + } + fn pending_slash(pool_account: Pool) -> Self::Balance { if LegacyAdapter::get() { return TransferStake::pending_slash(pool_account) diff --git a/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs index aa9135025900..28e978bba0e5 100644 --- a/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs +++ b/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs @@ -145,9 +145,9 @@ fn pool_lifecycle_e2e() { pool_events_since_last_call(), vec![ PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20, released_balance: 0 }, PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 21 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 21, released_balance: 0 }, ] ); @@ -186,7 +186,7 @@ fn pool_lifecycle_e2e() { pool_events_since_last_call(), vec![ PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, PoolsEvent::Destroyed { pool_id: 1 } ] ); @@ -275,7 +275,7 @@ fn destroy_pool_with_erroneous_consumer() { pool_events_since_last_call(), vec![ PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, PoolsEvent::Destroyed { pool_id: 1 } ] ); @@ -558,10 +558,10 @@ fn pool_slash_e2e() { vec![ // 20 had unbonded 10 safely, and 10 got slashed by half. PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20, released_balance: 0 }, // 21 unbonded all of it after the slash PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 21 } + PoolsEvent::MemberRemoved { pool_id: 1, member: 21, released_balance: 0 } ] ); assert_eq!( @@ -607,7 +607,7 @@ fn pool_slash_e2e() { pool_events_since_last_call(), vec![ PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, PoolsEvent::Destroyed { pool_id: 1 } ] ); diff --git a/substrate/frame/preimage/src/benchmarking.rs b/substrate/frame/preimage/src/benchmarking.rs index 2d3bec16b818..3d0c5b900579 100644 --- a/substrate/frame/preimage/src/benchmarking.rs +++ b/substrate/frame/preimage/src/benchmarking.rs @@ -116,7 +116,7 @@ benchmarks! { T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, hash ) verify { - let ticket = TicketOf::::new(¬er, Footprint { count: 1, size: MAX_SIZE as u64 }).unwrap().unwrap(); + let ticket = TicketOf::::new(¬er, Footprint { count: 1, size: MAX_SIZE as u64 }).unwrap(); let s = RequestStatus::Requested { maybe_ticket: Some((noter, ticket)), count: 1, maybe_len: Some(MAX_SIZE) }; assert_eq!(RequestStatusFor::::get(&hash), Some(s)); } diff --git a/substrate/frame/preimage/src/lib.rs b/substrate/frame/preimage/src/lib.rs index 30056fc6d9a4..658e7fec5348 100644 --- a/substrate/frame/preimage/src/lib.rs +++ b/substrate/frame/preimage/src/lib.rs @@ -124,8 +124,6 @@ pub mod pallet { type ManagerOrigin: EnsureOrigin; /// A means of providing some cost while data is stored on-chain. - /// - /// Should never return a `None`, implying no cost for a non-empty preimage. type Consideration: Consideration; } @@ -162,8 +160,6 @@ pub mod pallet { TooMany, /// Too few hashes were requested to be upgraded (i.e. zero). TooFew, - /// No ticket with a cost was returned by [`Config::Consideration`] to store the preimage. - NoCost, } /// A reason for this pallet placing a hold on funds. @@ -274,10 +270,10 @@ impl Pallet { // unreserve deposit T::Currency::unreserve(&who, amount); // take consideration - let Ok(Some(ticket)) = + let Ok(ticket) = T::Consideration::new(&who, Footprint::from_parts(1, len as usize)) + .defensive_proof("Unexpected inability to take deposit after unreserved") else { - defensive!("None ticket or inability to take deposit after unreserved"); return true }; RequestStatus::Unrequested { ticket: (who, ticket), len } @@ -288,10 +284,12 @@ impl Pallet { T::Currency::unreserve(&who, deposit); // take consideration if let Some(len) = maybe_len { - let Ok(Some(ticket)) = + let Ok(ticket) = T::Consideration::new(&who, Footprint::from_parts(1, len as usize)) + .defensive_proof( + "Unexpected inability to take deposit after unreserved", + ) else { - defensive!("None ticket or inability to take deposit after unreserved"); return true }; Some((who, ticket)) @@ -351,8 +349,7 @@ impl Pallet { RequestStatus::Requested { maybe_ticket: None, count: 1, maybe_len: Some(len) }, (None, Some(depositor)) => { let ticket = - T::Consideration::new(depositor, Footprint::from_parts(1, len as usize))? - .ok_or(Error::::NoCost)?; + T::Consideration::new(depositor, Footprint::from_parts(1, len as usize))?; RequestStatus::Unrequested { ticket: (depositor.clone(), ticket), len } }, }; diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml new file mode 100644 index 000000000000..747c2283e217 --- /dev/null +++ b/substrate/frame/revive/Cargo.toml @@ -0,0 +1,123 @@ +[package] +name = "pallet-revive" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +build = "build.rs" +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "FRAME pallet for PolkaVM contracts." +readme = "README.md" +include = ["CHANGELOG.md", "README.md", "build.rs", "src/**/*"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +paste = { workspace = true } +polkavm = { version = "0.10.0", default-features = false } +bitflags = { workspace = true } +codec = { features = [ + "derive", + "max-encoded-len", +], workspace = true } +scale-info = { features = ["derive"], workspace = true } +log = { workspace = true } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +impl-trait-for-tuples = { workspace = true } + +# Substrate Dependencies +environmental = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { optional = true, workspace = true } +pallet-revive-fixtures = { workspace = true, default-features = false } +pallet-revive-uapi = { workspace = true, default-features = true } +pallet-revive-proc-macro = { workspace = true, default-features = true } +sp-api = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +xcm = { workspace = true } +xcm-builder = { workspace = true } + +[dev-dependencies] +array-bytes = { workspace = true, default-features = true } +assert_matches = { workspace = true } +pretty_assertions = { workspace = true } +wat = { workspace = true } +pallet-revive-fixtures = { workspace = true, default-features = true } + +# Polkadot Dependencies +xcm-builder = { workspace = true, default-features = true } + +# Substrate Dependencies +pallet-balances = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } +pallet-assets = { workspace = true, default-features = true } +pallet-proxy = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } + +[features] +default = ["std"] +# enabling this feature will require having a riscv toolchain installed +# if no tests are ran and runtime benchmarks will not work +# apart from this the pallet will stay functional +riscv = ["pallet-revive-fixtures/riscv"] +std = [ + "codec/std", + "environmental/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances?/std", + "pallet-proxy/std", + "pallet-revive-fixtures/std", + "pallet-timestamp/std", + "pallet-utility/std", + "polkavm/std", + "scale-info/std", + "serde", + "sp-api/std", + "sp-core/std", + "sp-io/std", + "sp-keystore/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "xcm/std", +] +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-proxy/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-utility/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "pallet-message-queue/try-runtime", + "pallet-proxy/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-utility/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/revive/README.md b/substrate/frame/revive/README.md new file mode 100644 index 000000000000..5352e636c252 --- /dev/null +++ b/substrate/frame/revive/README.md @@ -0,0 +1,104 @@ +# Revive Pallet + +This is an **experimental** module that provides functionality for the runtime to deploy and execute PolkaVM +smart-contracts. It is a heavily modified `pallet_contracts` fork. + +## Overview + +This module extends accounts based on the [`frame_support::traits::fungible`] traits to have smart-contract +functionality. It can be used with other modules that implement accounts based on [`frame_support::traits::fungible`]. +These "smart-contract accounts" have the ability to instantiate smart-contracts and make calls to other contract and +non-contract accounts. + +The smart-contract code is stored once, and later retrievable via its `code_hash`. This means that multiple +smart-contracts can be instantiated from the same `code`, without replicating the code each time. + +When a smart-contract is called, its associated code is retrieved via the code hash and gets executed. This call can +alter the storage entries of the smart-contract account, instantiate new smart-contracts, or call other smart-contracts. + +Finally, when an account is reaped, its associated code and storage of the smart-contract account will also be deleted. + +### Weight + +Senders must specify a [`Weight`](https://paritytech.github.io/substrate/master/sp_weights/struct.Weight.html) limit +with every call, as all instructions invoked by the smart-contract require weight. Unused weight is refunded after the +call, regardless of the execution outcome. + +If the weight limit is reached, then all calls and state changes (including balance transfers) are only reverted at the +current call's contract level. For example, if contract A calls B and B runs out of weight mid-call, then all of B's +calls are reverted. Assuming correct error handling by contract A, A's other calls and state changes still persist. + +One `ref_time` `Weight` is defined as one picosecond of execution time on the runtime's reference machine. + +### Revert Behaviour + +Contract call failures are not cascading. When failures occur in a sub-call, they do not "bubble up", and the call will +only revert at the specific contract level. For example, if contract A calls contract B, and B fails, A can decide how +to handle that failure, either proceeding or reverting A's changes. + +## Interface + +### Dispatchable functions + +Those are documented in the [reference +documentation](https://paritytech.github.io/substrate/master/pallet_revive/index.html#dispatchable-functions). + +## Usage + +This module executes PolkaVM smart contracts. These can potentially be written in any language that compiles to +RISC-V. For now, the only officially supported languages are Solidity (via [`revive`](https://github.com/xermicus/revive)) +and Rust (check the `fixtures` directory for Rust examples). + +## Debugging + +Contracts can emit messages to the client when called as RPC through the +[`debug_message`](https://paritytech.github.io/substrate/master/pallet_revive/trait.SyscallDocs.html#tymethod.debug_message) +API. + +Those messages are gathered into an internal buffer and sent to the RPC client. It is up to the individual client if +and how those messages are presented to the user. + +This buffer is also printed as a debug message. In order to see these messages on the node console the log level for the +`runtime::revive` target needs to be raised to at least the `debug` level. However, those messages are easy to +overlook because of the noise generated by block production. A good starting point for observing them on the console is +using this command line in the root directory of the Substrate repository: + +```bash +cargo run --release -- --dev -lerror,runtime::revive=debug +``` + +This raises the log level of `runtime::revive` to `debug` and all other targets to `error` in order to prevent them +from spamming the console. + +`--dev`: Use a dev chain spec `--tmp`: Use temporary storage for chain data (the chain state is deleted on exit) + +## Host function tracing + +For contract authors, it can be a helpful debugging tool to see which host functions are called, with which arguments, +and what the result was. + +In order to see these messages on the node console, the log level for the `runtime::revive::strace` target needs to +be raised to the `trace` level. + +Example: + +```bash +cargo run --release -- --dev -lerror,runtime::revive::strace=trace,runtime::revive=debug +``` + +## Unstable Interfaces + +Driven by the desire to have an iterative approach in developing new contract interfaces this pallet contains the +concept of an unstable interface. Akin to the rust nightly compiler it allows us to add new interfaces but mark them as +unstable so that contract languages can experiment with them and give feedback before we stabilize those. + +In order to access interfaces which don't have a stable `#[api_version(x)]` in [`runtime.rs`](src/wasm/runtime.rs) +one need to set `pallet_revive::Config::UnsafeUnstableInterface` to `ConstU32`. +**It should be obvious that any production runtime should never be compiled with this feature: In addition to be +subject to change or removal those interfaces might not have proper weights associated with them and are therefore +considered unsafe**. + +New interfaces are generally added as unstable and might go through several iterations before they are promoted to a +stable interface. + +License: Apache-2.0 diff --git a/substrate/frame/revive/build.rs b/substrate/frame/revive/build.rs new file mode 100644 index 000000000000..ca8e62df6047 --- /dev/null +++ b/substrate/frame/revive/build.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::io::Write; + +/// We start with version 2 instead of 0 when adding the pallet. +/// +/// Because otherwise we can't test any migrations since they require the storage version +/// to be lower than the pallet version in order to be triggerd. With the pallet version +/// at the minimum (0) this would not work. +const LOWEST_STORAGE_VERSION: u16 = 2; + +/// Get the latest migration version. +/// +/// Find the highest version number from the available migration files. +/// Each migration file should follow the naming convention `vXX.rs`, where `XX` is the version +/// number. +fn get_latest_version() -> u16 { + let Ok(dir) = std::fs::read_dir("src/migration") else { return LOWEST_STORAGE_VERSION }; + dir.filter_map(|entry| { + let file_name = entry.as_ref().ok()?.file_name(); + let file_name = file_name.to_str()?; + if file_name.starts_with('v') && file_name.ends_with(".rs") { + let version = &file_name[1..&file_name.len() - 3]; + let version = version.parse::().ok()?; + + // Ensure that the version matches the one defined in the file. + let path = entry.unwrap().path(); + let file_content = std::fs::read_to_string(&path).ok()?; + assert!( + file_content.contains(&format!("const VERSION: u16 = {}", version)), + "Invalid MigrationStep::VERSION in {:?}", + path + ); + + return Some(version) + } + None + }) + .max() + .unwrap_or(LOWEST_STORAGE_VERSION) +} + +/// Generates a module that exposes the latest migration version, and the benchmark migrations type. +fn main() -> Result<(), Box> { + let out_dir = std::env::var("OUT_DIR")?; + let path = std::path::Path::new(&out_dir).join("migration_codegen.rs"); + let mut f = std::fs::File::create(path)?; + let version = get_latest_version(); + write!( + f, + " + pub mod codegen {{ + use crate::NoopMigration; + /// The latest migration version, pulled from the latest migration file. + pub const LATEST_MIGRATION_VERSION: u16 = {version}; + /// The Migration Steps used for benchmarking the migration framework. + pub type BenchMigrations = (NoopMigration<{}>, NoopMigration<{version}>); + }}", + version - 1, + )?; + + Ok(()) +} diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml new file mode 100644 index 000000000000..9e54acdace70 --- /dev/null +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pallet-revive-fixtures" +publish = true +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +description = "Fixtures for testing and benchmarking" + +[lints] +workspace = true + +[dependencies] +frame-system = { workspace = true, default-features = true, optional = true } +sp-runtime = { workspace = true, default-features = true, optional = true } +anyhow = { workspace = true } + +[build-dependencies] +parity-wasm = { workspace = true } +tempfile = { workspace = true } +toml = { workspace = true } +polkavm-linker = { version = "0.10.0" } +anyhow = { workspace = true } + +[features] +default = ["std"] +# only if the feature is set we are building the test fixtures +# this is because it requires a custom toolchain supporting polkavm +# we will remove this once there is an upstream toolchain +riscv = [] +# only when std is enabled all fixtures are available +std = [ + "anyhow/std", + "frame-system", + "sp-runtime", +] diff --git a/substrate/frame/revive/fixtures/build.rs b/substrate/frame/revive/fixtures/build.rs new file mode 100644 index 000000000000..ed981a75467b --- /dev/null +++ b/substrate/frame/revive/fixtures/build.rs @@ -0,0 +1,214 @@ +// 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. + +//! Compile text fixtures to PolkaVM binaries. +use anyhow::Result; + +fn main() -> Result<()> { + build::run() +} + +#[cfg(feature = "riscv")] +mod build { + use super::Result; + use anyhow::{bail, Context}; + use std::{ + cfg, env, fs, + path::{Path, PathBuf}, + process::Command, + }; + + /// A contract entry. + struct Entry { + /// The path to the contract source file. + path: PathBuf, + } + + impl Entry { + /// Create a new contract entry from the given path. + fn new(path: PathBuf) -> Self { + Self { path } + } + + /// Return the path to the contract source file. + fn path(&self) -> &str { + self.path.to_str().expect("path is valid unicode; qed") + } + + /// Return the name of the contract. + fn name(&self) -> &str { + self.path + .file_stem() + .expect("file exits; qed") + .to_str() + .expect("name is valid unicode; qed") + } + + /// Return the name of the polkavm file. + fn out_filename(&self) -> String { + format!("{}.polkavm", self.name()) + } + } + + /// Collect all contract entries from the given source directory. + /// Contracts that have already been compiled are filtered out. + fn collect_entries(contracts_dir: &Path) -> Vec { + fs::read_dir(contracts_dir) + .expect("src dir exists; qed") + .filter_map(|file| { + let path = file.expect("file exists; qed").path(); + if path.extension().map_or(true, |ext| ext != "rs") { + return None + } + + Some(Entry::new(path)) + }) + .collect::>() + } + + /// Create a `Cargo.toml` to compile the given contract entries. + fn create_cargo_toml<'a>( + fixtures_dir: &Path, + entries: impl Iterator, + output_dir: &Path, + ) -> Result<()> { + let mut cargo_toml: toml::Value = toml::from_str(include_str!("./build/Cargo.toml"))?; + let mut set_dep = |name, path| -> Result<()> { + cargo_toml["dependencies"][name]["path"] = toml::Value::String( + fixtures_dir.join(path).canonicalize()?.to_str().unwrap().to_string(), + ); + Ok(()) + }; + set_dep("uapi", "../uapi")?; + set_dep("common", "./contracts/common")?; + + cargo_toml["bin"] = toml::Value::Array( + entries + .map(|entry| { + let name = entry.name(); + let path = entry.path(); + toml::Value::Table(toml::toml! { + name = name + path = path + }) + }) + .collect::>(), + ); + + let cargo_toml = toml::to_string_pretty(&cargo_toml)?; + fs::write(output_dir.join("Cargo.toml"), cargo_toml).map_err(Into::into) + } + + fn invoke_build(current_dir: &Path) -> Result<()> { + let encoded_rustflags = [ + "-Crelocation-model=pie", + "-Clink-arg=--emit-relocs", + "-Clink-arg=--export-dynamic-symbol=__polkavm_symbol_export_hack__*", + ] + .join("\x1f"); + + let build_res = Command::new(env::var("CARGO")?) + .current_dir(current_dir) + .env_clear() + .env("PATH", env::var("PATH").unwrap_or_default()) + .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) + .env("RUSTUP_TOOLCHAIN", "rve-nightly") + .env("RUSTC_BOOTSTRAP", "1") + .env("RUSTUP_HOME", env::var("RUSTUP_HOME").unwrap_or_default()) + .args([ + "build", + "--release", + "--target=riscv32ema-unknown-none-elf", + "-Zbuild-std=core", + "-Zbuild-std-features=panic_immediate_abort", + ]) + .output() + .expect("failed to execute process"); + + if build_res.status.success() { + return Ok(()) + } + + let stderr = String::from_utf8_lossy(&build_res.stderr); + + if stderr.contains("'rve-nightly' is not installed") { + eprintln!("RISC-V toolchain is not installed.\nDownload and install toolchain from https://github.com/paritytech/rustc-rv32e-toolchain."); + eprintln!("{}", stderr); + } else { + eprintln!("{}", stderr); + } + + bail!("Failed to build contracts"); + } + + /// Post-process the compiled code. + fn post_process(input_path: &Path, output_path: &Path) -> Result<()> { + let mut config = polkavm_linker::Config::default(); + config.set_strip(true); + let orig = + fs::read(input_path).with_context(|| format!("Failed to read {:?}", input_path))?; + let linked = polkavm_linker::program_from_elf(config, orig.as_ref()) + .map_err(|err| anyhow::format_err!("Failed to link polkavm program: {}", err))?; + fs::write(output_path, linked).map_err(Into::into) + } + + /// Write the compiled contracts to the given output directory. + fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec) -> Result<()> { + for entry in entries { + post_process( + &build_dir.join("target/riscv32ema-unknown-none-elf/release").join(entry.name()), + &out_dir.join(entry.out_filename()), + )?; + } + + Ok(()) + } + + pub fn run() -> Result<()> { + let fixtures_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")?.into(); + let contracts_dir = fixtures_dir.join("contracts"); + let uapi_dir = fixtures_dir.parent().expect("uapi dir exits; qed").join("uapi"); + let out_dir: PathBuf = env::var("OUT_DIR")?.into(); + + // the fixtures have a dependency on the uapi crate + println!("cargo::rerun-if-changed={}", fixtures_dir.display()); + println!("cargo::rerun-if-changed={}", uapi_dir.display()); + + let entries = collect_entries(&contracts_dir); + if entries.is_empty() { + return Ok(()) + } + + let tmp_dir = tempfile::tempdir()?; + let tmp_dir_path = tmp_dir.path(); + + create_cargo_toml(&fixtures_dir, entries.iter(), tmp_dir.path())?; + invoke_build(tmp_dir_path)?; + + write_output(tmp_dir_path, &out_dir, entries)?; + Ok(()) + } +} + +#[cfg(not(feature = "riscv"))] +mod build { + use super::Result; + + pub fn run() -> Result<()> { + Ok(()) + } +} diff --git a/substrate/frame/revive/fixtures/build/Cargo.toml b/substrate/frame/revive/fixtures/build/Cargo.toml new file mode 100644 index 000000000000..7dead51b2306 --- /dev/null +++ b/substrate/frame/revive/fixtures/build/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "contracts" +publish = false +version = "1.0.0" +edition = "2021" + +# Binary targets are injected dynamically by the build script. +[[bin]] + +# All paths are injected dynamically by the build script. +[dependencies] +uapi = { package = 'pallet-revive-uapi', path = "", default-features = false } +common = { package = 'pallet-revive-fixtures-common', path = "" } +polkavm-derive = { version = "0.10.0" } + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 diff --git a/substrate/frame/revive/fixtures/contracts/balance.rs b/substrate/frame/revive/fixtures/contracts/balance.rs new file mode 100644 index 000000000000..4011b8379cbf --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/balance.rs @@ -0,0 +1,36 @@ +// 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. + +#![no_std] +#![no_main] + +use common::output; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + // Initialize buffer with 1s so that we can check that it is overwritten. + output!(balance, [1u8; 8], api::balance,); + + // Assert that the balance is 0. + assert_eq!(&[0u8; 8], balance); +} diff --git a/substrate/frame/revive/fixtures/contracts/call.rs b/substrate/frame/revive/fixtures/contracts/call.rs new file mode 100644 index 000000000000..a75aee65c205 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This calls another contract as passed as its account id. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + callee_input: [u8; 4], + callee_addr: [u8; 32], + ); + + // Call the callee + api::call( + uapi::CallFlags::empty(), + callee_addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + callee_input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_return_code.rs b/substrate/frame/revive/fixtures/contracts/call_return_code.rs new file mode 100644 index 000000000000..32654d59ef1f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_return_code.rs @@ -0,0 +1,56 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This calls the supplied dest and transfers 100 balance during this call and copies +//! the return code of this call to the output buffer. +//! It also forwards its input to the callee. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 100, + callee_addr: [u8; 32], + input: [u8], + ); + + // Call the callee + let err_code = match api::call( + uapi::CallFlags::empty(), + callee_addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &100u64.to_le_bytes(), // Value transferred to the contract. + input, + None, + ) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_runtime.rs b/substrate/frame/revive/fixtures/contracts/call_runtime.rs new file mode 100644 index 000000000000..2b132398fb68 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_runtime.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This passes its input to `call_runtime` and returns the return value to its caller. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + // Fixture calls should fit into 100 bytes. + input!(100, call: [u8], ); + + // Use the call passed as input to call the runtime. + let err_code = match api::call_runtime(call) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs new file mode 100644 index 000000000000..1323c8c5d55d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 512, + callee_input: [u8; 4], + callee_addr: [u8; 32], + call: [u8], + ); + + // Use the call passed as input to call the runtime. + api::call_runtime(call).unwrap(); + + // Call the callee + api::call( + uapi::CallFlags::empty(), + callee_addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + callee_input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs new file mode 100644 index 000000000000..a078162d7995 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs @@ -0,0 +1,51 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls the account_id with the flags and value. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 256, + callee_addr: [u8; 32], + flags: u32, + value: u64, + forwarded_input: [u8], + ); + + api::call( + uapi::CallFlags::from_bits(flags).unwrap(), + callee_addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &value.to_le_bytes(), // Value transferred to the contract. + forwarded_input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs new file mode 100644 index 000000000000..a5356924f24f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls the account_id with the 2D Weight limit. +//! It returns the result of the call as output data. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 256, + callee_addr: [u8; 32], + ref_time: u64, + proof_size: u64, + forwarded_input: [u8], + ); + + api::call( + uapi::CallFlags::empty(), + callee_addr, + ref_time, + proof_size, + None, // No deposit limit. + &0u64.to_le_bytes(), // value transferred to the contract. + forwarded_input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/caller_contract.rs b/substrate/frame/revive/fixtures/contracts/caller_contract.rs new file mode 100644 index 000000000000..2fa11df82d04 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/caller_contract.rs @@ -0,0 +1,145 @@ +// 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. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(code_hash: [u8; 32],); + + // The value to transfer on instantiation and calls. Chosen to be greater than existential + // deposit. + let value = 32768u64.to_le_bytes(); + let salt = [0u8; 0]; + + // Callee will use the first 4 bytes of the input to return an exit status. + let input = [0u8, 1, 34, 51, 68, 85, 102, 119]; + let reverted_input = [1u8, 34, 51, 68, 85, 102, 119]; + + // Fail to deploy the contract since it returns a non-zero exit status. + let res = api::instantiate( + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, + &reverted_input, + None, + None, + &salt, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); + + // Fail to deploy the contract due to insufficient ref_time weight. + let res = api::instantiate( + code_hash, 1u64, // too little ref_time weight + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, &input, None, None, &salt, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + + // Fail to deploy the contract due to insufficient proof_size weight. + let res = api::instantiate( + code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 1u64, // Too little proof_size weight + None, // No deposit limit. + &value, &input, None, None, &salt, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + + // Deploy the contract successfully. + let mut callee = [0u8; 32]; + let callee = &mut &mut callee[..]; + + api::instantiate( + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, + &input, + Some(callee), + None, + &salt, + ) + .unwrap(); + assert_eq!(callee.len(), 32); + + // Call the new contract and expect it to return failing exit code. + let res = api::call( + uapi::CallFlags::empty(), + callee, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, + &reverted_input, + None, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); + + // Fail to call the contract due to insufficient ref_time weight. + let res = api::call( + uapi::CallFlags::empty(), + callee, + 1u64, // Too little ref_time weight. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, + &input, + None, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + + // Fail to call the contract due to insufficient proof_size weight. + let res = api::call( + uapi::CallFlags::empty(), + callee, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 1u64, // too little proof_size weight + None, // No deposit limit. + &value, + &input, + None, + ); + assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + + // Call the contract successfully. + let mut output = [0u8; 4]; + api::call( + uapi::CallFlags::empty(), + callee, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &value, + &input, + Some(&mut &mut output[..]), + ) + .unwrap(); + assert_eq!(&output, &input[4..]) +} diff --git a/substrate/frame/revive/fixtures/contracts/caller_is_origin_n.rs b/substrate/frame/revive/fixtures/contracts/caller_is_origin_n.rs new file mode 100644 index 000000000000..fd6f59802fa0 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/caller_is_origin_n.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls caller_is_origin `n` times. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(n: u32, ); + + for _ in 0..n { + let _ = api::caller_is_origin(); + } +} diff --git a/substrate/frame/revive/fixtures/contracts/chain_extension.rs b/substrate/frame/revive/fixtures/contracts/chain_extension.rs new file mode 100644 index 000000000000..474df00d6912 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/chain_extension.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Call chain extension by passing through input and output of this contract. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(input, 8, func_id: u32,); + + // the chain extension passes through the input and returns it as output + let mut output_buffer = [0u8; 32]; + let output = &mut &mut output_buffer[0..input.len()]; + + let ret_id = api::call_chain_extension(func_id, input, Some(output)); + assert_eq!(ret_id, func_id); + + api::return_value(uapi::ReturnFlags::empty(), output); +} diff --git a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs new file mode 100644 index 000000000000..c7596e44dab3 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs @@ -0,0 +1,65 @@ +// 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. + +//! Call chain extension two times with the specified func_ids +//! It then calls itself once +#![no_std] +#![no_main] + +use common::{input, output}; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + input, + func_id1: u32, + func_id2: u32, + stop_recurse: u8, + ); + + api::call_chain_extension(func_id1, input, None); + api::call_chain_extension(func_id2, input, None); + + if stop_recurse == 0 { + // Setup next call + input[0..4].copy_from_slice(&((3 << 16) | 2u32).to_le_bytes()); + input[4..8].copy_from_slice(&((3 << 16) | 3u32).to_le_bytes()); + input[8] = 1u8; + + // Read the contract address. + output!(addr, [0u8; 32], api::address,); + + // call self + api::call( + uapi::CallFlags::ALLOW_REENTRY, + addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + input, + None, + ) + .unwrap(); + } +} diff --git a/substrate/frame/revive/fixtures/contracts/common/Cargo.toml b/substrate/frame/revive/fixtures/contracts/common/Cargo.toml new file mode 100644 index 000000000000..7dadb7b821a2 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/common/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pallet-revive-fixtures-common" +publish = false +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +description = "Common utilities for pallet-revive-fixtures." + +[dependencies] +uapi = { package = 'pallet-revive-uapi', path = "../../../uapi", default-features = false } diff --git a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs new file mode 100644 index 000000000000..6631af8292fe --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs @@ -0,0 +1,157 @@ +// 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. +#![no_std] + +pub use uapi::{HostFn, HostFnImpl as api}; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + // Safety: The unimp instruction is guaranteed to trap + unsafe { + core::arch::asm!("unimp"); + core::hint::unreachable_unchecked(); + } +} + +/// Utility macro to read input passed to a contract. +/// +/// Example: +/// +/// ``` +/// input$!( +/// var1: u32, // [0, 4) var1 decoded as u32 +/// var2: [u8; 32], // [4, 36) var2 decoded as a [u8] slice +/// var3: u8, // [36, 37) var3 decoded as a u8 +/// ); +/// +/// // Input and size can be specified as well: +/// input$!( +/// input, // input buffer (optional) +/// 512, // input size (optional) +/// var4: u32, // [0, 4) var4 decoded as u32 +/// var5: [u8], // [4, ..) var5 decoded as a [u8] slice +/// ); +/// ``` +#[macro_export] +macro_rules! input { + (@inner $input:expr, $cursor:expr,) => {}; + (@size $size:expr, ) => { $size }; + + // Match a u8 variable. + // e.g input!(var1: u8, ); + (@inner $input:expr, $cursor:expr, $var:ident: u8, $($rest:tt)*) => { + let $var = $input[$cursor]; + input!(@inner $input, $cursor + 1, $($rest)*); + }; + + // Size of u8 variable. + (@size $size:expr, $var:ident: u8, $($rest:tt)*) => { + input!(@size $size + 1, $($rest)*) + }; + + // Match a u64 variable. + // e.g input!(var1: u64, ); + (@inner $input:expr, $cursor:expr, $var:ident: u64, $($rest:tt)*) => { + let $var = u64::from_le_bytes($input[$cursor..$cursor + 8].try_into().unwrap()); + input!(@inner $input, $cursor + 8, $($rest)*); + }; + + // Size of u64 variable. + (@size $size:expr, $var:ident: u64, $($rest:tt)*) => { + input!(@size $size + 8, $($rest)*) + }; + + // Match a u32 variable. + // e.g input!(var1: u32, ); + (@inner $input:expr, $cursor:expr, $var:ident: u32, $($rest:tt)*) => { + let $var = u32::from_le_bytes($input[$cursor..$cursor + 4].try_into().unwrap()); + input!(@inner $input, $cursor + 4, $($rest)*); + }; + + // Size of u32 variable. + (@size $size:expr, $var:ident: u32, $($rest:tt)*) => { + input!(@size $size + 4, $($rest)*) + }; + + // Match a u8 slice with the remaining bytes. + // e.g input!(512, var1: [u8; 32], var2: [u8], ); + (@inner $input:expr, $cursor:expr, $var:ident: [u8],) => { + let $var = &$input[$cursor..]; + }; + + // Match a u8 slice of the given size. + // e.g input!(var1: [u8; 32], ); + (@inner $input:expr, $cursor:expr, $var:ident: [u8; $n:expr], $($rest:tt)*) => { + let $var = &$input[$cursor..$cursor+$n]; + input!(@inner $input, $cursor + $n, $($rest)*); + }; + + // Size of a u8 slice. + (@size $size:expr, $var:ident: [u8; $n:expr], $($rest:tt)*) => { + input!(@size $size + $n, $($rest)*) + }; + + // Entry point, with the buffer and it's size specified first. + // e.g input!(buffer, 512, var1: u32, var2: [u8], ); + ($buffer:ident, $size:expr, $($rest:tt)*) => { + let mut $buffer = [0u8; $size]; + let $buffer = &mut &mut $buffer[..]; + $crate::api::input($buffer); + input!(@inner $buffer, 0, $($rest)*); + }; + + // Entry point, with the name of the buffer specified and size of the input buffer computed. + // e.g input!(buffer, var1: u32, var2: u64, ); + ($buffer: ident, $($rest:tt)*) => { + input!($buffer, input!(@size 0, $($rest)*), $($rest)*); + }; + + // Entry point, with the size of the input buffer computed. + // e.g input!(var1: u32, var2: u64, ); + ($($rest:tt)*) => { + input!(buffer, $($rest)*); + }; +} + +/// Utility macro to invoke a host function that expect a `output: &mut &mut [u8]` as last argument. +/// +/// Example: +/// ``` +/// // call `api::caller` and store the output in `caller` +/// output!(caller, [0u8; 32], api::caller,); +/// +/// // call `api::get_storage` and store the output in `address` +/// output!(address, [0u8; 32], api::get_storage, &[1u8; 32]); +/// ``` +#[macro_export] +macro_rules! output { + ($output: ident, $buffer: expr, $host_fn:path, $($arg:expr),*) => { + let mut $output = $buffer; + let $output = &mut &mut $output[..]; + $host_fn($($arg,)* $output); + }; +} + +/// Similar to `output!` but unwraps the result. +#[macro_export] +macro_rules! unwrap_output { + ($output: ident, $buffer: expr, $host_fn:path, $($arg:expr),*) => { + let mut $output = $buffer; + let $output = &mut &mut $output[..]; + $host_fn($($arg,)* $output).unwrap(); + }; +} diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs new file mode 100644 index 000000000000..d0d3651dfe4d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs @@ -0,0 +1,58 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This calls another contract as passed as its account id. It also creates some storage. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + buffer, + input: [u8; 4], + callee: [u8; 32], + deposit_limit: [u8; 8], + ); + + // create 4 byte of storage before calling + api::set_storage(StorageFlags::empty(), buffer, &[1u8; 4]); + + // Call the callee + api::call( + uapi::CallFlags::empty(), + callee, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + Some(deposit_limit), + &0u64.to_le_bytes(), // Value transferred to the contract. + input, + None, + ) + .unwrap(); + + // create 8 byte of storage after calling + // item of 12 bytes because we override 4 bytes + api::set_storage(StorageFlags::empty(), buffer, &[1u8; 12]); +} diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs new file mode 100644 index 000000000000..918a4abe6b21 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs @@ -0,0 +1,58 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This instantiates another contract and passes some input to its constructor. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + input: [u8; 4], + code_hash: [u8; 32], + deposit_limit: [u8; 8], + ); + + let value = 10_000u64.to_le_bytes(); + let salt = [0u8; 0]; + let mut address = [0u8; 32]; + let address = &mut &mut address[..]; + + api::instantiate( + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + Some(deposit_limit), + &value, + input, + Some(address), + None, + &salt, + ) + .unwrap(); + + // Return the deployed contract address. + api::return_value(uapi::ReturnFlags::empty(), address); +} diff --git a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs new file mode 100644 index 000000000000..a2a0e85bcf64 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs @@ -0,0 +1,60 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This calls another contract as passed as its account id. It also creates some transient storage. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 512] = [0u8; 512]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + buffer, + len: u32, + input: [u8; 4], + callee: [u8; 32], + ); + + let rounds = len as usize / BUFFER.len(); + let rest = len as usize / BUFFER.len(); + for i in 0..rounds { + api::set_storage(StorageFlags::TRANSIENT, &i.to_le_bytes(), &BUFFER); + } + api::set_storage(StorageFlags::TRANSIENT, &u32::MAX.to_le_bytes(), &BUFFER[..rest]); + + // Call the callee + api::call( + uapi::CallFlags::empty(), + callee, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, + &0u64.to_le_bytes(), // Value transferred to the contract. + input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/crypto_hashes.rs b/substrate/frame/revive/fixtures/contracts/crypto_hashes.rs new file mode 100644 index 000000000000..35cc03f1e723 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/crypto_hashes.rs @@ -0,0 +1,84 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +/// Called by the tests. +/// +/// The `call` function expects data in a certain format in the input buffer. +/// +/// 1. The first byte encodes an identifier for the crypto hash function under test. (*) +/// 2. The rest encodes the input data that is directly fed into the crypto hash function chosen in +/// 1. +/// +/// The `deploy` function then computes the chosen crypto hash function +/// given the input and puts the result into the output buffer. +/// After contract execution the test driver then asserts that the returned +/// values are equal to the expected bytes for the input and chosen hash +/// function. +/// +/// (*) The possible value for the crypto hash identifiers can be found below: +/// +/// | value | Algorithm | Bit Width | +/// |-------|-----------|-----------| +/// | 0 | SHA2 | 256 | +/// | 1 | KECCAK | 256 | +/// | 2 | BLAKE2 | 256 | +/// | 3 | BLAKE2 | 128 | +/// --------------------------------- + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 256, + chosen_hash_fn: u8, + input: [u8], + ); + + match chosen_hash_fn { + 1 => { + let mut output = [0u8; 32]; + api::hash_sha2_256(input, &mut output); + api::return_value(uapi::ReturnFlags::empty(), &output); + }, + 2 => { + let mut output = [0u8; 32]; + api::hash_keccak_256(input, &mut output); + api::return_value(uapi::ReturnFlags::empty(), &output); + }, + 3 => { + let mut output = [0u8; 32]; + api::hash_blake2_256(input, &mut output); + api::return_value(uapi::ReturnFlags::empty(), &output); + }, + 4 => { + let mut output = [0u8; 16]; + api::hash_blake2_128(input, &mut output); + api::return_value(uapi::ReturnFlags::empty(), &output); + }, + _ => panic!("unknown crypto hash function identifier"), + } +} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs b/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs new file mode 100644 index 000000000000..6c850a9ec663 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs @@ -0,0 +1,33 @@ +// 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. + +//! Emit a debug message with an invalid utf-8 code. +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::debug_message(b"\xFC").unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs b/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs new file mode 100644 index 000000000000..0ce2b6b5628d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs @@ -0,0 +1,33 @@ +// 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. + +//! Emit a "Hello World!" debug message but assume that logging is disabled. +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + assert_eq!(api::debug_message(b"Hello World!"), Err(ReturnErrorCode::LoggingDisabled)); +} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs b/substrate/frame/revive/fixtures/contracts/debug_message_works.rs new file mode 100644 index 000000000000..3a2509509d8f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/debug_message_works.rs @@ -0,0 +1,33 @@ +// 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. + +//! Emit a "Hello World!" debug message. +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::debug_message(b"Hello World!").unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call.rs b/substrate/frame/revive/fixtures/contracts/delegate_call.rs new file mode 100644 index 000000000000..d03ddab1bc58 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/delegate_call.rs @@ -0,0 +1,49 @@ +// 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. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(code_hash: [u8; 32],); + + let mut key = [0u8; 32]; + key[0] = 1u8; + + let mut value = [0u8; 32]; + let value = &mut &mut value[..]; + value[0] = 2u8; + + api::set_storage(StorageFlags::empty(), &key, value); + api::get_storage(StorageFlags::empty(), &key, value).unwrap(); + assert!(value[0] == 2u8); + + let input = [0u8; 0]; + api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap(); + + api::get_storage(StorageFlags::empty(), &key, value).unwrap(); + assert!(value[0] == 1u8); +} diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs new file mode 100644 index 000000000000..055760729bd2 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_lib.rs @@ -0,0 +1,49 @@ +// 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. + +#![no_std] +#![no_main] + +use common::output; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut key = [0u8; 32]; + key[0] = 1u8; + + // Place a value in storage. + let mut value = [0u8; 32]; + let value = &mut &mut value[..]; + value[0] = 1u8; + api::set_storage(StorageFlags::empty(), &key, value); + + // Assert that `value_transferred` is equal to the value + // passed to the `caller` contract: 1337. + output!(value_transferred, [0u8; 8], api::value_transferred,); + let value_transferred = u64::from_le_bytes(value_transferred[..].try_into().unwrap()); + assert_eq!(value_transferred, 1337); + + // Assert that ALICE is the caller of the contract. + output!(caller, [0u8; 32], api::caller,); + assert_eq!(&caller[..], &[1u8; 32]); +} diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs new file mode 100644 index 000000000000..cf3351c52fdc --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs @@ -0,0 +1,36 @@ +// 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. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(code_hash: [u8; 32],); + + // Delegate call into passed code hash. + let input = [0u8; 0]; + api::delegate_call(uapi::CallFlags::empty(), code_hash, &input, None).unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs new file mode 100644 index 000000000000..8b74493a1e38 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs @@ -0,0 +1,86 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +const ADDRESS_KEY: [u8; 32] = [0u8; 32]; +const VALUE: [u8; 8] = [0, 0, 1u8, 0, 0, 0, 0, 0]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + input!(code_hash: [u8; 32],); + + let input = [0u8; 0]; + let mut address = [0u8; 32]; + let address = &mut &mut address[..]; + let salt = [71u8, 17u8]; + + api::instantiate( + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &VALUE, + &input, + Some(address), + None, + &salt, + ) + .unwrap(); + + // Return the deployed contract address. + api::set_storage(StorageFlags::empty(), &ADDRESS_KEY, address); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut callee_addr = [0u8; 32]; + let callee_addr = &mut &mut callee_addr[..]; + api::get_storage(StorageFlags::empty(), &ADDRESS_KEY, callee_addr).unwrap(); + + // Calling the destination contract with non-empty input data should fail. + let res = api::call( + uapi::CallFlags::empty(), + callee_addr, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &VALUE, + &[0u8; 1], + None, + ); + assert!(matches!(res, Err(uapi::ReturnErrorCode::CalleeTrapped))); + + // Call the destination contract regularly, forcing it to self-destruct. + api::call( + uapi::CallFlags::empty(), + callee_addr, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, // How much proof_size weight to devote for the execution. 0 = all. + None, // No deposit limit. + &VALUE, + &[0u8; 0], + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/drain.rs b/substrate/frame/revive/fixtures/contracts/drain.rs new file mode 100644 index 000000000000..f5c8681c9382 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/drain.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::output; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + output!(balance, [0u8; 8], api::balance,); + let balance = u64::from_le_bytes(balance[..].try_into().unwrap()); + + output!(minimum_balance, [0u8; 8], api::minimum_balance,); + let minimum_balance = u64::from_le_bytes(minimum_balance[..].try_into().unwrap()); + + // Make the transferred value exceed the balance by adding the minimum balance. + let balance = balance + minimum_balance; + + // Try to self-destruct by sending more balance to the 0 address. + // The call will fail because a contract transfer has a keep alive requirement. + let res = api::transfer(&[0u8; 32], &balance.to_le_bytes()); + assert!(matches!(res, Err(uapi::ReturnErrorCode::TransferFailed))); +} diff --git a/substrate/frame/revive/fixtures/contracts/dummy.rs b/substrate/frame/revive/fixtures/contracts/dummy.rs new file mode 100644 index 000000000000..c7294e99139e --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/dummy.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +extern crate common; + +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +// Export that is never called. We can put code here that should be in the binary +// but is never supposed to be run. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call_never() { + // Make sure the 0xDEADBEEF pattern appears in the binary by + // making it opaque to the optimizer. The benchmarking code will + // just find and replace this pattern to make the code unique when + // necessary. + api::return_value(ReturnFlags::empty(), &[0xDE, 0xAD, 0xBE, 0xEF]); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs b/substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs new file mode 100644 index 000000000000..0f28ca2c8198 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/ecdsa_recover.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + signature: [u8; 65], + hash: [u8; 32], + ); + + let mut output = [0u8; 33]; + api::ecdsa_recover( + &signature[..].try_into().unwrap(), + &hash[..].try_into().unwrap(), + &mut output, + ) + .unwrap(); + api::return_value(uapi::ReturnFlags::empty(), &output); +} diff --git a/substrate/frame/revive/fixtures/contracts/event_and_return_on_deploy.rs b/substrate/frame/revive/fixtures/contracts/event_and_return_on_deploy.rs new file mode 100644 index 000000000000..9186835d2911 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/event_and_return_on_deploy.rs @@ -0,0 +1,36 @@ +// 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. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + let buffer = [1u8, 2, 3, 4]; + api::deposit_event(&[0u8; 0], &buffer); + api::return_value(uapi::ReturnFlags::empty(), &buffer); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + unreachable!() +} diff --git a/substrate/frame/revive/fixtures/contracts/event_size.rs b/substrate/frame/revive/fixtures/contracts/event_size.rs new file mode 100644 index 000000000000..2b56de4bd3fd --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/event_size.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +static BUFFER: [u8; 16 * 1024 + 1] = [0u8; 16 * 1024 + 1]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(len: u32,); + + let data = &BUFFER[..len as usize]; + + api::deposit_event(&[0u8; 0], data); +} diff --git a/substrate/frame/revive/fixtures/contracts/float_instruction.rs b/substrate/frame/revive/fixtures/contracts/float_instruction.rs new file mode 100644 index 000000000000..b1eaaf8543c6 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/float_instruction.rs @@ -0,0 +1,34 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} + +#[no_mangle] +pub extern "C" fn add(a: f32, b: f32) -> f32 { + a + b +} diff --git a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs new file mode 100644 index 000000000000..90884f1a2a68 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(buffer, 36, code_hash: [u8; 32],); + let input = &buffer[32..]; + + let err_code = match api::instantiate( + code_hash, + 0u64, // How much ref_time weight to devote for the execution. 0 = all. + 0u64, /* How much proof_size weight to devote for the execution. 0 = + * all. */ + None, // No deposit limit. + &10_000u64.to_le_bytes(), // Value to transfer. + input, + None, + None, + &[0u8; 0], // Empty salt. + ) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + // Exit with success and take transfer return code to the output buffer. + api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/instr_benchmark.rs b/substrate/frame/revive/fixtures/contracts/instr_benchmark.rs new file mode 100644 index 000000000000..c5fb382c3276 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/instr_benchmark.rs @@ -0,0 +1,41 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +extern crate common; + +use common::input; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(rounds: u32, start: u32, div: u32, mult: u32, add: u32, ); + + let mut acc = start; + + for _ in 0..rounds { + acc = acc / div * mult + add; + } + + api::return_value(ReturnFlags::empty(), start.to_le_bytes().as_ref()); +} diff --git a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs new file mode 100644 index 000000000000..3ed886b49489 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.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. + +//! This contract tests the behavior of locking / unlocking delegate_dependencies when delegate +//! calling into a contract. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +const ALICE: [u8; 32] = [1u8; 32]; + +/// Load input data and perform the action specified by the input. +/// If `delegate_call` is true, then delegate call into the contract. +fn load_input(delegate_call: bool) { + input!( + action: u32, + code_hash: [u8; 32], + ); + + match action { + // 1 = Lock delegate dependency + 1 => { + api::lock_delegate_dependency(code_hash); + }, + // 2 = Unlock delegate dependency + 2 => { + api::unlock_delegate_dependency(code_hash); + }, + // 3 = Terminate + 3 => { + api::terminate(&ALICE); + }, + // Everything else is a noop + _ => {}, + } + + if delegate_call { + api::delegate_call(uapi::CallFlags::empty(), code_hash, &[], None).unwrap(); + } +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + load_input(false); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + load_input(true); +} diff --git a/substrate/frame/revive/fixtures/contracts/multi_store.rs b/substrate/frame/revive/fixtures/contracts/multi_store.rs new file mode 100644 index 000000000000..079a4548e78d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/multi_store.rs @@ -0,0 +1,43 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Does two stores to two separate storage items +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 512] = [0u8; 512]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + size1: u32, + size2: u32, + ); + + // Place a values in storage sizes are specified in the input buffer. + // We don't care about the contents of the storage item. + api::set_storage(StorageFlags::empty(), &[1u8; 32], &BUFFER[0..size1 as _]); + api::set_storage(StorageFlags::empty(), &[2u8; 32], &BUFFER[0..size2 as _]); +} diff --git a/substrate/frame/revive/fixtures/contracts/new_set_code_hash_contract.rs b/substrate/frame/revive/fixtures/contracts/new_set_code_hash_contract.rs new file mode 100644 index 000000000000..2a59b6e33d89 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/new_set_code_hash_contract.rs @@ -0,0 +1,32 @@ +// 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. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::return_value(uapi::ReturnFlags::empty(), &2u32.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/noop.rs b/substrate/frame/revive/fixtures/contracts/noop.rs new file mode 100644 index 000000000000..48d8a6896d62 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/noop.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +extern crate common; + +use common::input; +use uapi::HostFn; + +#[polkavm_derive::polkavm_import] +extern "C" { + pub fn noop(); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(rounds: u32, ); + + for _ in 0..rounds { + unsafe { + noop(); + } + } +} diff --git a/substrate/frame/revive/fixtures/contracts/ok_trap_revert.rs b/substrate/frame/revive/fixtures/contracts/ok_trap_revert.rs new file mode 100644 index 000000000000..55115f8642f3 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/ok_trap_revert.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + ok_trap_revert(); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + ok_trap_revert(); +} + +#[no_mangle] +fn ok_trap_revert() { + input!(buffer, 4,); + match buffer.first().unwrap_or(&0) { + 1 => api::return_value(uapi::ReturnFlags::REVERT, &[0u8; 0]), + 2 => panic!(), + _ => {}, + }; +} diff --git a/substrate/frame/revive/fixtures/contracts/read_only_call.rs b/substrate/frame/revive/fixtures/contracts/read_only_call.rs new file mode 100644 index 000000000000..a62bb205039f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/read_only_call.rs @@ -0,0 +1,50 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This fixture tests if read-only call works as expected. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 256, + callee_addr: [u8; 32], + callee_input: [u8], + ); + + // Call the callee + api::call( + uapi::CallFlags::READ_ONLY, + callee_addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + callee_input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/recurse.rs b/substrate/frame/revive/fixtures/contracts/recurse.rs new file mode 100644 index 000000000000..f4dfd6c965d0 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/recurse.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls itself as many times as passed as argument. + +#![no_std] +#![no_main] + +use common::{input, output}; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(calls_left: u32, ); + + // own address + output!(addr, [0u8; 32], api::address,); + + if calls_left == 0 { + return + } + + api::call( + uapi::CallFlags::ALLOW_REENTRY, + addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much deposit_limit to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + &(calls_left - 1).to_le_bytes(), + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/return_with_data.rs b/substrate/frame/revive/fixtures/contracts/return_with_data.rs new file mode 100644 index 000000000000..47a1cc911192 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/return_with_data.rs @@ -0,0 +1,47 @@ +// 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. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + call(); +} + +/// Reads the first byte as the exit status and copy all but the first 4 bytes of the input as +/// output data. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + input, 128, + exit_status: [u8; 4], + output: [u8], + ); + + // Burn some PoV, clear_storage consumes some PoV as in order to clear the storage we need to we + // need to read its size first. + api::clear_storage(StorageFlags::empty(), b""); + + let exit_status = uapi::ReturnFlags::from_bits(exit_status[0] as u32).unwrap(); + api::return_value(exit_status, output); +} diff --git a/substrate/frame/revive/fixtures/contracts/run_out_of_gas.rs b/substrate/frame/revive/fixtures/contracts/run_out_of_gas.rs new file mode 100644 index 000000000000..11eaaa7c8624 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/run_out_of_gas.rs @@ -0,0 +1,32 @@ +// 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. + +#![no_std] +#![no_main] + +extern crate common; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + #[allow(clippy::empty_loop)] + loop {} +} diff --git a/substrate/frame/revive/fixtures/contracts/self_destruct.rs b/substrate/frame/revive/fixtures/contracts/self_destruct.rs new file mode 100644 index 000000000000..10f33226acf2 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/self_destruct.rs @@ -0,0 +1,55 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::{input, output}; +use uapi::{HostFn, HostFnImpl as api}; + +const DJANGO: [u8; 32] = [4u8; 32]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + // If the input data is not empty, then recursively call self with empty input data. + // This should trap instead of self-destructing since a contract cannot be removed, while it's + // in the execution stack. If the recursive call traps, then trap here as well. + input!(input, 4,); + + if !input.is_empty() { + output!(addr, [0u8; 32], api::address,); + api::call( + uapi::CallFlags::ALLOW_REENTRY, + addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value to transfer. + &[0u8; 0], + None, + ) + .unwrap(); + } else { + // Try to terminate and give balance to django. + api::terminate(&DJANGO); + } +} diff --git a/substrate/frame/revive/fixtures/contracts/self_destructing_constructor.rs b/substrate/frame/revive/fixtures/contracts/self_destructing_constructor.rs new file mode 100644 index 000000000000..28bcef978231 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/self_destructing_constructor.rs @@ -0,0 +1,32 @@ +// 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. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + api::terminate(&[0u8; 32]); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/fixtures/contracts/set_code_hash.rs b/substrate/frame/revive/fixtures/contracts/set_code_hash.rs new file mode 100644 index 000000000000..e3cf4becfb97 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/set_code_hash.rs @@ -0,0 +1,37 @@ +// 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. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(addr: [u8; 32],); + api::set_code_hash(addr).unwrap(); + + // we return 1 after setting new code_hash + // next `call` will NOT return this value, because contract code has been changed + api::return_value(uapi::ReturnFlags::empty(), &1u32.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/set_empty_storage.rs b/substrate/frame/revive/fixtures/contracts/set_empty_storage.rs new file mode 100644 index 000000000000..f8bbfe3faa5b --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/set_empty_storage.rs @@ -0,0 +1,32 @@ +// 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. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::set_storage(StorageFlags::empty(), &[0u8; 32], &[0u8; 4]); +} diff --git a/substrate/frame/revive/fixtures/contracts/set_transient_storage.rs b/substrate/frame/revive/fixtures/contracts/set_transient_storage.rs new file mode 100644 index 000000000000..a8a1fbd65148 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/set_transient_storage.rs @@ -0,0 +1,41 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 512] = [0u8; 512]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(len: u32, ); + + let rounds = len as usize / BUFFER.len(); + let rest = len as usize / BUFFER.len(); + for i in 0..rounds { + api::set_storage(StorageFlags::TRANSIENT, &i.to_le_bytes(), &BUFFER); + } + api::set_storage(StorageFlags::TRANSIENT, &u32::MAX.to_le_bytes(), &BUFFER[..rest]); +} diff --git a/substrate/frame/revive/fixtures/contracts/sr25519_verify.rs b/substrate/frame/revive/fixtures/contracts/sr25519_verify.rs new file mode 100644 index 000000000000..8920ce0d4f6c --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/sr25519_verify.rs @@ -0,0 +1,48 @@ +// 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. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + signature: [u8; 64], + pub_key: [u8; 32], + msg: [u8; 11], + ); + + let exit_status = match api::sr25519_verify( + &signature.try_into().unwrap(), + msg, + &pub_key.try_into().unwrap(), + ) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + // Exit with success and take transfer return code to the output buffer. + api::return_value(uapi::ReturnFlags::empty(), &exit_status.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/storage.rs b/substrate/frame/revive/fixtures/contracts/storage.rs new file mode 100644 index 000000000000..dc21e322466c --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/storage.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This contract tests the storage APIs. It sets and clears storage values using the different +//! versions of the storage APIs. +#![no_std] +#![no_main] + +use common::unwrap_output; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + const KEY: [u8; 32] = [1u8; 32]; + const VALUE_1: [u8; 4] = [1u8; 4]; + const VALUE_2: [u8; 4] = [2u8; 4]; + const VALUE_3: [u8; 4] = [3u8; 4]; + + api::set_storage(StorageFlags::empty(), &KEY, &VALUE_1); + assert_eq!(api::contains_storage(StorageFlags::empty(), &KEY), Some(VALUE_1.len() as _)); + unwrap_output!(val, [0u8; 4], api::get_storage, StorageFlags::empty(), &KEY); + assert_eq!(**val, VALUE_1); + + let existing = api::set_storage(StorageFlags::empty(), &KEY, &VALUE_2); + assert_eq!(existing, Some(VALUE_1.len() as _)); + unwrap_output!(val, [0u8; 4], api::get_storage, StorageFlags::empty(), &KEY); + assert_eq!(**val, VALUE_2); + + api::clear_storage(StorageFlags::empty(), &KEY); + assert_eq!(api::contains_storage(StorageFlags::empty(), &KEY), None); + + let existing = api::set_storage(StorageFlags::empty(), &KEY, &VALUE_3); + assert_eq!(existing, None); + assert_eq!(api::contains_storage(StorageFlags::empty(), &KEY), Some(VALUE_1.len() as _)); + unwrap_output!(val, [0u8; 32], api::get_storage, StorageFlags::empty(), &KEY); + assert_eq!(**val, VALUE_3); + + api::clear_storage(StorageFlags::empty(), &KEY); + assert_eq!(api::contains_storage(StorageFlags::empty(), &KEY), None); + let existing = api::set_storage(StorageFlags::empty(), &KEY, &VALUE_3); + assert_eq!(existing, None); + unwrap_output!(val, [0u8; 32], api::take_storage, StorageFlags::empty(), &KEY); + assert_eq!(**val, VALUE_3); +} diff --git a/substrate/frame/revive/fixtures/contracts/storage_size.rs b/substrate/frame/revive/fixtures/contracts/storage_size.rs new file mode 100644 index 000000000000..617e8d2ea79f --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/storage_size.rs @@ -0,0 +1,50 @@ +// 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. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static mut BUFFER: [u8; 16 * 1024 + 1] = [0u8; 16 * 1024 + 1]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(len: u32, ); + + let data = unsafe { + &BUFFER[..len as usize] + }; + + // Place a garbage value in storage, the size of which is specified by the call input. + let mut key = [0u8; 32]; + key[0] = 1; + + api::set_storage(StorageFlags::empty(), &key, data); + + let data = unsafe { + &mut &mut BUFFER[..] + }; + api::get_storage(StorageFlags::empty(), &key, data).unwrap(); + assert_eq!(data.len(), len as usize); +} diff --git a/substrate/frame/revive/fixtures/contracts/store_call.rs b/substrate/frame/revive/fixtures/contracts/store_call.rs new file mode 100644 index 000000000000..b08d445191e5 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/store_call.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 512] = [0u8; 512]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(len: u32, ); + + let data = &BUFFER[..len as usize]; + + // Place a garbage value in storage, the size of which is specified by the call input. + let mut key = [0u8; 32]; + key[0] = 1; + + api::set_storage(StorageFlags::empty(), &key, data); +} diff --git a/substrate/frame/revive/fixtures/contracts/store_deploy.rs b/substrate/frame/revive/fixtures/contracts/store_deploy.rs new file mode 100644 index 000000000000..e08c79d78f3b --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/store_deploy.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +static BUFFER: [u8; 16 * 1024 + 1] = [0u8; 16 * 1024 + 1]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + input!(len: u32, ); + + let data = &BUFFER[..len as usize]; + + // place a garbage value in storage, the size of which is specified by the call input. + let mut key = [0u8; 32]; + key[0] = 1; + + api::set_storage(StorageFlags::empty(), &key, data); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs new file mode 100644 index 000000000000..d3f6a1dd3a08 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let ret_code = match api::transfer(&[0u8; 32], &100u64.to_le_bytes()) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + // Exit with success and take transfer return code to the output buffer. + api::return_value(uapi::ReturnFlags::empty(), &ret_code.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/transient_storage.rs b/substrate/frame/revive/fixtures/contracts/transient_storage.rs new file mode 100644 index 000000000000..aa0a69435a69 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/transient_storage.rs @@ -0,0 +1,55 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This contract tests the transient storage APIs. +#![no_std] +#![no_main] + +use common::unwrap_output; +use uapi::{HostFn, HostFnImpl as api, StorageFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + const KEY: [u8; 32] = [1u8; 32]; + const VALUE_1: [u8; 4] = [1u8; 4]; + const VALUE_2: [u8; 5] = [2u8; 5]; + const VALUE_3: [u8; 6] = [3u8; 6]; + + let existing = api::set_storage(StorageFlags::TRANSIENT, &KEY, &VALUE_1); + assert_eq!(existing, None); + assert_eq!(api::contains_storage(StorageFlags::TRANSIENT, &KEY), Some(VALUE_1.len() as _)); + unwrap_output!(val, [0u8; 32], api::get_storage, StorageFlags::TRANSIENT, &KEY); + assert_eq!(**val, VALUE_1); + + let existing = api::set_storage(StorageFlags::TRANSIENT, &KEY, &VALUE_2); + assert_eq!(existing, Some(VALUE_1.len() as _)); + unwrap_output!(val, [0u8; 32], api::get_storage, StorageFlags::TRANSIENT, &KEY); + assert_eq!(**val, VALUE_2); + + assert_eq!(api::clear_storage(StorageFlags::TRANSIENT, &KEY), Some(VALUE_2.len() as _)); + assert_eq!(api::contains_storage(StorageFlags::TRANSIENT, &KEY), None); + + let existing = api::set_storage(StorageFlags::TRANSIENT, &KEY, &VALUE_3); + assert_eq!(existing, None); + unwrap_output!(val, [0u8; 128], api::take_storage, StorageFlags::TRANSIENT, &KEY); + assert_eq!(**val, VALUE_3); +} diff --git a/substrate/frame/revive/fixtures/contracts/xcm_execute.rs b/substrate/frame/revive/fixtures/contracts/xcm_execute.rs new file mode 100644 index 000000000000..1d570ffead71 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/xcm_execute.rs @@ -0,0 +1,40 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(512, msg: [u8],); + + #[allow(deprecated)] + let err_code = match api::xcm_execute(msg) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes()); +} diff --git a/substrate/frame/revive/fixtures/contracts/xcm_send.rs b/substrate/frame/revive/fixtures/contracts/xcm_send.rs new file mode 100644 index 000000000000..6d4629e748a7 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/xcm_send.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + 512, + dest: [u8; 3], + msg: [u8], + ); + + let mut message_id = [0u8; 32]; + + #[allow(deprecated)] + api::xcm_send(dest, msg, &mut message_id).unwrap(); + api::return_value(uapi::ReturnFlags::empty(), &message_id); +} diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs new file mode 100644 index 000000000000..1b6103f57aee --- /dev/null +++ b/substrate/frame/revive/fixtures/src/lib.rs @@ -0,0 +1,79 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +/// Load a given wasm module and returns a wasm binary contents along with it's hash. +#[cfg(feature = "std")] +pub fn compile_module( + fixture_name: &str, +) -> anyhow::Result<(Vec, ::Output)> +where + T: frame_system::Config, +{ + use sp_runtime::traits::Hash; + let out_dir: std::path::PathBuf = env!("OUT_DIR").into(); + let fixture_path = out_dir.join(format!("{fixture_name}.polkavm")); + let binary = std::fs::read(fixture_path)?; + let code_hash = T::Hashing::hash(&binary); + Ok((binary, code_hash)) +} + +/// Fixtures used in runtime benchmarks. +/// +/// We explicitly include those fixtures into the binary to make them +/// available in no-std environments (runtime benchmarks). +pub mod bench { + use alloc::vec::Vec; + + #[cfg(feature = "riscv")] + macro_rules! fixture { + ($name: literal) => { + include_bytes!(concat!(env!("OUT_DIR"), "/", $name, ".polkavm")) + }; + } + #[cfg(not(feature = "riscv"))] + macro_rules! fixture { + ($name: literal) => { + &[] + }; + } + pub const DUMMY: &[u8] = fixture!("dummy"); + pub const NOOP: &[u8] = fixture!("noop"); + pub const INSTR: &[u8] = fixture!("instr_benchmark"); + + pub fn dummy_unique(replace_with: u32) -> Vec { + let mut dummy = DUMMY.to_vec(); + let idx = dummy + .windows(4) + .position(|w| w == &[0xDE, 0xAD, 0xBE, 0xEF]) + .expect("Benchmark fixture contains this pattern; qed"); + dummy[idx..idx + 4].copy_from_slice(&replace_with.to_le_bytes()); + dummy + } +} + +#[cfg(test)] +mod test { + #[test] + fn out_dir_should_have_compiled_mocks() { + let out_dir: std::path::PathBuf = env!("OUT_DIR").into(); + assert!(out_dir.join("dummy.polkavm").exists()); + } +} diff --git a/substrate/frame/revive/mock-network/Cargo.toml b/substrate/frame/revive/mock-network/Cargo.toml new file mode 100644 index 000000000000..0d597bbdc228 --- /dev/null +++ b/substrate/frame/revive/mock-network/Cargo.toml @@ -0,0 +1,88 @@ +[package] +name = "pallet-revive-mock-network" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "A mock network for testing pallet-revive." + +[lints] +workspace = true + +[dependencies] +codec = { features = ["derive", "max-encoded-len"], workspace = true } + +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +pallet-revive = { workspace = true, default-features = true } +pallet-revive-uapi = { workspace = true } +pallet-revive-proc-macro = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } +pallet-proxy = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } +pallet-xcm = { workspace = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } +sp-api = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true } +sp-tracing = { workspace = true, default-features = true } +xcm = { workspace = true } +xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true } +xcm-simulator = { workspace = true, default-features = true } + +[dev-dependencies] +assert_matches = { workspace = true } +pretty_assertions = { workspace = true } +pallet-revive-fixtures = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-proxy/std", + "pallet-revive-fixtures/std", + "pallet-revive/std", + "pallet-timestamp/std", + "pallet-utility/std", + "pallet-xcm/std", + "scale-info/std", + "sp-api/std", + "sp-core/std", + "sp-io/std", + "sp-keystore/std", + "sp-runtime/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", + "pallet-revive/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-utility/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", + "polkadot-runtime-parachains/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] diff --git a/substrate/frame/revive/mock-network/src/lib.rs b/substrate/frame/revive/mock-network/src/lib.rs new file mode 100644 index 000000000000..2e4f273a0103 --- /dev/null +++ b/substrate/frame/revive/mock-network/src/lib.rs @@ -0,0 +1,152 @@ +// 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 . + +pub mod mocks; +pub mod parachain; +pub mod primitives; +pub mod relay_chain; + +#[cfg(all(test, feature = "riscv"))] +mod tests; + +use crate::primitives::{AccountId, UNITS}; +pub use pallet_revive::test_utils::{ALICE, BOB}; +use sp_runtime::BuildStorage; +use xcm::latest::prelude::*; +use xcm_executor::traits::ConvertLocation; +pub use xcm_simulator::TestExt; +use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; + +// Accounts +pub const ADMIN: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); + +// Balances +pub const INITIAL_BALANCE: u128 = 1_000_000_000 * UNITS; + +decl_test_parachain! { + pub struct ParaA { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(1), + } +} + +decl_test_relay_chain! { + pub struct Relay { + Runtime = relay_chain::Runtime, + RuntimeCall = relay_chain::RuntimeCall, + RuntimeEvent = relay_chain::RuntimeEvent, + XcmConfig = relay_chain::XcmConfig, + MessageQueue = relay_chain::MessageQueue, + System = relay_chain::System, + new_ext = relay_ext(), + } +} + +decl_test_network! { + pub struct MockNet { + relay_chain = Relay, + parachains = vec![ + (1, ParaA), + ], + } +} + +pub fn relay_sovereign_account_id() -> AccountId { + let location: Location = (Parent,).into(); + parachain::SovereignAccountOf::convert_location(&location).unwrap() +} + +pub fn parachain_sovereign_account_id(para: u32) -> AccountId { + let location: Location = (Parachain(para),).into(); + relay_chain::SovereignAccountOf::convert_location(&location).unwrap() +} + +pub fn parachain_account_sovereign_account_id( + para: u32, + who: sp_runtime::AccountId32, +) -> AccountId { + let location: Location = ( + Parachain(para), + AccountId32 { network: Some(relay_chain::RelayNetwork::get()), id: who.into() }, + ) + .into(); + relay_chain::SovereignAccountOf::convert_location(&location).unwrap() +} + +pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { + use parachain::{MsgQueue, Runtime, System}; + + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (relay_sovereign_account_id(), INITIAL_BALANCE), + (BOB, INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + pallet_assets::GenesisConfig:: { + assets: vec![ + (0u128, ADMIN, false, 1u128), // Create derivative asset for relay's native token + ], + metadata: Default::default(), + accounts: vec![ + (0u128, ALICE, INITIAL_BALANCE), + (0u128, relay_sovereign_account_id(), INITIAL_BALANCE), + ], + next_asset_id: None, + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + sp_tracing::try_init_simple(); + System::set_block_number(1); + MsgQueue::set_para_id(para_id.into()); + }); + ext +} + +pub fn relay_ext() -> sp_io::TestExternalities { + use relay_chain::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, INITIAL_BALANCE), + (parachain_sovereign_account_id(1), INITIAL_BALANCE), + (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +pub type ParachainPalletXcm = pallet_xcm::Pallet; +pub type ParachainBalances = pallet_balances::Pallet; diff --git a/substrate/frame/revive/mock-network/src/mocks.rs b/substrate/frame/revive/mock-network/src/mocks.rs new file mode 100644 index 000000000000..bf3baec7a524 --- /dev/null +++ b/substrate/frame/revive/mock-network/src/mocks.rs @@ -0,0 +1,18 @@ +// 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 . + +pub mod msg_queue; +pub mod relay_message_queue; diff --git a/substrate/frame/revive/mock-network/src/mocks/msg_queue.rs b/substrate/frame/revive/mock-network/src/mocks/msg_queue.rs new file mode 100644 index 000000000000..6e922c16c298 --- /dev/null +++ b/substrate/frame/revive/mock-network/src/mocks/msg_queue.rs @@ -0,0 +1,186 @@ +// 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 . + +//! Parachain runtime mock. + +use codec::{Decode, Encode}; + +use frame_support::weights::Weight; +use polkadot_parachain_primitives::primitives::{ + DmpMessageHandler, Id as ParaId, XcmpMessageFormat, XcmpMessageHandler, +}; +use polkadot_primitives::BlockNumber as RelayBlockNumber; +use sp_runtime::traits::{Get, Hash}; + +use xcm::{latest::prelude::*, VersionedXcm}; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type XcmExecutor: ExecuteXcm; + } + + #[pallet::call] + impl Pallet {} + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; + + #[pallet::storage] + /// A queue of received DMP messages + pub(super) type ReceivedDmp = StorageValue<_, Vec>, ValueQuery>; + + impl Get for Pallet { + fn get() -> ParaId { + ParachainId::::get() + } + } + + pub type MessageId = [u8; 32]; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Some XCM was executed OK. + Success(Option), + /// Some XCM failed. + Fail(Option, XcmError), + /// Bad XCM version used. + BadVersion(Option), + /// Bad XCM format used. + BadFormat(Option), + + // DMP + /// Downward message is invalid XCM. + InvalidFormat(MessageId), + /// Downward message is unsupported version of XCM. + UnsupportedVersion(MessageId), + /// Downward message executed with the given outcome. + ExecutedDownward(MessageId, Outcome), + } + + impl Pallet { + pub fn set_para_id(para_id: ParaId) { + ParachainId::::put(para_id); + } + + pub fn parachain_id() -> ParaId { + ParachainId::::get() + } + + pub fn received_dmp() -> Vec> { + ReceivedDmp::::get() + } + + fn handle_xcmp_message( + sender: ParaId, + _sent_at: RelayBlockNumber, + xcm: VersionedXcm, + max_weight: Weight, + ) -> Result { + let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + 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::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 { used, error } => + (Ok(used), Event::Fail(Some(hash), error)), + } + }, + Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), + }; + Self::deposit_event(event); + result + } + } + + impl XcmpMessageHandler for Pallet { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + max_weight: Weight, + ) -> Weight { + for (sender, sent_at, data) in iter { + let mut data_ref = data; + let _ = XcmpMessageFormat::decode(&mut data_ref) + .expect("Simulator encodes with versioned xcm format; qed"); + + let mut remaining_fragments = data_ref; + while !remaining_fragments.is_empty() { + if let Ok(xcm) = + VersionedXcm::::decode(&mut remaining_fragments) + { + let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); + } else { + debug_assert!(false, "Invalid incoming XCMP message data"); + } + } + } + max_weight + } + } + + impl DmpMessageHandler for Pallet { + fn handle_dmp_messages( + iter: impl Iterator)>, + limit: Weight, + ) -> Weight { + for (_i, (_sent_at, data)) in iter.enumerate() { + let mut id = sp_io::hashing::blake2_256(&data[..]); + let maybe_versioned = VersionedXcm::::decode(&mut &data[..]); + match maybe_versioned { + Err(_) => { + Self::deposit_event(Event::InvalidFormat(id)); + }, + Ok(versioned) => match Xcm::try_from(versioned) { + Err(()) => Self::deposit_event(Event::UnsupportedVersion(id)), + Ok(x) => { + let outcome = T::XcmExecutor::prepare_and_execute( + Parent, + x.clone(), + &mut id, + limit, + Weight::zero(), + ); + ReceivedDmp::::append(x); + Self::deposit_event(Event::ExecutedDownward(id, outcome)); + }, + }, + } + } + limit + } + } +} diff --git a/substrate/frame/revive/mock-network/src/mocks/relay_message_queue.rs b/substrate/frame/revive/mock-network/src/mocks/relay_message_queue.rs new file mode 100644 index 000000000000..14099965e3f1 --- /dev/null +++ b/substrate/frame/revive/mock-network/src/mocks/relay_message_queue.rs @@ -0,0 +1,52 @@ +// 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 frame_support::{parameter_types, weights::Weight}; +use xcm::latest::prelude::*; +use xcm_simulator::{ + AggregateMessageOrigin, ProcessMessage, ProcessMessageError, UmpQueueId, WeightMeter, +}; + +use crate::relay_chain::{RuntimeCall, XcmConfig}; + +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); + pub const MessageQueueHeapSize: u32 = 65_536; + pub const MessageQueueMaxStale: u32 = 16; +} + +/// Message processor to handle any messages that were enqueued into the `MessageQueue` pallet. +pub struct MessageProcessor; +impl ProcessMessage for MessageProcessor { + type Origin = AggregateMessageOrigin; + + fn process_message( + message: &[u8], + origin: Self::Origin, + meter: &mut WeightMeter, + id: &mut [u8; 32], + ) -> Result { + let para = match origin { + AggregateMessageOrigin::Ump(UmpQueueId::Para(para)) => para, + }; + xcm_builder::ProcessXcmMessage::< + Junction, + xcm_executor::XcmExecutor, + RuntimeCall, + >::process_message(message, Junction::Parachain(para.into()), meter, id) + } +} diff --git a/substrate/frame/revive/mock-network/src/parachain.rs b/substrate/frame/revive/mock-network/src/parachain.rs new file mode 100644 index 000000000000..3def48cca96b --- /dev/null +++ b/substrate/frame/revive/mock-network/src/parachain.rs @@ -0,0 +1,346 @@ +// 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 . + +//! Parachain runtime mock. + +mod contracts_config; +use crate::{ + mocks::msg_queue::pallet as mock_msg_queue, + primitives::{AccountId, AssetIdForAssets, Balance}, +}; +use core::marker::PhantomData; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{AsEnsureOriginWithArg, Contains, ContainsPair, Everything, EverythingBut, Nothing}, + weights::{ + constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND}, + Weight, + }, +}; +use frame_system::{EnsureRoot, EnsureSigned}; +use pallet_xcm::XcmPassthrough; +use sp_core::{ConstU32, ConstU64, H256}; +use sp_runtime::traits::{Get, IdentityLookup, MaybeEquivalence}; + +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, + ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, NativeAsset, + NoChecking, ParentAsSuperuser, ParentIsPreset, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, WithComputedOrigin, +}; +use xcm_executor::{traits::JustTry, Config, XcmExecutor}; + +pub type SovereignAccountOf = + (AccountId32Aliases, ParentIsPreset); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Block = Block; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + 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! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type AccountStore = System; + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type FreezeIdentifier = (); + type MaxFreezes = ConstU32<0>; + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type WeightInfo = (); +} + +parameter_types! { + pub const AssetDeposit: u128 = 1_000_000; + pub const MetadataDepositBase: u128 = 1_000_000; + pub const MetadataDepositPerByte: u128 = 100_000; + pub const AssetAccountDeposit: u128 = 1_000_000; + pub const ApprovalDeposit: u128 = 1_000_000; + pub const AssetsStringLimit: u32 = 50; + pub const RemoveItemsLimit: u32 = 50; +} + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetIdForAssets; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type AssetAccountDeposit = AssetAccountDeposit; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = AssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type RemoveItemsLimit = RemoveItemsLimit; + type AssetIdParameter = AssetIdForAssets; + type CallbackHandle = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); + pub const ReservedDmpWeight: Weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_div(4), 0); +} + +parameter_types! { + pub const KsmLocation: Location = Location::parent(); + pub const TokenLocation: Location = Here.into_location(); + pub const RelayNetwork: NetworkId = ByGenesis([0; 32]); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(MsgQueue::parachain_id().into())].into(); +} + +pub type XcmOriginToCallOrigin = ( + SovereignSignedViaLocation, + ParentAsSuperuser, + SignedAccountId32AsNative, + XcmPassthrough, +); + +parameter_types! { + pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) = (AssetId(Parent.into()), 1_000_000_000_000, 1024 * 1024); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub ForeignPrefix: Location = (Parent,).into(); + pub CheckingAccount: AccountId = PolkadotXcm::check_account(); + pub TrustedLockPairs: (Location, AssetFilter) = + (Parent.into(), Wild(AllOf { id: AssetId(Parent.into()), fun: WildFungible })); +} + +pub fn estimate_message_fee(number_of_instructions: u64) -> u128 { + let weight = estimate_weight(number_of_instructions); + + estimate_fee_for_weight(weight) +} + +pub fn estimate_weight(number_of_instructions: u64) -> Weight { + XcmInstructionWeight::get().saturating_mul(number_of_instructions) +} + +pub fn estimate_fee_for_weight(weight: Weight) -> u128 { + let (_, units_per_second, units_per_mb) = TokensPerSecondPerMegabyte::get(); + + units_per_second * (weight.ref_time() as u128) / (WEIGHT_REF_TIME_PER_SECOND as u128) + + units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128) +} + +pub type LocalBalancesTransactor = + FungibleAdapter, SovereignAccountOf, AccountId, ()>; + +pub struct FromLocationToAsset(PhantomData<(Location, AssetId)>); +impl MaybeEquivalence + for FromLocationToAsset +{ + fn convert(value: &Location) -> Option { + match value.unpack() { + (1, []) => Some(0 as AssetIdForAssets), + (1, [Parachain(para_id)]) => Some(*para_id as AssetIdForAssets), + _ => None, + } + } + + fn convert_back(_id: &AssetIdForAssets) -> Option { + None + } +} + +pub type ForeignAssetsTransactor = FungiblesAdapter< + Assets, + ConvertedConcreteId< + AssetIdForAssets, + Balance, + FromLocationToAsset, + JustTry, + >, + SovereignAccountOf, + AccountId, + NoChecking, + CheckingAccount, +>; + +/// Means for transacting assets on this chain +pub type AssetTransactors = (LocalBalancesTransactor, ForeignAssetsTransactor); + +pub struct ParentRelay; +impl Contains for ParentRelay { + fn contains(location: &Location) -> bool { + location.contains_parents_only(1) + } +} +pub struct ThisParachain; +impl Contains for ThisParachain { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (0, [Junction::AccountId32 { .. }])) + } +} + +pub type XcmRouter = crate::ParachainXcmRouter; + +pub type Barrier = ( + xcm_builder::AllowUnpaidExecutionFrom, + WithComputedOrigin< + (AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom), + UniversalLocation, + ConstU32<1>, + >, +); + +parameter_types! { + pub NftCollectionOne: AssetFilter + = Wild(AllOf { fun: WildNonFungible, id: AssetId((Parent, GeneralIndex(1)).into()) }); + pub NftCollectionOneForRelay: (AssetFilter, Location) + = (NftCollectionOne::get(), Parent.into()); + pub RelayNativeAsset: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId((Parent, Here).into()) }); + pub RelayNativeAssetForRelay: (AssetFilter, Location) = (RelayNativeAsset::get(), Parent.into()); +} +pub type TrustedTeleporters = + (xcm_builder::Case, xcm_builder::Case); +pub type TrustedReserves = EverythingBut>; + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; + type OriginConverter = XcmOriginToCallOrigin; + type IsReserve = (NativeAsset, TrustedReserves); + type IsTeleporter = TrustedTeleporters; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetLocker = PolkadotXcm; + type AssetExchanger = (); + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = AllPalletsWithSystem; + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; + type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); + type XcmRecorder = PolkadotXcm; +} + +impl mock_msg_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +pub struct TrustedLockerCase(PhantomData); +impl> ContainsPair for TrustedLockerCase { + fn contains(origin: &Location, asset: &Asset) -> bool { + let (o, a) = T::get(); + a.matches(asset) && &o == origin + } +} + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Nothing; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + 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 = IsConcrete; + type TrustedLockers = TrustedLockerCase; + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); + type WeightInfo = pallet_xcm::TestWeightInfo; + type AdminOrigin = EnsureRoot; +} + +type Block = frame_system::mocking::MockBlock; + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1>; + type WeightInfo = (); +} + +construct_runtime!( + pub enum Runtime + { + System: frame_system, + Balances: pallet_balances, + Timestamp: pallet_timestamp, + MsgQueue: mock_msg_queue, + PolkadotXcm: pallet_xcm, + Contracts: pallet_revive, + Assets: pallet_assets, + } +); diff --git a/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs b/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs new file mode 100644 index 000000000000..49f53ae5bc3d --- /dev/null +++ b/substrate/frame/revive/mock-network/src/parachain/contracts_config.rs @@ -0,0 +1,27 @@ +// 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 super::{Balances, Runtime, RuntimeCall, RuntimeEvent}; +use crate::parachain::RuntimeHoldReason; +use frame_support::derive_impl; + +#[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)] +impl pallet_revive::Config for Runtime { + type AddressGenerator = pallet_revive::DefaultAddressGenerator; + type Currency = Balances; + type Time = super::Timestamp; + type Xcm = pallet_xcm::Pallet; +} diff --git a/substrate/frame/revive/mock-network/src/primitives.rs b/substrate/frame/revive/mock-network/src/primitives.rs new file mode 100644 index 000000000000..efc42772f88a --- /dev/null +++ b/substrate/frame/revive/mock-network/src/primitives.rs @@ -0,0 +1,23 @@ +// 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 . + +pub type Balance = u128; + +pub const UNITS: Balance = 10_000_000_000; +pub const CENTS: Balance = UNITS / 100; // 100_000_000 + +pub type AccountId = sp_runtime::AccountId32; +pub type AssetIdForAssets = u128; diff --git a/substrate/frame/revive/mock-network/src/relay_chain.rs b/substrate/frame/revive/mock-network/src/relay_chain.rs new file mode 100644 index 000000000000..8829fff3d043 --- /dev/null +++ b/substrate/frame/revive/mock-network/src/relay_chain.rs @@ -0,0 +1,239 @@ +// 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 . + +//! Relay chain runtime mock. + +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{Contains, Everything, Nothing}, + weights::Weight, +}; + +use frame_system::EnsureRoot; +use sp_core::{ConstU32, H256}; +use sp_runtime::traits::IdentityLookup; + +use polkadot_parachain_primitives::primitives::Id as ParaId; +use polkadot_runtime_parachains::{configuration, origin, shared}; +use xcm::latest::prelude::*; +use xcm_builder::{ + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, + ChildSystemParachainAsSuperuser, DescribeAllTerminal, DescribeFamily, FixedRateOfFungible, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin, +}; +use xcm_executor::{Config, XcmExecutor}; + +use super::{ + mocks::relay_message_queue::*, + primitives::{AccountId, Balance}, +}; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Block = Block; + type Nonce = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + 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! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = ConstU32<0>; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; +} + +impl shared::Config for Runtime { + type DisabledValidators = (); +} + +impl configuration::Config for Runtime { + type WeightInfo = configuration::TestWeightInfo; +} + +parameter_types! { + pub RelayNetwork: NetworkId = ByGenesis([0; 32]); + pub const TokenLocation: Location = Here.into_location(); + pub UniversalLocation: InteriorLocation = RelayNetwork::get().into(); + pub UnitWeightCost: u64 = 1_000; +} + +pub type SovereignAccountOf = ( + HashedDescription>, + AccountId32Aliases, + ChildParachainConvertsVia, +); + +pub type LocalBalancesTransactor = + FungibleAdapter, SovereignAccountOf, AccountId, ()>; + +pub type AssetTransactors = LocalBalancesTransactor; + +type LocalOriginConverter = ( + SovereignSignedViaLocation, + ChildParachainAsNative, + SignedAccountId32AsNative, + ChildSystemParachainAsSuperuser, +); + +parameter_types! { + pub const XcmInstructionWeight: Weight = Weight::from_parts(1_000, 1_000); + pub TokensPerSecondPerMegabyte: (AssetId, u128, u128) = + (AssetId(TokenLocation::get()), 1_000_000_000_000, 1024 * 1024); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; +} + +pub struct ChildrenParachains; +impl Contains for ChildrenParachains { + fn contains(location: &Location) -> bool { + matches!(location.unpack(), (0, [Parachain(_)])) + } +} + +pub type XcmRouter = crate::RelayChainXcmRouter; +pub type Barrier = WithComputedOrigin< + ( + AllowExplicitUnpaidExecutionFrom, + AllowTopLevelPaidExecutionFrom, + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<1>, +>; + +pub struct XcmConfig; +impl Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = AssetTransactors; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = XcmPallet; + type AssetTrap = XcmPallet; + type AssetLocker = XcmPallet; + type AssetExchanger = (); + type AssetClaims = XcmPallet; + type SubscriptionService = XcmPallet; + type PalletInstancesInfo = AllPalletsWithSystem; + type FeeManager = (); + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = RuntimeCall; + type SafeCallFilter = Everything; + type Aliasers = Nothing; + type TransactionalProcessor = FrameTransactionalProcessor; + type HrmpNewChannelOpenRequestHandler = (); + type HrmpChannelAcceptedHandler = (); + type HrmpChannelClosingHandler = (); + type XcmRecorder = XcmPallet; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + 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 = IsConcrete; + type TrustedLockers = (); + type SovereignAccountOf = SovereignAccountOf; + type MaxLockers = ConstU32<8>; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); + type WeightInfo = pallet_xcm::TestWeightInfo; + type AdminOrigin = EnsureRoot; +} + +impl origin::Config for Runtime {} + +type Block = frame_system::mocking::MockBlock; + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Size = u32; + type HeapSize = MessageQueueHeapSize; + type MaxStale = MessageQueueMaxStale; + type ServiceWeight = MessageQueueServiceWeight; + type IdleMaxServiceWeight = (); + type MessageProcessor = MessageProcessor; + type QueueChangeHandler = (); + type WeightInfo = (); + type QueuePausedQuery = (); +} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + Balances: pallet_balances, + ParasOrigin: origin, + XcmPallet: pallet_xcm, + MessageQueue: pallet_message_queue, + } +); diff --git a/substrate/frame/revive/mock-network/src/tests.rs b/substrate/frame/revive/mock-network/src/tests.rs new file mode 100644 index 000000000000..9259dd6f169e --- /dev/null +++ b/substrate/frame/revive/mock-network/src/tests.rs @@ -0,0 +1,207 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + parachain::{self, Runtime}, + parachain_account_sovereign_account_id, + primitives::{AccountId, CENTS}, + relay_chain, MockNet, ParaA, ParachainBalances, Relay, ALICE, BOB, INITIAL_BALANCE, +}; +use codec::{Decode, Encode}; +use frame_support::traits::{fungibles::Mutate, Currency}; +use frame_system::RawOrigin; +use pallet_revive::{ + test_utils::{self, builder::*}, + Code, +}; +use pallet_revive_fixtures::compile_module; +use pallet_revive_uapi::ReturnErrorCode; +use xcm::{v4::prelude::*, VersionedLocation, VersionedXcm}; +use xcm_simulator::TestExt; + +macro_rules! assert_return_code { + ( $x:expr , $y:expr $(,)? ) => {{ + assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); + }}; +} + +fn bare_call(dest: sp_runtime::AccountId32) -> BareCallBuilder { + BareCallBuilder::::bare_call(RawOrigin::Signed(ALICE).into(), dest) +} + +/// Instantiate the tests contract, and fund it with some balance and assets. +fn instantiate_test_contract(name: &str) -> AccountId { + let (wasm, _) = compile_module::(name).unwrap(); + + // Instantiate contract. + let contract_addr = ParaA::execute_with(|| { + BareInstantiateBuilder::::bare_instantiate( + RawOrigin::Signed(ALICE).into(), + Code::Upload(wasm), + ) + .build_and_unwrap_account_id() + }); + + // Funds contract account with some balance and assets. + ParaA::execute_with(|| { + parachain::Balances::make_free_balance_be(&contract_addr, INITIAL_BALANCE); + parachain::Assets::mint_into(0u32.into(), &contract_addr, INITIAL_BALANCE).unwrap(); + }); + Relay::execute_with(|| { + let sovereign_account = parachain_account_sovereign_account_id(1u32, contract_addr.clone()); + relay_chain::Balances::make_free_balance_be(&sovereign_account, INITIAL_BALANCE); + }); + + contract_addr +} + +#[test] +fn test_xcm_execute() { + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + + // Execute XCM instructions through the contract. + ParaA::execute_with(|| { + let amount: u128 = 10 * CENTS; + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: BOB.clone().into() }; + + // The XCM used to transfer funds to Bob. + let message: Xcm<()> = Xcm::builder_unsafe() + .withdraw_asset(assets.clone()) + .deposit_asset(assets, beneficiary) + .build(); + + let result = bare_call(contract_addr.clone()) + .data(VersionedXcm::V4(message).encode()) + .build(); + + assert_eq!(result.gas_consumed, result.gas_required); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::Success); + + // Check if the funds are subtracted from the account of Alice and added to the account of + // Bob. + let initial = INITIAL_BALANCE; + assert_eq!(ParachainBalances::free_balance(BOB), initial + amount); + assert_eq!(ParachainBalances::free_balance(&contract_addr), initial - amount); + }); +} + +#[test] +fn test_xcm_execute_incomplete() { + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + let amount = 10 * CENTS; + + // Execute XCM instructions through the contract. + ParaA::execute_with(|| { + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: BOB.clone().into() }; + + // The XCM used to transfer funds to Bob. + let message: Xcm<()> = Xcm::builder_unsafe() + .withdraw_asset(assets.clone()) + // This will fail as the contract does not have enough balance to complete both + // withdrawals. + .withdraw_asset((Here, INITIAL_BALANCE)) + .buy_execution(assets.clone(), Unlimited) + .deposit_asset(assets, beneficiary) + .build(); + + let result = bare_call(contract_addr.clone()) + .data(VersionedXcm::V4(message).encode()) + .build(); + + assert_eq!(result.gas_consumed, result.gas_required); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); + + assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); + assert_eq!(ParachainBalances::free_balance(&contract_addr), INITIAL_BALANCE - amount); + }); +} + +#[test] +fn test_xcm_execute_reentrant_call() { + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + + ParaA::execute_with(|| { + let transact_call = parachain::RuntimeCall::Contracts(pallet_revive::Call::call { + dest: contract_addr.clone(), + gas_limit: 1_000_000.into(), + storage_deposit_limit: test_utils::deposit_limit::(), + data: vec![], + value: 0u128, + }); + + // The XCM used to transfer funds to Bob. + let message: Xcm = Xcm::builder_unsafe() + .transact(OriginKind::Native, 1_000_000_000, transact_call.encode()) + .expect_transact_status(MaybeErrorCode::Success) + .build(); + + let result = bare_call(contract_addr.clone()) + .data(VersionedXcm::V4(message).encode()) + .build_and_unwrap_result(); + + assert_return_code!(&result, ReturnErrorCode::XcmExecutionFailed); + + // Funds should not change hands as the XCM transact failed. + assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); + }); +} + +#[test] +fn test_xcm_send() { + MockNet::reset(); + let contract_addr = instantiate_test_contract("xcm_send"); + let amount = 1_000 * CENTS; + let fee = parachain::estimate_message_fee(4); // Accounts for the `DescendOrigin` instruction added by `send_xcm` + + // Send XCM instructions through the contract, to transfer some funds from the contract + // derivative account to Alice on the relay chain. + ParaA::execute_with(|| { + let dest = VersionedLocation::V4(Parent.into()); + let assets: Asset = (Here, amount).into(); + let beneficiary = AccountId32 { network: None, id: ALICE.clone().into() }; + + let message: Xcm<()> = Xcm::builder() + .withdraw_asset(assets.clone()) + .buy_execution((Here, fee), Unlimited) + .deposit_asset(assets, beneficiary) + .build(); + + let result = bare_call(contract_addr.clone()) + .data((dest, VersionedXcm::V4(message)).encode()) + .build_and_unwrap_result(); + + let mut data = &result.data[..]; + XcmHash::decode(&mut data).expect("Failed to decode xcm_send message_id"); + }); + + Relay::execute_with(|| { + let derived_contract_addr = ¶chain_account_sovereign_account_id(1, contract_addr); + assert_eq!( + INITIAL_BALANCE - amount, + relay_chain::Balances::free_balance(derived_contract_addr) + ); + assert_eq!(INITIAL_BALANCE + amount - fee, relay_chain::Balances::free_balance(ALICE)); + }); +} diff --git a/substrate/frame/revive/proc-macro/Cargo.toml b/substrate/frame/revive/proc-macro/Cargo.toml new file mode 100644 index 000000000000..7b47d6053504 --- /dev/null +++ b/substrate/frame/revive/proc-macro/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "pallet-revive-proc-macro" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "Procedural macros used in pallet_revive" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { features = ["full"], workspace = true } diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs new file mode 100644 index 000000000000..95f4110a2d76 --- /dev/null +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -0,0 +1,616 @@ +// 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. + +//! Procedural macros used in the contracts module. +//! +//! Most likely you should use the [`#[define_env]`][`macro@define_env`] attribute macro which hides +//! boilerplate of defining external environment for a wasm module. + +use proc_macro::TokenStream; +use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; +use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, FnArg, Ident}; + +/// Defines a host functions set that can be imported by contract wasm code. +/// +/// **NB**: Be advised that all functions defined by this macro +/// will panic if called with unexpected arguments. +/// +/// It's up to you as the user of this macro to check signatures of wasm code to be executed +/// and reject the code if any imported function has a mismatched signature. +/// +/// ## Example +/// +/// ```nocompile +/// #[define_env] +/// pub mod some_env { +/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result<(), TrapReason> { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// } +/// ``` +/// This example will expand to the `foo()` defined in the wasm module named `seal0`. This is +/// because the module `seal0` is the default when no module is specified. +/// +/// To define a host function in `seal2` and `seal3` modules, it should be annotated with the +/// appropriate attribute as follows: +/// +/// ## Example +/// +/// ```nocompile +/// #[define_env] +/// pub mod some_env { +/// #[version(2)] +/// fn foo(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// +/// #[version(3)] +/// #[unstable] +/// fn bar(ctx: _, memory: _, key_ptr: u32, value_ptr: u32, value_len: u32) -> Result { +/// ctx.some_host_fn(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) +/// } +/// } +/// ``` +/// The function `bar` is additionally annotated with `unstable` which removes it from the stable +/// interface. Check out the README to learn about unstable functions. +/// +/// +/// In this example, the following host functions will be generated by the macro: +/// - `foo()` in module `seal1`, +/// - `seal_foo()` in module `seal1`, +/// - `bar()` in module `seal42`. +/// +/// Only following return types are allowed for the host functions defined with the macro: +/// - `Result<(), TrapReason>`, +/// - `Result`, +/// - `Result`. +/// +/// The macro expands to `pub struct Env` declaration, with the following traits implementations: +/// - `pallet_revive::wasm::Environment> where E: Ext` +/// - `pallet_revive::wasm::Environment<()>` +/// +/// The implementation on `()` can be used in places where no `Ext` exists, yet. This is useful +/// when only checking whether a code can be instantiated without actually executing any code. +/// +/// +/// To build up these docs, run: +/// +/// ```nocompile +/// cargo doc +/// ``` +#[proc_macro_attribute] +pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { + if !attr.is_empty() { + let msg = r#"Invalid `define_env` attribute macro: expected no attributes: + - `#[define_env]`"#; + let span = TokenStream2::from(attr).span(); + return syn::Error::new(span, msg).to_compile_error().into() + } + + let item = syn::parse_macro_input!(item as syn::ItemMod); + + match EnvDef::try_from(item) { + Ok(mut def) => expand_env(&mut def).into(), + Err(e) => e.to_compile_error().into(), + } +} + +/// Parsed environment definition. +struct EnvDef { + host_funcs: Vec, +} + +/// Parsed host function definition. +struct HostFn { + item: syn::ItemFn, + api_version: Option, + name: String, + returns: HostFnReturn, + cfg: Option, +} + +enum HostFnReturn { + Unit, + U32, + ReturnCode, +} + +impl HostFnReturn { + fn map_output(&self) -> TokenStream2 { + match self { + Self::Unit => quote! { |_| None }, + Self::U32 => quote! { |ret_val| Some(ret_val) }, + Self::ReturnCode => quote! { |ret_code| Some(ret_code.into()) }, + } + } + + fn success_type(&self) -> syn::ReturnType { + match self { + Self::Unit => syn::ReturnType::Default, + Self::U32 => parse_quote! { -> u32 }, + Self::ReturnCode => parse_quote! { -> ReturnErrorCode }, + } + } +} + +impl EnvDef { + pub fn try_from(item: syn::ItemMod) -> syn::Result { + let span = item.span(); + let err = |msg| syn::Error::new(span, msg); + let items = &item + .content + .as_ref() + .ok_or(err("Invalid environment definition, expected `mod` to be inlined."))? + .1; + + let extract_fn = |i: &syn::Item| match i { + syn::Item::Fn(i_fn) => Some(i_fn.clone()), + _ => None, + }; + + let host_funcs = items + .iter() + .filter_map(extract_fn) + .map(HostFn::try_from) + .collect::, _>>()?; + + Ok(Self { host_funcs }) + } +} + +impl HostFn { + pub fn try_from(mut item: syn::ItemFn) -> syn::Result { + let err = |span, msg| { + let msg = format!("Invalid host function definition.\n{}", msg); + syn::Error::new(span, msg) + }; + + // process attributes + let msg = "Only #[api_version()], #[cfg] and #[mutating] attributes are allowed."; + let span = item.span(); + let mut attrs = item.attrs.clone(); + attrs.retain(|a| !a.path().is_ident("doc")); + let mut api_version = None; + let mut mutating = false; + let mut cfg = None; + while let Some(attr) = attrs.pop() { + let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string(); + match ident.as_str() { + "api_version" => { + if api_version.is_some() { + return Err(err(span, "#[api_version] can only be specified once")) + } + api_version = + Some(attr.parse_args::().and_then(|lit| lit.base10_parse())?); + }, + "mutating" => { + if mutating { + return Err(err(span, "#[mutating] can only be specified once")) + } + mutating = true; + }, + "cfg" => { + if cfg.is_some() { + return Err(err(span, "#[cfg] can only be specified once")) + } + cfg = Some(attr); + }, + id => return Err(err(span, &format!("Unsupported attribute \"{id}\". {msg}"))), + } + } + + if mutating { + let stmt = syn::parse_quote! { + if self.ext().is_read_only() { + return Err(Error::::StateChangeDenied.into()); + } + }; + item.block.stmts.insert(0, stmt); + } + + let name = item.sig.ident.to_string(); + + let msg = "Every function must start with these two parameters: &mut self, memory: &mut M"; + let special_args = item + .sig + .inputs + .iter() + .take(2) + .enumerate() + .map(|(i, arg)| is_valid_special_arg(i, arg)) + .fold(0u32, |acc, valid| if valid { acc + 1 } else { acc }); + + if special_args != 2 { + return Err(err(span, msg)) + } + + // process return type + let msg = r#"Should return one of the following: + - Result<(), TrapReason>, + - Result, + - Result"#; + let ret_ty = match item.clone().sig.output { + syn::ReturnType::Type(_, ty) => Ok(ty.clone()), + _ => Err(err(span, &msg)), + }?; + match *ret_ty { + syn::Type::Path(tp) => { + let result = &tp.path.segments.last().ok_or(err(span, &msg))?; + let (id, span) = (result.ident.to_string(), result.ident.span()); + id.eq(&"Result".to_string()).then_some(()).ok_or(err(span, &msg))?; + + match &result.arguments { + syn::PathArguments::AngleBracketed(group) => { + if group.args.len() != 2 { + return Err(err(span, &msg)) + }; + + let arg2 = group.args.last().ok_or(err(span, &msg))?; + + let err_ty = match arg2 { + syn::GenericArgument::Type(ty) => Ok(ty.clone()), + _ => Err(err(arg2.span(), &msg)), + }?; + + match err_ty { + syn::Type::Path(tp) => Ok(tp + .path + .segments + .first() + .ok_or(err(arg2.span(), &msg))? + .ident + .to_string()), + _ => Err(err(tp.span(), &msg)), + }? + .eq("TrapReason") + .then_some(()) + .ok_or(err(span, &msg))?; + + let arg1 = group.args.first().ok_or(err(span, &msg))?; + let ok_ty = match arg1 { + syn::GenericArgument::Type(ty) => Ok(ty.clone()), + _ => Err(err(arg1.span(), &msg)), + }?; + let ok_ty_str = match ok_ty { + syn::Type::Path(tp) => Ok(tp + .path + .segments + .first() + .ok_or(err(arg1.span(), &msg))? + .ident + .to_string()), + syn::Type::Tuple(tt) => { + if !tt.elems.is_empty() { + return Err(err(arg1.span(), &msg)) + }; + Ok("()".to_string()) + }, + _ => Err(err(ok_ty.span(), &msg)), + }?; + let returns = match ok_ty_str.as_str() { + "()" => Ok(HostFnReturn::Unit), + "u32" => Ok(HostFnReturn::U32), + "ReturnErrorCode" => Ok(HostFnReturn::ReturnCode), + _ => Err(err(arg1.span(), &msg)), + }?; + + Ok(Self { item, api_version, name, returns, cfg }) + }, + _ => Err(err(span, &msg)), + } + }, + _ => Err(err(span, &msg)), + } + } +} + +fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool { + match (idx, arg) { + (0, FnArg::Receiver(rec)) => rec.reference.is_some() && rec.mutability.is_some(), + (1, FnArg::Typed(pat)) => { + let ident = + if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false }; + if !(ident == "memory" || ident == "_memory") { + return false + } + matches!(*pat.ty, syn::Type::Reference(_)) + }, + _ => false, + } +} + +fn arg_decoder<'a, P, I>(param_names: P, param_types: I) -> TokenStream2 +where + P: Iterator> + Clone, + I: Iterator> + Clone, +{ + const ALLOWED_REGISTERS: u32 = 6; + let mut registers_used = 0; + let mut bindings = vec![]; + for (idx, (name, ty)) in param_names.clone().zip(param_types.clone()).enumerate() { + let syn::Type::Path(path) = &**ty else { + panic!("Type needs to be path"); + }; + let Some(ident) = path.path.get_ident() else { + panic!("Type needs to be ident"); + }; + let size = + if ident == "i8" || + ident == "i16" || ident == "i32" || + ident == "u8" || ident == "u16" || + ident == "u32" + { + 1 + } else if ident == "i64" || ident == "u64" { + 2 + } else { + panic!("Pass by value only supports primitives"); + }; + registers_used += size; + if registers_used > ALLOWED_REGISTERS { + return quote! { + let (#( #param_names, )*): (#( #param_types, )*) = memory.read_as(__a0__)?; + } + } + let this_reg = quote::format_ident!("__a{}__", idx); + let next_reg = quote::format_ident!("__a{}__", idx + 1); + let binding = if size == 1 { + quote! { + let #name = #this_reg as #ty; + } + } else { + quote! { + let #name = (#this_reg as #ty) | ((#next_reg as #ty) << 32); + } + }; + bindings.push(binding); + } + quote! { + #( #bindings )* + } +} + +/// Expands environment definition. +/// Should generate source code for: +/// - implementations of the host functions to be added to the wasm runtime environment (see +/// `expand_impls()`). +fn expand_env(def: &EnvDef) -> TokenStream2 { + let impls = expand_functions(def); + let bench_impls = expand_bench_functions(def); + let docs = expand_func_doc(def); + let highest_api_version = + def.host_funcs.iter().filter_map(|f| f.api_version).max().unwrap_or_default(); + + quote! { + #[cfg(test)] + pub const HIGHEST_API_VERSION: u16 = #highest_api_version; + + impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { + fn handle_ecall( + &mut self, + memory: &mut M, + __syscall_symbol__: &[u8], + __available_api_version__: ApiVersion, + ) -> Result, TrapReason> + { + #impls + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { + #bench_impls + } + + /// Documentation of the syscalls (host functions) available to contracts. + /// + /// Each of the functions in this trait represent a function that is callable + /// by the contract. Guests use the function name as the import symbol. + /// + /// # Note + /// + /// This module is not meant to be used by any code. Rather, it is meant to be + /// consumed by humans through rustdoc. + #[cfg(doc)] + pub trait SyscallDoc { + #docs + } + } +} + +fn expand_functions(def: &EnvDef) -> TokenStream2 { + let impls = def.host_funcs.iter().map(|f| { + // skip the self and memory argument + let params = f.item.sig.inputs.iter().skip(2); + let param_names = params.clone().filter_map(|arg| { + let FnArg::Typed(arg) = arg else { + return None; + }; + Some(&arg.pat) + }); + let param_types = params.clone().filter_map(|arg| { + let FnArg::Typed(arg) = arg else { + return None; + }; + Some(&arg.ty) + }); + let arg_decoder = arg_decoder(param_names, param_types); + let cfg = &f.cfg; + let name = &f.name; + let syscall_symbol = Literal::byte_string(name.as_bytes()); + let body = &f.item.block; + let map_output = f.returns.map_output(); + let output = &f.item.sig.output; + let api_version = match f.api_version { + Some(version) => quote! { Some(#version) }, + None => quote! { None }, + }; + + // wrapped host function body call with host function traces + // see https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/contracts#host-function-tracing + let wrapped_body_with_trace = { + let trace_fmt_args = params.clone().filter_map(|arg| match arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(p) => match *p.pat.clone() { + syn::Pat::Ident(ref pat_ident) => Some(pat_ident.ident.clone()), + _ => None, + }, + }); + + let params_fmt_str = trace_fmt_args + .clone() + .map(|s| format!("{s}: {{:?}}")) + .collect::>() + .join(", "); + let trace_fmt_str = format!("{}({}) = {{:?}}\n", name, params_fmt_str); + + quote! { + // wrap body in closure to make sure the tracing is always executed + let result = (|| #body)(); + if ::log::log_enabled!(target: "runtime::revive::strace", ::log::Level::Trace) { + use core::fmt::Write; + let mut w = sp_std::Writer::default(); + let _ = core::write!(&mut w, #trace_fmt_str, #( #trace_fmt_args, )* result); + let msg = core::str::from_utf8(&w.inner()).unwrap_or_default(); + self.ext().append_debug_buffer(msg); + } + result + } + }; + + quote! { + #cfg + #syscall_symbol if __is_available__(#api_version) => { + // closure is needed so that "?" can infere the correct type + (|| #output { + #arg_decoder + #wrapped_body_with_trace + })().map(#map_output) + }, + } + }); + + quote! { + // Write gas from polkavm into pallet-revive before entering the host function. + let __gas_left_before__ = self + .ext + .gas_meter_mut() + .sync_from_executor(memory.gas()) + .map_err(TrapReason::from)?; + + // This is the overhead to call an empty syscall that always needs to be charged. + self.charge_gas(crate::wasm::RuntimeCosts::HostFn).map_err(TrapReason::from)?; + + // Not all APIs are available depending on configuration or when the code was deployed. + // This closure will be used by syscall specific code to perform this check. + let __is_available__ = |syscall_version: Option| { + match __available_api_version__ { + ApiVersion::UnsafeNewest => true, + ApiVersion::Versioned(max_available_version) => + syscall_version + .map(|required_version| max_available_version >= required_version) + .unwrap_or(false), + } + }; + + // They will be mapped to variable names by the syscall specific code. + let (__a0__, __a1__, __a2__, __a3__, __a4__, __a5__) = memory.read_input_regs(); + + // Execute the syscall specific logic in a closure so that the gas metering code is always executed. + let result = (|| match __syscall_symbol__ { + #( #impls )* + _ => Err(TrapReason::SupervisorError(Error::::InvalidSyscall.into())) + })(); + + // Write gas from pallet-revive into polkavm after leaving the host function. + let gas = self.ext.gas_meter_mut().sync_to_executor(__gas_left_before__).map_err(TrapReason::from)?; + memory.set_gas(gas.into()); + result + } +} + +fn expand_bench_functions(def: &EnvDef) -> TokenStream2 { + let impls = def.host_funcs.iter().map(|f| { + // skip the context and memory argument + let params = f.item.sig.inputs.iter().skip(2); + let cfg = &f.cfg; + let name = &f.name; + let body = &f.item.block; + let output = &f.item.sig.output; + + let name = Ident::new(&format!("bench_{name}"), Span::call_site()); + quote! { + #cfg + pub fn #name(&mut self, memory: &mut M, #(#params),*) #output { + #body + } + } + }); + + quote! { + #( #impls )* + } +} + +fn expand_func_doc(def: &EnvDef) -> TokenStream2 { + let docs = def.host_funcs.iter().map(|func| { + // Remove auxiliary args: `ctx: _` and `memory: _` + let func_decl = { + let mut sig = func.item.sig.clone(); + sig.inputs = sig + .inputs + .iter() + .skip(2) + .map(|p| p.clone()) + .collect::>(); + sig.output = func.returns.success_type(); + sig.to_token_stream() + }; + let func_doc = { + let func_docs = { + let docs = func.item.attrs.iter().filter(|a| a.path().is_ident("doc")).map(|d| { + let docs = d.to_token_stream(); + quote! { #docs } + }); + quote! { #( #docs )* } + }; + let availability = if let Some(version) = func.api_version { + let info = format!( + "\n# Required API version\nThis API was added in version **{version}**.", + ); + quote! { #[doc = #info] } + } else { + let info = + "\n# Unstable API\nThis API is not standardized and only available for testing."; + quote! { #[doc = #info] } + }; + quote! { + #func_docs + #availability + } + }; + quote! { + #func_doc + #func_decl; + } + }); + + quote! { + #( #docs )* + } +} diff --git a/substrate/frame/revive/src/address.rs b/substrate/frame/revive/src/address.rs new file mode 100644 index 000000000000..5758daf7b1ff --- /dev/null +++ b/substrate/frame/revive/src/address.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. + +//! Functions that deal with address derivation. + +use crate::{CodeHash, Config}; +use codec::{Decode, Encode}; +use sp_runtime::traits::{Hash, TrailingZeroInput}; + +/// Provides the contract address generation method. +/// +/// See [`DefaultAddressGenerator`] for the default implementation. +/// +/// # Note for implementors +/// +/// 1. Make sure that there are no collisions, different inputs never lead to the same output. +/// 2. Make sure that the same inputs lead to the same output. +pub trait AddressGenerator { + /// The address of a contract based on the given instantiate parameters. + /// + /// Changing the formular for an already deployed chain is fine as long as no collisions + /// with the old formular. Changes only affect existing contracts. + fn contract_address( + deploying_address: &T::AccountId, + code_hash: &CodeHash, + input_data: &[u8], + salt: &[u8], + ) -> T::AccountId; +} + +/// Default address generator. +/// +/// This is the default address generator used by contract instantiation. Its result +/// is only dependent on its inputs. It can therefore be used to reliably predict the +/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There +/// is no CREATE equivalent because CREATE2 is strictly more powerful. +/// Formula: +/// `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)` +pub struct DefaultAddressGenerator; + +impl AddressGenerator for DefaultAddressGenerator { + /// Formula: `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)` + fn contract_address( + deploying_address: &T::AccountId, + code_hash: &CodeHash, + input_data: &[u8], + salt: &[u8], + ) -> T::AccountId { + let entropy = (b"contract_addr_v1", deploying_address, code_hash, input_data, salt) + .using_encoded(T::Hashing::hash); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + } +} diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs new file mode 100644 index 000000000000..654ba3de4f72 --- /dev/null +++ b/substrate/frame/revive/src/benchmarking/call_builder.rs @@ -0,0 +1,217 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + benchmarking::{default_deposit_limit, Contract, WasmModule}, + exec::{ExportedFunction, Ext, Key, Stack}, + storage::meter::Meter, + transient_storage::MeterEntry, + wasm::{ApiVersion, PreparedCall, Runtime}, + BalanceOf, Config, DebugBuffer, Error, GasMeter, Origin, TypeInfo, WasmBlob, Weight, +}; +use alloc::{vec, vec::Vec}; +use codec::{Encode, HasCompact}; +use core::fmt::Debug; +use frame_benchmarking::benchmarking; + +type StackExt<'a, T> = Stack<'a, T, WasmBlob>; + +/// A builder used to prepare a contract call. +pub struct CallSetup { + contract: Contract, + dest: T::AccountId, + origin: Origin, + gas_meter: GasMeter, + storage_meter: Meter, + value: BalanceOf, + debug_message: Option, + data: Vec, + transient_storage_size: u32, +} + +impl Default for CallSetup +where + T: Config + pallet_balances::Config, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, +{ + fn default() -> Self { + Self::new(WasmModule::dummy()) + } +} + +impl CallSetup +where + T: Config + pallet_balances::Config, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, +{ + /// Setup a new call for the given module. + pub fn new(module: WasmModule) -> Self { + let contract = Contract::::new(module.clone(), vec![]).unwrap(); + let dest = contract.account_id.clone(); + let origin = Origin::from_account_id(contract.caller.clone()); + + let storage_meter = Meter::new(&origin, default_deposit_limit::(), 0u32.into()).unwrap(); + + // Whitelist contract account, as it is already accounted for in the call benchmark + benchmarking::add_to_whitelist( + frame_system::Account::::hashed_key_for(&contract.account_id).into(), + ); + + // Whitelist the contract's contractInfo as it is already accounted for in the call + // benchmark + benchmarking::add_to_whitelist( + crate::ContractInfoOf::::hashed_key_for(&contract.account_id).into(), + ); + + Self { + contract, + dest, + origin, + gas_meter: GasMeter::new(Weight::MAX), + storage_meter, + value: 0u32.into(), + debug_message: None, + data: vec![], + transient_storage_size: 0, + } + } + + /// Set the meter's storage deposit limit. + pub fn set_storage_deposit_limit(&mut self, balance: BalanceOf) { + self.storage_meter = Meter::new(&self.origin, balance, 0u32.into()).unwrap(); + } + + /// Set the call's origin. + pub fn set_origin(&mut self, origin: Origin) { + self.origin = origin; + } + + /// Set the contract's balance. + pub fn set_balance(&mut self, value: BalanceOf) { + self.contract.set_balance(value); + } + + /// Set the call's input data. + pub fn set_data(&mut self, value: Vec) { + self.data = value; + } + + /// Set the transient storage size. + pub fn set_transient_storage_size(&mut self, size: u32) { + self.transient_storage_size = size; + } + + /// Set the debug message. + pub fn enable_debug_message(&mut self) { + self.debug_message = Some(Default::default()); + } + + /// Get the debug message. + pub fn debug_message(&self) -> Option { + self.debug_message.clone() + } + + /// Get the call's input data. + pub fn data(&self) -> Vec { + self.data.clone() + } + + /// Get the call's contract. + pub fn contract(&self) -> Contract { + self.contract.clone() + } + + /// Build the call stack. + pub fn ext(&mut self) -> (StackExt<'_, T>, WasmBlob) { + let mut ext = StackExt::bench_new_call( + self.dest.clone(), + self.origin.clone(), + &mut self.gas_meter, + &mut self.storage_meter, + self.value, + self.debug_message.as_mut(), + ); + if self.transient_storage_size > 0 { + Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap(); + } + ext + } + + /// Prepare a call to the module. + pub fn prepare_call<'a>( + ext: &'a mut StackExt<'a, T>, + module: WasmBlob, + input: Vec, + ) -> PreparedCall<'a, StackExt<'a, T>> { + module + .prepare_call( + Runtime::new(ext, input), + ExportedFunction::Call, + ApiVersion::UnsafeNewest, + ) + .unwrap() + } + + /// Add transient_storage + fn with_transient_storage(ext: &mut StackExt, size: u32) -> Result<(), &'static str> { + let &MeterEntry { amount, limit } = ext.transient_storage().meter().current(); + ext.transient_storage().meter().current_mut().limit = size; + for i in 1u32.. { + let mut key_data = i.to_le_bytes().to_vec(); + while key_data.last() == Some(&0) { + key_data.pop(); + } + let key = Key::try_from_var(key_data).unwrap(); + if let Err(e) = ext.set_transient_storage(&key, Some(Vec::new()), false) { + // Restore previous settings. + ext.transient_storage().meter().current_mut().limit = limit; + ext.transient_storage().meter().current_mut().amount = amount; + if e == Error::::OutOfTransientStorage.into() { + break; + } else { + return Err("Initialization of the transient storage failed"); + } + } + } + Ok(()) + } +} + +#[macro_export] +macro_rules! memory( + ($($bytes:expr,)*) => {{ + vec![].iter()$(.chain($bytes.iter()))*.cloned().collect::>() + }}; +); + +#[macro_export] +macro_rules! build_runtime( + ($runtime:ident, $memory:ident: [$($segment:expr,)*]) => { + $crate::build_runtime!($runtime, _contract, $memory: [$($segment,)*]); + }; + ($runtime:ident, $contract:ident, $memory:ident: [$($bytes:expr,)*]) => { + $crate::build_runtime!($runtime, $contract); + let mut $memory = $crate::memory!($($bytes,)*); + }; + ($runtime:ident, $contract:ident) => { + let mut setup = CallSetup::::default(); + let $contract = setup.contract(); + let input = setup.data(); + let (mut ext, _) = setup.ext(); + let mut $runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, input); + }; +); diff --git a/substrate/frame/revive/src/benchmarking/code.rs b/substrate/frame/revive/src/benchmarking/code.rs new file mode 100644 index 000000000000..eba4710d8a2c --- /dev/null +++ b/substrate/frame/revive/src/benchmarking/code.rs @@ -0,0 +1,69 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Functions to procedurally construct contract code used for benchmarking. +//! +//! In order to be able to benchmark events that are triggered by contract execution +//! (API calls into seal, individual instructions), we need to generate contracts that +//! perform those events. Because those contracts can get very big we cannot simply define +//! them as text (.wat) as this will be too slow and consume too much memory. Therefore +//! we define this simple definition of a contract that can be passed to `create_code` that +//! compiles it down into a `WasmModule` that can be used as a contract's code. + +use crate::Config; +use alloc::vec::Vec; +use pallet_revive_fixtures::bench as bench_fixtures; +use sp_runtime::traits::Hash; + +/// A wasm module ready to be put on chain. +#[derive(Clone)] +pub struct WasmModule { + pub code: Vec, + pub hash: ::Output, +} + +impl WasmModule { + /// Return a contract code that does nothing. + pub fn dummy() -> Self { + Self::new(bench_fixtures::DUMMY.to_vec()) + } + + /// Same as [`Self::dummy`] but uses `replace_with` to make the code unique. + pub fn dummy_unique(replace_with: u32) -> Self { + Self::new(bench_fixtures::dummy_unique(replace_with)) + } + + /// A contract code of specified sizte that does nothing. + pub fn sized(_size: u32) -> Self { + Self::dummy() + } + + /// A contract code that calls the "noop" host function in a loop depending in the input. + pub fn noop() -> Self { + Self::new(bench_fixtures::NOOP.to_vec()) + } + + /// A contract code that executes some ALU instructions in a loop. + pub fn instr() -> Self { + Self::new(bench_fixtures::INSTR.to_vec()) + } + + fn new(code: Vec) -> Self { + let hash = T::Hashing::hash(&code); + Self { code, hash } + } +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs new file mode 100644 index 000000000000..b4bd028d6f03 --- /dev/null +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -0,0 +1,1815 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarks for the contracts pallet + +#![cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] + +mod call_builder; +mod code; +use self::{call_builder::CallSetup, code::WasmModule}; +use crate::{ + exec::{Key, MomentOf}, + limits, + migration::codegen::LATEST_MIGRATION_VERSION, + storage::WriteOutcome, + Pallet as Contracts, *, +}; +use alloc::{vec, vec::Vec}; +use codec::{Encode, MaxEncodedLen}; +use frame_benchmarking::v2::*; +use frame_support::{ + self, assert_ok, + pallet_prelude::StorageVersion, + storage::child, + traits::{fungible::InspectHold, Currency}, + weights::{Weight, WeightMeter}, +}; +use frame_system::RawOrigin; +use pallet_balances; +use pallet_revive_uapi::{CallFlags, ReturnErrorCode, StorageFlags}; +use sp_runtime::traits::{Bounded, Hash}; + +/// How many runs we do per API benchmark. +/// +/// This is picked more or less arbitrary. We experimented with different numbers until +/// the results appeared to be stable. Reducing the number would speed up the benchmarks +/// but might make the results less precise. +const API_BENCHMARK_RUNS: u32 = 1600; + +/// How many runs we do per instruction benchmark. +/// +/// Same rationale as for [`API_BENCHMARK_RUNS`]. The number is bigger because instruction +/// benchmarks are faster. +const INSTR_BENCHMARK_RUNS: u32 = 5000; + +/// Number of layers in a Radix16 unbalanced trie. +const UNBALANCED_TRIE_LAYERS: u32 = 20; + +/// An instantiated and deployed contract. +#[derive(Clone)] +struct Contract { + caller: T::AccountId, + account_id: T::AccountId, + addr: AccountIdLookupOf, +} + +impl Contract +where + T: Config + pallet_balances::Config, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, +{ + /// Create new contract and use a default account id as instantiator. + fn new(module: WasmModule, data: Vec) -> Result, &'static str> { + Self::with_index(0, module, data) + } + + /// Create new contract and use an account id derived from the supplied index as instantiator. + fn with_index( + index: u32, + module: WasmModule, + data: Vec, + ) -> Result, &'static str> { + Self::with_caller(account("instantiator", index, 0), module, data) + } + + /// Create new contract and use the supplied `caller` as instantiator. + fn with_caller( + caller: T::AccountId, + module: WasmModule, + data: Vec, + ) -> Result, &'static str> { + T::Currency::set_balance(&caller, caller_funding::()); + let salt = vec![0xff]; + + let outcome = Contracts::::bare_instantiate( + RawOrigin::Signed(caller.clone()).into(), + 0u32.into(), + Weight::MAX, + default_deposit_limit::(), + Code::Upload(module.code), + data, + salt, + DebugInfo::Skip, + CollectEvents::Skip, + ); + + let addr = outcome.result?.account_id; + let result = Contract { caller, account_id: addr.clone(), addr: T::Lookup::unlookup(addr) }; + + ContractInfoOf::::insert(&result.account_id, result.info()?); + + Ok(result) + } + + /// Create a new contract with the supplied storage item count and size each. + fn with_storage( + code: WasmModule, + stor_num: u32, + stor_size: u32, + ) -> Result { + let contract = Contract::::new(code, vec![])?; + let storage_items = (0..stor_num) + .map(|i| { + let hash = T::Hashing::hash_of(&i) + .as_ref() + .try_into() + .map_err(|_| "Hash too big for storage key")?; + Ok((hash, vec![42u8; stor_size as usize])) + }) + .collect::, &'static str>>()?; + contract.store(&storage_items)?; + Ok(contract) + } + + /// Store the supplied storage items into this contracts storage. + fn store(&self, items: &Vec<([u8; 32], Vec)>) -> Result<(), &'static str> { + let info = self.info()?; + for item in items { + info.write(&Key::Fix(item.0), Some(item.1.clone()), None, false) + .map_err(|_| "Failed to write storage to restoration dest")?; + } + >::insert(&self.account_id, info); + Ok(()) + } + + /// Create a new contract with the specified unbalanced storage trie. + fn with_unbalanced_storage_trie(code: WasmModule, key: &[u8]) -> Result { + if (key.len() as u32) < (UNBALANCED_TRIE_LAYERS + 1) / 2 { + return Err("Key size too small to create the specified trie"); + } + + let value = vec![16u8; limits::PAYLOAD_BYTES as usize]; + let contract = Contract::::new(code, vec![])?; + let info = contract.info()?; + let child_trie_info = info.child_trie_info(); + child::put_raw(&child_trie_info, &key, &value); + for l in 0..UNBALANCED_TRIE_LAYERS { + let pos = l as usize / 2; + let mut key_new = key.to_vec(); + for i in 0u8..16 { + key_new[pos] = if l % 2 == 0 { + (key_new[pos] & 0xF0) | i + } else { + (key_new[pos] & 0x0F) | (i << 4) + }; + + if key == &key_new { + continue + } + child::put_raw(&child_trie_info, &key_new, &value); + } + } + Ok(contract) + } + + /// Get the `ContractInfo` of the `addr` or an error if it no longer exists. + fn address_info(addr: &T::AccountId) -> Result, &'static str> { + ContractInfoOf::::get(addr).ok_or("Expected contract to exist at this point.") + } + + /// Get the `ContractInfo` of this contract or an error if it no longer exists. + fn info(&self) -> Result, &'static str> { + Self::address_info(&self.account_id) + } + + /// Set the balance of the contract to the supplied amount. + fn set_balance(&self, balance: BalanceOf) { + T::Currency::set_balance(&self.account_id, balance); + } + + /// Returns `true` iff all storage entries related to code storage exist. + fn code_exists(hash: &CodeHash) -> bool { + >::contains_key(hash) && >::contains_key(&hash) + } + + /// Returns `true` iff no storage entry related to code storage exist. + fn code_removed(hash: &CodeHash) -> bool { + !>::contains_key(hash) && !>::contains_key(&hash) + } +} + +/// The funding that each account that either calls or instantiates contracts is funded with. +fn caller_funding() -> BalanceOf { + // Minting can overflow, so we can't abuse of the funding. This value happens to be big enough, + // but not too big to make the total supply overflow. + BalanceOf::::max_value() / 10_000u32.into() +} + +/// The deposit limit we use for benchmarks. +fn default_deposit_limit() -> BalanceOf { + (T::DepositPerByte::get() * 1024u32.into() * 1024u32.into()) + + T::DepositPerItem::get() * 1024u32.into() +} + +#[benchmarks( + where + as codec::HasCompact>::Type: Clone + Eq + PartialEq + core::fmt::Debug + scale_info::TypeInfo + codec::Encode, + T: Config + pallet_balances::Config, + BalanceOf: From< as Currency>::Balance>, + as Currency>::Balance: From>, +)] +mod benchmarks { + use super::*; + + // The base weight consumed on processing contracts deletion queue. + #[benchmark(pov_mode = Measured)] + fn on_process_deletion_queue_batch() { + #[block] + { + ContractInfo::::process_deletion_queue_batch(&mut WeightMeter::new()) + } + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn on_initialize_per_trie_key(k: Linear<0, 1024>) -> Result<(), BenchmarkError> { + let instance = Contract::::with_storage(WasmModule::dummy(), k, limits::PAYLOAD_BYTES)?; + instance.info()?.queue_trie_for_deletion(); + + #[block] + { + ContractInfo::::process_deletion_queue_batch(&mut WeightMeter::new()) + } + + Ok(()) + } + + // This benchmarks the weight of executing Migration::migrate to execute a noop migration. + #[benchmark(pov_mode = Measured)] + fn migration_noop() { + let version = LATEST_MIGRATION_VERSION; + StorageVersion::new(version).put::>(); + #[block] + { + Migration::::migrate(&mut WeightMeter::new()); + } + assert_eq!(StorageVersion::get::>(), version); + } + + // This benchmarks the weight of dispatching migrate to execute 1 `NoopMigration` + #[benchmark(pov_mode = Measured)] + fn migrate() { + let latest_version = LATEST_MIGRATION_VERSION; + StorageVersion::new(latest_version - 2).put::>(); + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); + + #[extrinsic_call] + _(RawOrigin::Signed(whitelisted_caller()), Weight::MAX); + + assert_eq!(StorageVersion::get::>(), latest_version - 1); + } + + // This benchmarks the weight of running on_runtime_upgrade when there are no migration in + // progress. + #[benchmark(pov_mode = Measured)] + fn on_runtime_upgrade_noop() { + let latest_version = LATEST_MIGRATION_VERSION; + StorageVersion::new(latest_version).put::>(); + #[block] + { + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); + } + assert!(MigrationInProgress::::get().is_none()); + } + + // This benchmarks the weight of running on_runtime_upgrade when there is a migration in + // progress. + #[benchmark(pov_mode = Measured)] + fn on_runtime_upgrade_in_progress() { + let latest_version = LATEST_MIGRATION_VERSION; + StorageVersion::new(latest_version - 2).put::>(); + let v = vec![42u8].try_into().ok(); + MigrationInProgress::::set(v.clone()); + #[block] + { + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); + } + assert!(MigrationInProgress::::get().is_some()); + assert_eq!(MigrationInProgress::::get(), v); + } + + // This benchmarks the weight of running on_runtime_upgrade when there is a migration to + // process. + #[benchmark(pov_mode = Measured)] + fn on_runtime_upgrade() { + let latest_version = LATEST_MIGRATION_VERSION; + StorageVersion::new(latest_version - 2).put::>(); + #[block] + { + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); + } + assert!(MigrationInProgress::::get().is_some()); + } + + // This benchmarks the overhead of loading a code of size `c` byte from storage and into + // the execution engine. This does **not** include the actual execution for which the gas meter + // is responsible. This is achieved by generating all code to the `deploy` function + // which is in the wasm module but not executed on `call`. + // The results are supposed to be used as `call_with_code_per_byte(c) - + // call_with_code_per_byte(0)`. + #[benchmark(pov_mode = Measured)] + fn call_with_code_per_byte( + c: Linear<0, { T::MaxCodeLen::get() }>, + ) -> Result<(), BenchmarkError> { + let instance = + Contract::::with_caller(whitelisted_caller(), WasmModule::sized(c), vec![])?; + let value = Pallet::::min_balance(); + let callee = instance.addr; + let storage_deposit = default_deposit_limit::(); + + #[extrinsic_call] + call( + RawOrigin::Signed(instance.caller.clone()), + callee, + value, + Weight::MAX, + storage_deposit, + vec![], + ); + + Ok(()) + } + + // `c`: Size of the code in bytes. + // `i`: Size of the input in bytes. + // `s`: Size of the salt in bytes. + #[benchmark(pov_mode = Measured)] + fn instantiate_with_code( + c: Linear<0, { T::MaxCodeLen::get() }>, + i: Linear<0, { limits::MEMORY_BYTES }>, + s: Linear<0, { limits::MEMORY_BYTES }>, + ) { + let input = vec![42u8; i as usize]; + let salt = vec![42u8; s as usize]; + let value = Pallet::::min_balance(); + let caller = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + let WasmModule { code, hash, .. } = WasmModule::::sized(c); + let origin = RawOrigin::Signed(caller.clone()); + let addr = Contracts::::contract_address(&caller, &hash, &input, &salt); + let storage_deposit = default_deposit_limit::(); + #[extrinsic_call] + _(origin, value, Weight::MAX, storage_deposit, code, input, salt); + + let deposit = + T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr); + // uploading the code reserves some balance in the callers account + let code_deposit = + T::Currency::balance_on_hold(&HoldReason::CodeUploadDepositReserve.into(), &caller); + assert_eq!( + T::Currency::balance(&caller), + caller_funding::() - value - deposit - code_deposit - Pallet::::min_balance(), + ); + // contract has the full value + assert_eq!(T::Currency::balance(&addr), value + Pallet::::min_balance()); + } + + // `i`: Size of the input in bytes. + // `s`: Size of the salt in bytes. + #[benchmark(pov_mode = Measured)] + fn instantiate( + i: Linear<0, { limits::MEMORY_BYTES }>, + s: Linear<0, { limits::MEMORY_BYTES }>, + ) -> Result<(), BenchmarkError> { + let input = vec![42u8; i as usize]; + let salt = vec![42u8; s as usize]; + let value = Pallet::::min_balance(); + let caller = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + let origin = RawOrigin::Signed(caller.clone()); + let WasmModule { code, .. } = WasmModule::::dummy(); + let storage_deposit = default_deposit_limit::(); + let hash = + Contracts::::bare_upload_code(origin.into(), code, storage_deposit)?.code_hash; + let addr = Contracts::::contract_address(&caller, &hash, &input, &salt); + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + value, + Weight::MAX, + storage_deposit, + hash, + input, + salt, + ); + + let deposit = + T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr); + let code_deposit = + T::Currency::balance_on_hold(&HoldReason::CodeUploadDepositReserve.into(), &caller); + // value was removed from the caller + assert_eq!( + T::Currency::balance(&caller), + caller_funding::() - value - deposit - code_deposit - Pallet::::min_balance(), + ); + // contract has the full value + assert_eq!(T::Currency::balance(&addr), value + Pallet::::min_balance()); + + Ok(()) + } + + // We just call a dummy contract to measure the overhead of the call extrinsic. + // The size of the data has no influence on the costs of this extrinsic as long as the contract + // won't call `seal_input` in its constructor to copy the data to contract memory. + // The dummy contract used here does not do this. The costs for the data copy is billed as + // part of `seal_input`. The costs for invoking a contract of a specific size are not part + // of this benchmark because we cannot know the size of the contract when issuing a call + // transaction. See `call_with_code_per_byte` for this. + #[benchmark(pov_mode = Measured)] + fn call() -> Result<(), BenchmarkError> { + let data = vec![42u8; 1024]; + let instance = + Contract::::with_caller(whitelisted_caller(), WasmModule::dummy(), vec![])?; + let value = Pallet::::min_balance(); + let origin = RawOrigin::Signed(instance.caller.clone()); + let callee = instance.addr.clone(); + let before = T::Currency::balance(&instance.account_id); + let storage_deposit = default_deposit_limit::(); + #[extrinsic_call] + _(origin, callee, value, Weight::MAX, storage_deposit, data); + let deposit = T::Currency::balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &instance.account_id, + ); + let code_deposit = T::Currency::balance_on_hold( + &HoldReason::CodeUploadDepositReserve.into(), + &instance.caller, + ); + // value and value transferred via call should be removed from the caller + assert_eq!( + T::Currency::balance(&instance.caller), + caller_funding::() - value - deposit - code_deposit - Pallet::::min_balance(), + ); + // contract should have received the value + assert_eq!(T::Currency::balance(&instance.account_id), before + value); + // contract should still exist + instance.info()?; + + Ok(()) + } + + // This constructs a contract that is maximal expensive to instrument. + // It creates a maximum number of metering blocks per byte. + // `c`: Size of the code in bytes. + #[benchmark(pov_mode = Measured)] + fn upload_code(c: Linear<0, { T::MaxCodeLen::get() }>) { + let caller = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + let WasmModule { code, hash, .. } = WasmModule::::sized(c); + let origin = RawOrigin::Signed(caller.clone()); + let storage_deposit = default_deposit_limit::(); + #[extrinsic_call] + _(origin, code, storage_deposit); + // uploading the code reserves some balance in the callers account + assert!(T::Currency::total_balance_on_hold(&caller) > 0u32.into()); + assert!(>::code_exists(&hash)); + } + + // Removing code does not depend on the size of the contract because all the information + // needed to verify the removal claim (refcount, owner) is stored in a separate storage + // item (`CodeInfoOf`). + #[benchmark(pov_mode = Measured)] + fn remove_code() -> Result<(), BenchmarkError> { + let caller = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + let WasmModule { code, hash, .. } = WasmModule::::dummy(); + let origin = RawOrigin::Signed(caller.clone()); + let storage_deposit = default_deposit_limit::(); + let uploaded = + >::bare_upload_code(origin.clone().into(), code, storage_deposit)?; + assert_eq!(uploaded.code_hash, hash); + assert_eq!(uploaded.deposit, T::Currency::total_balance_on_hold(&caller)); + assert!(>::code_exists(&hash)); + #[extrinsic_call] + _(origin, hash); + // removing the code should have unreserved the deposit + assert_eq!(T::Currency::total_balance_on_hold(&caller), 0u32.into()); + assert!(>::code_removed(&hash)); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn set_code() -> Result<(), BenchmarkError> { + let instance = + >::with_caller(whitelisted_caller(), WasmModule::dummy(), vec![])?; + // we just add some bytes so that the code hash is different + let WasmModule { code, .. } = >::dummy_unique(128); + let origin = RawOrigin::Signed(instance.caller.clone()); + let storage_deposit = default_deposit_limit::(); + let hash = + >::bare_upload_code(origin.into(), code, storage_deposit)?.code_hash; + let callee = instance.addr.clone(); + assert_ne!(instance.info()?.code_hash, hash); + #[extrinsic_call] + _(RawOrigin::Root, callee, hash); + assert_eq!(instance.info()?.code_hash, hash); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn noop_host_fn(r: Linear<0, API_BENCHMARK_RUNS>) { + let mut setup = CallSetup::::new(WasmModule::noop()); + let (mut ext, module) = setup.ext(); + let prepared = CallSetup::::prepare_call(&mut ext, module, r.encode()); + #[block] + { + prepared.call().unwrap(); + } + } + + #[benchmark(pov_mode = Measured)] + fn seal_caller() { + let len = ::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + + let result; + #[block] + { + result = runtime.bench_caller(memory.as_mut_slice(), 4, 0); + } + + assert_ok!(result); + assert_eq!( + &::decode(&mut &memory[4..]).unwrap(), + runtime.ext().caller().account_id().unwrap() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_is_contract() { + let Contract { account_id, .. } = + Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); + + build_runtime!(runtime, memory: [account_id.encode(), ]); + + let result; + #[block] + { + result = runtime.bench_is_contract(memory.as_mut_slice(), 0); + } + + assert_eq!(result.unwrap(), 1); + } + + #[benchmark(pov_mode = Measured)] + fn seal_code_hash() { + let contract = Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); + let len = as MaxEncodedLen>::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], contract.account_id.encode(), ]); + + let result; + #[block] + { + result = runtime.bench_code_hash(memory.as_mut_slice(), 4 + len, 4, 0); + } + + assert_ok!(result); + assert_eq!( + as Decode>::decode(&mut &memory[4..]).unwrap(), + contract.info().unwrap().code_hash + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_own_code_hash() { + let len = as MaxEncodedLen>::max_encoded_len() as u32; + build_runtime!(runtime, contract, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_own_code_hash(memory.as_mut_slice(), 4, 0); + } + + assert_ok!(result); + assert_eq!( + as Decode>::decode(&mut &memory[4..]).unwrap(), + contract.info().unwrap().code_hash + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_caller_is_origin() { + build_runtime!(runtime, memory: []); + + let result; + #[block] + { + result = runtime.bench_caller_is_origin(memory.as_mut_slice()); + } + assert_eq!(result.unwrap(), 1u32); + } + + #[benchmark(pov_mode = Measured)] + fn seal_caller_is_root() { + let mut setup = CallSetup::::default(); + setup.set_origin(Origin::Root); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![]); + + let result; + #[block] + { + result = runtime.bench_caller_is_root([0u8; 0].as_mut_slice()); + } + assert_eq!(result.unwrap(), 1u32); + } + + #[benchmark(pov_mode = Measured)] + fn seal_address() { + let len = as MaxEncodedLen>::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + + let result; + #[block] + { + result = runtime.bench_address(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + &::decode(&mut &memory[4..]).unwrap(), + runtime.ext().address() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_weight_left() { + // use correct max_encoded_len when new version of parity-scale-codec is released + let len = 18u32; + assert!(::max_encoded_len() as u32 != len); + build_runtime!(runtime, memory: [32u32.to_le_bytes(), vec![0u8; len as _], ]); + + let result; + #[block] + { + result = runtime.bench_weight_left(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + ::decode(&mut &memory[4..]).unwrap(), + runtime.ext().gas_meter().gas_left() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_balance() { + let len = ::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_balance(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + ::decode(&mut &memory[4..]).unwrap(), + runtime.ext().balance().into() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_value_transferred() { + let len = ::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_value_transferred(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + ::decode(&mut &memory[4..]).unwrap(), + runtime.ext().value_transferred().into() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_minimum_balance() { + let len = ::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_minimum_balance(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + ::decode(&mut &memory[4..]).unwrap(), + runtime.ext().minimum_balance().into() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_block_number() { + let len = as MaxEncodedLen>::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_block_number(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!( + >::decode(&mut &memory[4..]).unwrap(), + runtime.ext().block_number() + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_now() { + let len = as MaxEncodedLen>::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let result; + #[block] + { + result = runtime.bench_now(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!(>::decode(&mut &memory[4..]).unwrap(), *runtime.ext().now()); + } + + #[benchmark(pov_mode = Measured)] + fn seal_weight_to_fee() { + let len = ::max_encoded_len() as u32; + build_runtime!(runtime, memory: [len.to_le_bytes(), vec![0u8; len as _], ]); + let weight = Weight::from_parts(500_000, 300_000); + let result; + #[block] + { + result = runtime.bench_weight_to_fee( + memory.as_mut_slice(), + weight.ref_time(), + weight.proof_size(), + 4, + 0, + ); + } + assert_ok!(result); + assert_eq!( + >::decode(&mut &memory[4..]).unwrap(), + runtime.ext().get_weight_price(weight) + ); + } + + #[benchmark(pov_mode = Measured)] + fn seal_input(n: Linear<0, { limits::MEMORY_BYTES - 4 }>) { + let mut setup = CallSetup::::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![42u8; n as usize]); + let mut memory = memory!(n.to_le_bytes(), vec![0u8; n as usize],); + let result; + #[block] + { + result = runtime.bench_input(memory.as_mut_slice(), 4, 0); + } + assert_ok!(result); + assert_eq!(&memory[4..], &vec![42u8; n as usize]); + } + + #[benchmark(pov_mode = Measured)] + fn seal_return(n: Linear<0, { limits::MEMORY_BYTES - 4 }>) { + build_runtime!(runtime, memory: [n.to_le_bytes(), vec![42u8; n as usize], ]); + + let result; + #[block] + { + result = runtime.bench_seal_return(memory.as_mut_slice(), 0, 0, n); + } + + assert!(matches!( + result, + Err(crate::wasm::TrapReason::Return(crate::wasm::ReturnData { .. })) + )); + } + + #[benchmark(pov_mode = Measured)] + fn seal_terminate( + n: Linear<0, { limits::DELEGATE_DEPENDENCIES }>, + ) -> Result<(), BenchmarkError> { + let beneficiary = account::("beneficiary", 0, 0); + let caller = whitelisted_caller(); + T::Currency::set_balance(&caller, caller_funding::()); + let origin = RawOrigin::Signed(caller); + let storage_deposit = default_deposit_limit::(); + + build_runtime!(runtime, memory: [beneficiary.encode(),]); + + (0..n).for_each(|i| { + let new_code = WasmModule::::dummy_unique(65 + i); + Contracts::::bare_upload_code(origin.clone().into(), new_code.code, storage_deposit) + .unwrap(); + runtime.ext().lock_delegate_dependency(new_code.hash).unwrap(); + }); + + let result; + #[block] + { + result = runtime.bench_terminate(memory.as_mut_slice(), 0); + } + + assert!(matches!(result, Err(crate::wasm::TrapReason::Termination))); + + Ok(()) + } + + // Benchmark the overhead that topics generate. + // `t`: Number of topics + // `n`: Size of event payload in bytes + #[benchmark(pov_mode = Measured)] + fn seal_deposit_event( + t: Linear<0, { limits::NUM_EVENT_TOPICS as u32 }>, + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) { + let topics = (0..t).map(|i| T::Hashing::hash_of(&i)).collect::>().encode(); + let topics_len = topics.len() as u32; + + build_runtime!(runtime, memory: [ + n.to_le_bytes(), + topics, + vec![0u8; n as _], + ]); + + let result; + #[block] + { + result = runtime.bench_deposit_event( + memory.as_mut_slice(), + 4, // topics_ptr + topics_len, // topics_len + 4 + topics_len, // data_ptr + 0, // data_len + ); + } + + assert_ok!(result); + } + + // Benchmark debug_message call + // Whereas this function is used in RPC mode only, it still should be secured + // against an excessive use. + // + // i: size of input in bytes up to maximum allowed contract memory or maximum allowed debug + // buffer size, whichever is less. + #[benchmark] + fn seal_debug_message( + i: Linear<0, { (limits::MEMORY_BYTES).min(limits::DEBUG_BUFFER_BYTES) }>, + ) { + let mut setup = CallSetup::::default(); + setup.enable_debug_message(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + // Fill memory with printable ASCII bytes. + let mut memory = (0..i).zip((32..127).cycle()).map(|i| i.1).collect::>(); + + let result; + #[block] + { + result = runtime.bench_debug_message(memory.as_mut_slice(), 0, i); + } + assert_ok!(result); + assert_eq!(setup.debug_message().unwrap().len() as u32, i); + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn get_storage_empty() -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = vec![0u8; max_key_len as usize]; + let max_value_len = limits::PAYLOAD_BYTES as usize; + let value = vec![1u8; max_value_len]; + + let instance = Contract::::new(WasmModule::dummy(), vec![])?; + let info = instance.info()?; + let child_trie_info = info.child_trie_info(); + info.bench_write_raw(&key, Some(value.clone()), false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let result; + #[block] + { + result = child::get_raw(&child_trie_info, &key); + } + + assert_eq!(result, Some(value)); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn get_storage_full() -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = vec![0u8; max_key_len as usize]; + let max_value_len = limits::PAYLOAD_BYTES; + let value = vec![1u8; max_value_len as usize]; + + let instance = Contract::::with_unbalanced_storage_trie(WasmModule::dummy(), &key)?; + let info = instance.info()?; + let child_trie_info = info.child_trie_info(); + info.bench_write_raw(&key, Some(value.clone()), false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let result; + #[block] + { + result = child::get_raw(&child_trie_info, &key); + } + + assert_eq!(result, Some(value)); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn set_storage_empty() -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = vec![0u8; max_key_len as usize]; + let max_value_len = limits::PAYLOAD_BYTES as usize; + let value = vec![1u8; max_value_len]; + + let instance = Contract::::new(WasmModule::dummy(), vec![])?; + let info = instance.info()?; + let child_trie_info = info.child_trie_info(); + info.bench_write_raw(&key, Some(vec![42u8; max_value_len]), false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let val = Some(value.clone()); + let result; + #[block] + { + result = info.bench_write_raw(&key, val, true); + } + + assert_ok!(result); + assert_eq!(child::get_raw(&child_trie_info, &key).unwrap(), value); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn set_storage_full() -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = vec![0u8; max_key_len as usize]; + let max_value_len = limits::PAYLOAD_BYTES; + let value = vec![1u8; max_value_len as usize]; + + let instance = Contract::::with_unbalanced_storage_trie(WasmModule::dummy(), &key)?; + let info = instance.info()?; + let child_trie_info = info.child_trie_info(); + info.bench_write_raw(&key, Some(vec![42u8; max_value_len as usize]), false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let val = Some(value.clone()); + let result; + #[block] + { + result = info.bench_write_raw(&key, val, true); + } + + assert_ok!(result); + assert_eq!(child::get_raw(&child_trie_info, &key).unwrap(), value); + Ok(()) + } + + // n: new byte size + // o: old byte size + #[benchmark(skip_meta, pov_mode = Measured)] + fn seal_set_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + o: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + let value = vec![1u8; n as usize]; + + build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]); + let info = instance.info()?; + + info.write(&key, Some(vec![42u8; o as usize]), None, false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_set_storage( + memory.as_mut_slice(), + StorageFlags::empty().bits(), + 0, // key_ptr + max_key_len, // key_len + max_key_len, // value_ptr + n, // value_len + ); + } + + assert_ok!(result); + assert_eq!(info.read(&key).unwrap(), value); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn seal_clear_storage(n: Linear<0, { limits::PAYLOAD_BYTES }>) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, instance, memory: [ key.unhashed(), ]); + let info = instance.info()?; + + info.write(&key, Some(vec![42u8; n as usize]), None, false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_clear_storage( + memory.as_mut_slice(), + StorageFlags::empty().bits(), + 0, + max_key_len, + ); + } + + assert_ok!(result); + assert!(info.read(&key).is_none()); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn seal_get_storage(n: Linear<0, { limits::PAYLOAD_BYTES }>) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]); + let info = instance.info()?; + + info.write(&key, Some(vec![42u8; n as usize]), None, false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let out_ptr = max_key_len + 4; + let result; + #[block] + { + result = runtime.bench_get_storage( + memory.as_mut_slice(), + StorageFlags::empty().bits(), + 0, // key_ptr + max_key_len, // key_len + out_ptr, // out_ptr + max_key_len, // out_len_ptr + ); + } + + assert_ok!(result); + assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn seal_contains_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, instance, memory: [ key.unhashed(), ]); + let info = instance.info()?; + + info.write(&key, Some(vec![42u8; n as usize]), None, false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_contains_storage( + memory.as_mut_slice(), + StorageFlags::empty().bits(), + 0, + max_key_len, + ); + } + + assert_eq!(result.unwrap(), n); + Ok(()) + } + + #[benchmark(skip_meta, pov_mode = Measured)] + fn seal_take_storage(n: Linear<0, { limits::PAYLOAD_BYTES }>) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]); + let info = instance.info()?; + + let value = vec![42u8; n as usize]; + info.write(&key, Some(value.clone()), None, false) + .map_err(|_| "Failed to write to storage during setup.")?; + + let out_ptr = max_key_len + 4; + let result; + #[block] + { + result = runtime.bench_take_storage( + memory.as_mut_slice(), + StorageFlags::empty().bits(), + 0, // key_ptr + max_key_len, // key_len + out_ptr, // out_ptr + max_key_len, // out_len_ptr + ); + } + + assert_ok!(result); + assert!(&info.read(&key).is_none()); + assert_eq!(&value, &memory[out_ptr as usize..]); + Ok(()) + } + + // We use both full and empty benchmarks here instead of benchmarking transient_storage + // (BTreeMap) directly. This approach is necessary because benchmarking this BTreeMap is very + // slow. Additionally, we use linear regression for our benchmarks, and the BTreeMap's log(n) + // complexity can introduce approximation errors. + #[benchmark(pov_mode = Ignored)] + fn set_transient_storage_empty() -> Result<(), BenchmarkError> { + let max_value_len = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + let value = Some(vec![42u8; max_value_len as _]); + let mut setup = CallSetup::::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + let result; + #[block] + { + result = runtime.ext().set_transient_storage(&key, value, false); + } + + assert_eq!(result, Ok(WriteOutcome::New)); + assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _])); + Ok(()) + } + + #[benchmark(pov_mode = Ignored)] + fn set_transient_storage_full() -> Result<(), BenchmarkError> { + let max_value_len = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + let value = Some(vec![42u8; max_value_len as _]); + let mut setup = CallSetup::::default(); + setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + let result; + #[block] + { + result = runtime.ext().set_transient_storage(&key, value, false); + } + + assert_eq!(result, Ok(WriteOutcome::New)); + assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _])); + Ok(()) + } + + #[benchmark(pov_mode = Ignored)] + fn get_transient_storage_empty() -> Result<(), BenchmarkError> { + let max_value_len = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + + let mut setup = CallSetup::::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + let result; + #[block] + { + result = runtime.ext().get_transient_storage(&key); + } + + assert_eq!(result, Some(vec![42u8; max_value_len as _])); + Ok(()) + } + + #[benchmark(pov_mode = Ignored)] + fn get_transient_storage_full() -> Result<(), BenchmarkError> { + let max_value_len = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + + let mut setup = CallSetup::::default(); + setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + let result; + #[block] + { + result = runtime.ext().get_transient_storage(&key); + } + + assert_eq!(result, Some(vec![42u8; max_value_len as _])); + Ok(()) + } + + // The weight of journal rollbacks should be taken into account when setting storage. + #[benchmark(pov_mode = Ignored)] + fn rollback_transient_storage() -> Result<(), BenchmarkError> { + let max_value_len = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + + let mut setup = CallSetup::::default(); + setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime.ext().transient_storage().start_transaction(); + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + #[block] + { + runtime.ext().transient_storage().rollback_transaction(); + } + + assert_eq!(runtime.ext().get_transient_storage(&key), None); + Ok(()) + } + + // n: new byte size + // o: old byte size + #[benchmark(pov_mode = Measured)] + fn seal_set_transient_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + o: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + let value = vec![1u8; n as usize]; + build_runtime!(runtime, memory: [ key.unhashed(), value.clone(), ]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; o as usize]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_set_storage( + memory.as_mut_slice(), + StorageFlags::TRANSIENT.bits(), + 0, // key_ptr + max_key_len, // key_len + max_key_len, // value_ptr + n, // value_len + ); + } + + assert_ok!(result); + assert_eq!(runtime.ext().get_transient_storage(&key).unwrap(), value); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn seal_clear_transient_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, memory: [ key.unhashed(), ]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; n as usize]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_clear_storage( + memory.as_mut_slice(), + StorageFlags::TRANSIENT.bits(), + 0, + max_key_len, + ); + } + + assert_ok!(result); + assert!(runtime.ext().get_transient_storage(&key).is_none()); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn seal_get_transient_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; n as usize]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + + let out_ptr = max_key_len + 4; + let result; + #[block] + { + result = runtime.bench_get_storage( + memory.as_mut_slice(), + StorageFlags::TRANSIENT.bits(), + 0, // key_ptr + max_key_len, // key_len + out_ptr, // out_ptr + max_key_len, // out_len_ptr + ); + } + + assert_ok!(result); + assert_eq!( + &runtime.ext().get_transient_storage(&key).unwrap(), + &memory[out_ptr as usize..] + ); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn seal_contains_transient_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, memory: [ key.unhashed(), ]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + runtime + .ext() + .set_transient_storage(&key, Some(vec![42u8; n as usize]), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + + let result; + #[block] + { + result = runtime.bench_contains_storage( + memory.as_mut_slice(), + StorageFlags::TRANSIENT.bits(), + 0, + max_key_len, + ); + } + + assert_eq!(result.unwrap(), n); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn seal_take_transient_storage( + n: Linear<0, { limits::PAYLOAD_BYTES }>, + ) -> Result<(), BenchmarkError> { + let n = limits::PAYLOAD_BYTES; + let max_key_len = limits::STORAGE_KEY_BYTES; + let key = Key::try_from_var(vec![0u8; max_key_len as usize]) + .map_err(|_| "Key has wrong length")?; + build_runtime!(runtime, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]); + runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX; + let value = vec![42u8; n as usize]; + runtime + .ext() + .set_transient_storage(&key, Some(value.clone()), false) + .map_err(|_| "Failed to write to transient storage during setup.")?; + + let out_ptr = max_key_len + 4; + let result; + #[block] + { + result = runtime.bench_take_storage( + memory.as_mut_slice(), + StorageFlags::TRANSIENT.bits(), + 0, // key_ptr + max_key_len, // key_len + out_ptr, // out_ptr + max_key_len, // out_len_ptr + ); + } + + assert_ok!(result); + assert!(&runtime.ext().get_transient_storage(&key).is_none()); + assert_eq!(&value, &memory[out_ptr as usize..]); + Ok(()) + } + + // We transfer to unique accounts. + #[benchmark(pov_mode = Measured)] + fn seal_transfer() { + let account = account::("receiver", 0, 0); + let value = Pallet::::min_balance(); + assert!(value > 0u32.into()); + + let mut setup = CallSetup::::default(); + setup.set_balance(value); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + + let account_bytes = account.encode(); + let account_len = account_bytes.len() as u32; + let value_bytes = value.encode(); + let mut memory = memory!(account_bytes, value_bytes,); + + let result; + #[block] + { + result = runtime.bench_transfer( + memory.as_mut_slice(), + 0, // account_ptr + account_len, // value_ptr + ); + } + + assert_ok!(result); + } + + // t: with or without some value to transfer + // i: size of the input data + #[benchmark(pov_mode = Measured)] + fn seal_call(t: Linear<0, 1>, i: Linear<0, { limits::MEMORY_BYTES }>) { + let Contract { account_id: callee, .. } = + Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); + let callee_bytes = callee.encode(); + let callee_len = callee_bytes.len() as u32; + + let value: BalanceOf = t.into(); + let value_bytes = value.encode(); + + let deposit: BalanceOf = (u32::MAX - 100).into(); + let deposit_bytes = deposit.encode(); + let deposit_len = deposit_bytes.len() as u32; + + let mut setup = CallSetup::::default(); + setup.set_storage_deposit_limit(deposit); + setup.set_data(vec![42; i as usize]); + setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); + + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes,); + + let result; + #[block] + { + result = runtime.bench_call( + memory.as_mut_slice(), + CallFlags::CLONE_INPUT.bits(), // flags + 0, // callee_ptr + 0, // ref_time_limit + 0, // proof_size_limit + callee_len, // deposit_ptr + callee_len + deposit_len, // value_ptr + 0, // input_data_ptr + 0, // input_data_len + SENTINEL, // output_ptr + 0, // output_len_ptr + ); + } + + assert_ok!(result); + } + + #[benchmark(pov_mode = Measured)] + fn seal_delegate_call() -> Result<(), BenchmarkError> { + let hash = Contract::::with_index(1, WasmModule::dummy(), vec![])?.info()?.code_hash; + + let mut setup = CallSetup::::default(); + setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); + + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + let mut memory = memory!(hash.encode(),); + + let result; + #[block] + { + result = runtime.bench_delegate_call( + memory.as_mut_slice(), + 0, // flags + 0, // code_hash_ptr + 0, // input_data_ptr + 0, // input_data_len + SENTINEL, // output_ptr + 0, + ); + } + + assert_ok!(result); + Ok(()) + } + + // t: value to transfer + // i: size of input in bytes + // s: size of salt in bytes + #[benchmark(pov_mode = Measured)] + fn seal_instantiate( + i: Linear<0, { limits::MEMORY_BYTES }>, + s: Linear<0, { limits::MEMORY_BYTES }>, + ) -> Result<(), BenchmarkError> { + let hash = Contract::::with_index(1, WasmModule::dummy(), vec![])?.info()?.code_hash; + let hash_bytes = hash.encode(); + let hash_len = hash_bytes.len() as u32; + + let value: BalanceOf = 1u32.into(); + let value_bytes = value.encode(); + let value_len = value_bytes.len() as u32; + + let deposit: BalanceOf = 0u32.into(); + let deposit_bytes = deposit.encode(); + let deposit_len = deposit_bytes.len() as u32; + + let mut setup = CallSetup::::default(); + setup.set_origin(Origin::from_account_id(setup.contract().account_id.clone())); + setup.set_balance(value + (Pallet::::min_balance() * 2u32.into())); + + let account_id = &setup.contract().account_id.clone(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); + + let input = vec![42u8; i as _]; + let salt = vec![42u8; s as _]; + let addr = Contracts::::contract_address(&account_id, &hash, &input, &salt); + let mut memory = memory!(hash_bytes, deposit_bytes, value_bytes, input, salt,); + + let mut offset = { + let mut current = 0u32; + move |after: u32| { + current += after; + current + } + }; + + assert!(ContractInfoOf::::get(&addr).is_none()); + + let result; + #[block] + { + result = runtime.bench_instantiate( + memory.as_mut_slice(), + 0, // code_hash_ptr + 0, // ref_time_limit + 0, // proof_size_limit + offset(hash_len), // deposit_ptr + offset(deposit_len), // value_ptr + offset(value_len), // input_data_ptr + i, // input_data_len + SENTINEL, // address_ptr + 0, // address_len_ptr + SENTINEL, // output_ptr + 0, // output_len_ptr + offset(i), // salt_ptr + s, // salt_len + ); + } + + assert_ok!(result); + assert!(ContractInfoOf::::get(&addr).is_some()); + assert_eq!(T::Currency::balance(&addr), Pallet::::min_balance() + value); + Ok(()) + } + + // `n`: Input to hash in bytes + #[benchmark(pov_mode = Measured)] + fn seal_hash_sha2_256(n: Linear<0, { limits::MEMORY_BYTES }>) { + build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]); + + let result; + #[block] + { + result = runtime.bench_hash_sha2_256(memory.as_mut_slice(), 32, n, 0); + } + assert_eq!(sp_io::hashing::sha2_256(&memory[32..]), &memory[0..32]); + assert_ok!(result); + } + + // `n`: Input to hash in bytes + #[benchmark(pov_mode = Measured)] + fn seal_hash_keccak_256(n: Linear<0, { limits::MEMORY_BYTES }>) { + build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]); + + let result; + #[block] + { + result = runtime.bench_hash_keccak_256(memory.as_mut_slice(), 32, n, 0); + } + assert_eq!(sp_io::hashing::keccak_256(&memory[32..]), &memory[0..32]); + assert_ok!(result); + } + + // `n`: Input to hash in bytes + #[benchmark(pov_mode = Measured)] + fn seal_hash_blake2_256(n: Linear<0, { limits::MEMORY_BYTES }>) { + build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]); + + let result; + #[block] + { + result = runtime.bench_hash_blake2_256(memory.as_mut_slice(), 32, n, 0); + } + assert_eq!(sp_io::hashing::blake2_256(&memory[32..]), &memory[0..32]); + assert_ok!(result); + } + + // `n`: Input to hash in bytes + #[benchmark(pov_mode = Measured)] + fn seal_hash_blake2_128(n: Linear<0, { limits::MEMORY_BYTES }>) { + build_runtime!(runtime, memory: [[0u8; 16], vec![0u8; n as usize], ]); + + let result; + #[block] + { + result = runtime.bench_hash_blake2_128(memory.as_mut_slice(), 16, n, 0); + } + assert_eq!(sp_io::hashing::blake2_128(&memory[16..]), &memory[0..16]); + assert_ok!(result); + } + + // `n`: Message input length to verify in bytes. + // need some buffer so the code size does not exceed the max code size. + #[benchmark(pov_mode = Measured)] + fn seal_sr25519_verify(n: Linear<0, { T::MaxCodeLen::get() - 255 }>) { + let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::>(); + let message_len = message.len() as u32; + + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let pub_key = sp_io::crypto::sr25519_generate(key_type, None); + let sig = + sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature"); + let sig = AsRef::<[u8; 64]>::as_ref(&sig).to_vec(); + let sig_len = sig.len() as u32; + + build_runtime!(runtime, memory: [sig, pub_key.to_vec(), message, ]); + + let result; + #[block] + { + result = runtime.bench_sr25519_verify( + memory.as_mut_slice(), + 0, // signature_ptr + sig_len, // pub_key_ptr + message_len, // message_len + sig_len + pub_key.len() as u32, // message_ptr + ); + } + + assert_eq!(result.unwrap(), ReturnErrorCode::Success); + } + + #[benchmark(pov_mode = Measured)] + fn seal_ecdsa_recover() { + let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let signature = { + let pub_key = sp_io::crypto::ecdsa_generate(key_type, None); + let sig = sp_io::crypto::ecdsa_sign_prehashed(key_type, &pub_key, &message_hash) + .expect("Generates signature"); + AsRef::<[u8; 65]>::as_ref(&sig).to_vec() + }; + + build_runtime!(runtime, memory: [signature, message_hash, [0u8; 33], ]); + + let result; + #[block] + { + result = runtime.bench_ecdsa_recover( + memory.as_mut_slice(), + 0, // signature_ptr + 65, // message_hash_ptr + 65 + 32, // output_ptr + ); + } + + assert_eq!(result.unwrap(), ReturnErrorCode::Success); + } + + // Only calling the function itself for the list of + // generated different ECDSA keys. + // This is a slow call: We reduce the number of runs. + #[benchmark(pov_mode = Measured)] + fn seal_ecdsa_to_eth_address() { + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let pub_key_bytes = sp_io::crypto::ecdsa_generate(key_type, None).0; + build_runtime!(runtime, memory: [[0u8; 20], pub_key_bytes,]); + + let result; + #[block] + { + result = runtime.bench_ecdsa_to_eth_address( + memory.as_mut_slice(), + 20, // key_ptr + 0, // output_ptr + ); + } + + assert_ok!(result); + assert_eq!(&memory[..20], runtime.ext().ecdsa_to_eth_address(&pub_key_bytes).unwrap()); + } + + #[benchmark(pov_mode = Measured)] + fn seal_set_code_hash() -> Result<(), BenchmarkError> { + let code_hash = + Contract::::with_index(1, WasmModule::dummy(), vec![])?.info()?.code_hash; + + build_runtime!(runtime, memory: [ code_hash.encode(),]); + + let result; + #[block] + { + result = runtime.bench_set_code_hash(memory.as_mut_slice(), 0); + } + + assert_ok!(result); + Ok(()) + } + + #[benchmark(pov_mode = Measured)] + fn lock_delegate_dependency() -> Result<(), BenchmarkError> { + let code_hash = Contract::::with_index(1, WasmModule::dummy_unique(1), vec![])? + .info()? + .code_hash; + + build_runtime!(runtime, memory: [ code_hash.encode(),]); + + let result; + #[block] + { + result = runtime.bench_lock_delegate_dependency(memory.as_mut_slice(), 0); + } + + assert_ok!(result); + Ok(()) + } + + #[benchmark] + fn unlock_delegate_dependency() -> Result<(), BenchmarkError> { + let code_hash = Contract::::with_index(1, WasmModule::dummy_unique(1), vec![])? + .info()? + .code_hash; + + build_runtime!(runtime, memory: [ code_hash.encode(),]); + runtime.bench_lock_delegate_dependency(memory.as_mut_slice(), 0).unwrap(); + + let result; + #[block] + { + result = runtime.bench_unlock_delegate_dependency(memory.as_mut_slice(), 0); + } + + assert_ok!(result); + Ok(()) + } + + // Benchmark the execution of instructions. + #[benchmark(pov_mode = Ignored)] + fn instr(r: Linear<0, INSTR_BENCHMARK_RUNS>) { + // (round, start, div, mult, add) + let input = (r, 1_000u32, 2u32, 3u32, 100u32).encode(); + let mut setup = CallSetup::::new(WasmModule::instr()); + let (mut ext, module) = setup.ext(); + let prepared = CallSetup::::prepare_call(&mut ext, module, input); + #[block] + { + prepared.call().unwrap(); + } + } + + impl_benchmark_test_suite!( + Contracts, + crate::tests::ExtBuilder::default().build(), + crate::tests::Test, + ); +} diff --git a/substrate/frame/revive/src/benchmarking_dummy.rs b/substrate/frame/revive/src/benchmarking_dummy.rs new file mode 100644 index 000000000000..6bb467911272 --- /dev/null +++ b/substrate/frame/revive/src/benchmarking_dummy.rs @@ -0,0 +1,37 @@ +// 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. +// + +//! Defines a dummy benchmarking suite so that the build doesn't fail in case +//! no RISC-V toolchain is available. + +#![cfg(feature = "runtime-benchmarks")] +#![cfg(not(feature = "riscv"))] + +use crate::{Config, *}; +use frame_benchmarking::v2::*; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark(pov_mode = Ignored)] + fn enable_riscv_feature_to_unlock_benchmarks() { + #[block] + {} + } +} diff --git a/substrate/frame/revive/src/chain_extension.rs b/substrate/frame/revive/src/chain_extension.rs new file mode 100644 index 000000000000..ccea12945054 --- /dev/null +++ b/substrate/frame/revive/src/chain_extension.rs @@ -0,0 +1,358 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A mechanism for runtime authors to augment the functionality of contracts. +//! +//! The runtime is able to call into any contract and retrieve the result using +//! [`bare_call`](crate::Pallet::bare_call). This already allows customization of runtime +//! behaviour by user generated code (contracts). However, often it is more straightforward +//! to allow the reverse behaviour: The contract calls into the runtime. We call the latter +//! one a "chain extension" because it allows the chain to extend the set of functions that are +//! callable by a contract. +//! +//! In order to create a chain extension the runtime author implements the [`ChainExtension`] +//! trait and declares it in this pallet's [configuration Trait](Config). All types +//! required for this endeavour are defined or re-exported in this module. There is an +//! implementation on `()` which can be used to signal that no chain extension is available. +//! +//! # Using multiple chain extensions +//! +//! Often there is a need for having multiple chain extensions. This is often the case when +//! some generally useful off-the-shelf extensions should be included. To have multiple chain +//! extensions they can be put into a tuple which is then passed to [`Config::ChainExtension`] like +//! this `type Extensions = (ExtensionA, ExtensionB)`. +//! +//! However, only extensions implementing [`RegisteredChainExtension`] can be put into a tuple. +//! This is because the [`RegisteredChainExtension::ID`] is used to decide which of those extensions +//! should be used when the contract calls a chain extensions. Extensions which are generally +//! useful should claim their `ID` with [the registry](https://github.com/paritytech/chainextension-registry) +//! so that no collisions with other vendors will occur. +//! +//! **Chain specific extensions must use the reserved `ID = 0` so that they can't be registered with +//! the registry.** +//! +//! # Security +//! +//! The chain author alone is responsible for the security of the chain extension. +//! This includes avoiding the exposure of exploitable functions and charging the +//! appropriate amount of weight. In order to do so benchmarks must be written and the +//! [`charge_weight`](Environment::charge_weight) function must be called **before** +//! carrying out any action that causes the consumption of the chargeable weight. +//! It cannot be overstated how delicate of a process the creation of a chain extension +//! is. Check whether using [`bare_call`](crate::Pallet::bare_call) suffices for the +//! use case at hand. +//! +//! # Benchmarking +//! +//! The builtin contract callable functions that pallet-revive provides all have +//! benchmarks that determine the correct weight that an invocation of these functions +//! induces. In order to be able to charge the correct weight for the functions defined +//! by a chain extension benchmarks must be written, too. In the near future this crate +//! will provide the means for easier creation of those specialized benchmarks. +//! +//! # Example +//! +//! The ink-examples repository maintains an +//! [end-to-end example](https://github.com/paritytech/ink-examples/tree/main/rand-extension) +//! on how to use a chain extension in order to provide new features to ink! contracts. + +use crate::{ + wasm::{Memory, Runtime, RuntimeCosts}, + Error, +}; +use alloc::vec::Vec; +use codec::{Decode, MaxEncodedLen}; +use frame_support::weights::Weight; +use sp_runtime::DispatchError; + +pub use crate::{exec::Ext, gas::ChargedAmount, storage::meter::Diff, Config}; +pub use frame_system::Config as SysConfig; +pub use pallet_revive_uapi::ReturnFlags; + +/// Result that returns a [`DispatchError`] on error. +pub type Result = core::result::Result; + +/// A trait used to extend the set of contract callable functions. +/// +/// In order to create a custom chain extension this trait must be implemented and supplied +/// to the pallet contracts configuration trait as the associated type of the same name. +/// Consult the [module documentation](self) for a general explanation of chain extensions. +/// +/// # Lifetime +/// +/// The extension will be [`Default`] initialized at the beginning of each call +/// (**not** per call stack) and dropped afterwards. Hence any value held inside the extension +/// can be used as a per-call scratch buffer. +pub trait ChainExtension { + /// Call the chain extension logic. + /// + /// This is the only function that needs to be implemented in order to write a + /// chain extensions. It is called whenever a contract calls the `seal_call_chain_extension` + /// imported wasm function. + /// + /// # Parameters + /// - `env`: Access to the remaining arguments and the execution environment. + /// + /// # Return + /// + /// In case of `Err` the contract execution is immediately suspended and the passed error + /// is returned to the caller. Otherwise the value of [`RetVal`] determines the exit + /// behaviour. + /// + /// # Note + /// + /// The [`Self::call`] can be invoked within a read-only context, where any state-changing calls + /// are disallowed. This information can be obtained using `env.ext().is_read_only()`. It is + /// crucial for the implementer to handle this scenario appropriately. + fn call, M: ?Sized + Memory>( + &mut self, + env: Environment, + ) -> Result; + + /// Determines whether chain extensions are enabled for this chain. + /// + /// The default implementation returns `true`. Therefore it is not necessary to overwrite + /// this function when implementing a chain extension. In case of `false` the deployment of + /// a contract that references `seal_call_chain_extension` will be denied and calling this + /// function will return [`NoChainExtension`](Error::NoChainExtension) without first calling + /// into [`call`](Self::call). + fn enabled() -> bool { + true + } +} + +/// A [`ChainExtension`] that can be composed with other extensions using a tuple. +/// +/// An extension that implements this trait can be put in a tuple in order to have multiple +/// extensions available. The tuple implementation routes requests based on the first two +/// most significant bytes of the `id` passed to `call`. +/// +/// If this extensions is to be used by multiple runtimes consider +/// [registering it](https://github.com/paritytech/chainextension-registry) to ensure that there +/// are no collisions with other vendors. +/// +/// # Note +/// +/// Currently, we support tuples of up to ten registered chain extensions. If more chain extensions +/// are needed consider opening an issue. +pub trait RegisteredChainExtension: ChainExtension { + /// The extensions globally unique identifier. + const ID: u16; +} + +#[impl_trait_for_tuples::impl_for_tuples(10)] +#[tuple_types_custom_trait_bound(RegisteredChainExtension)] +impl ChainExtension for Tuple { + fn call, M: ?Sized + Memory>( + &mut self, + mut env: Environment, + ) -> Result { + for_tuples!( + #( + if (Tuple::ID == env.ext_id()) && Tuple::enabled() { + return Tuple.call(env); + } + )* + ); + Err(Error::::NoChainExtension.into()) + } + + fn enabled() -> bool { + for_tuples!( + #( + if Tuple::enabled() { + return true; + } + )* + ); + false + } +} + +/// Determines the exit behaviour and return value of a chain extension. +pub enum RetVal { + /// The chain extensions returns the supplied value to its calling contract. + Converging(u32), + /// The control does **not** return to the calling contract. + /// + /// Use this to stop the execution of the contract when the chain extension returns. + /// The semantic is the same as for calling `seal_return`: The control returns to + /// the caller of the currently executing contract yielding the supplied buffer and + /// flags. + Diverging { flags: ReturnFlags, data: Vec }, +} + +/// Grants the chain extension access to its parameters and execution environment. +pub struct Environment<'a, 'b, E: Ext, M: ?Sized> { + /// The runtime contains all necessary functions to interact with the running contract. + runtime: &'a mut Runtime<'b, E, M>, + /// Reference to the contract's memory. + memory: &'a mut M, + /// Verbatim argument passed to `seal_call_chain_extension`. + id: u32, + /// Verbatim argument passed to `seal_call_chain_extension`. + input_ptr: u32, + /// Verbatim argument passed to `seal_call_chain_extension`. + input_len: u32, + /// Verbatim argument passed to `seal_call_chain_extension`. + output_ptr: u32, + /// Verbatim argument passed to `seal_call_chain_extension`. + output_len_ptr: u32, +} + +/// Functions that are available in every state of this type. +impl<'a, 'b, E: Ext, M: ?Sized + Memory> Environment<'a, 'b, E, M> { + /// Creates a new environment for consumption by a chain extension. + pub fn new( + runtime: &'a mut Runtime<'b, E, M>, + memory: &'a mut M, + id: u32, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Self { + Self { runtime, memory, id, input_ptr, input_len, output_ptr, output_len_ptr } + } + + /// The function id within the `id` passed by a contract. + /// + /// It returns the two least significant bytes of the `id` passed by a contract as the other + /// two bytes represent the chain extension itself (the code which is calling this function). + pub fn func_id(&self) -> u16 { + (self.id & 0x0000FFFF) as u16 + } + + /// The chain extension id within the `id` passed by a contract. + /// + /// It returns the two most significant bytes of the `id` passed by a contract which represent + /// the chain extension itself (the code which is calling this function). + pub fn ext_id(&self) -> u16 { + (self.id >> 16) as u16 + } + + /// Charge the passed `amount` of weight from the overall limit. + /// + /// It returns `Ok` when there the remaining weight budget is larger than the passed + /// `weight`. It returns `Err` otherwise. In this case the chain extension should + /// abort the execution and pass through the error. + /// + /// The returned value can be used to with [`Self::adjust_weight`]. Other than that + /// it has no purpose. + /// + /// # Note + /// + /// Weight is synonymous with gas in substrate. + pub fn charge_weight(&mut self, amount: Weight) -> Result { + self.runtime.charge_gas(RuntimeCosts::ChainExtension(amount)) + } + + /// Adjust a previously charged amount down to its actual amount. + /// + /// This is when a maximum a priori amount was charged and then should be partially + /// refunded to match the actual amount. + pub fn adjust_weight(&mut self, charged: ChargedAmount, actual_weight: Weight) { + self.runtime.adjust_gas(charged, RuntimeCosts::ChainExtension(actual_weight)) + } + + /// Grants access to the execution environment of the current contract call. + /// + /// Consult the functions on the returned type before re-implementing those functions. + pub fn ext(&mut self) -> &mut E { + self.runtime.ext() + } + + /// Reads `min(max_len, in_len)` from contract memory. + /// + /// This does **not** charge any weight. The caller must make sure that the an + /// appropriate amount of weight is charged **before** reading from contract memory. + /// The reason for that is that usually the costs for reading data and processing + /// said data cannot be separated in a benchmark. Therefore a chain extension would + /// charge the overall costs either using `max_len` (worst case approximation) or using + /// [`in_len()`](Self::in_len). + pub fn read(&self, max_len: u32) -> Result> { + self.memory.read(self.input_ptr, self.input_len.min(max_len)) + } + + /// Reads `min(buffer.len(), in_len) from contract memory. + /// + /// This takes a mutable pointer to a buffer fills it with data and shrinks it to + /// the size of the actual data. Apart from supporting pre-allocated buffers it is + /// equivalent to to [`read()`](Self::read). + pub fn read_into(&self, buffer: &mut &mut [u8]) -> Result<()> { + let len = buffer.len(); + let sliced = { + let buffer = core::mem::take(buffer); + &mut buffer[..len.min(self.input_len as usize)] + }; + self.memory.read_into_buf(self.input_ptr, sliced)?; + *buffer = sliced; + Ok(()) + } + + /// Reads and decodes a type with a size fixed at compile time from contract memory. + /// + /// This function is secure and recommended for all input types of fixed size + /// as long as the cost of reading the memory is included in the overall already charged + /// weight of the chain extension. This should usually be the case when fixed input types + /// are used. + pub fn read_as(&mut self) -> Result { + self.memory.read_as(self.input_ptr) + } + + /// Reads and decodes a type with a dynamic size from contract memory. + /// + /// Make sure to include `len` in your weight calculations. + pub fn read_as_unbounded(&mut self, len: u32) -> Result { + self.memory.read_as_unbounded(self.input_ptr, len) + } + + /// The length of the input as passed in as `input_len`. + /// + /// A chain extension would use this value to calculate the dynamic part of its + /// weight. For example a chain extension that calculates the hash of some passed in + /// bytes would use `in_len` to charge the costs of hashing that amount of bytes. + /// This also subsumes the act of copying those bytes as a benchmarks measures both. + pub fn in_len(&self) -> u32 { + self.input_len + } + + /// Write the supplied buffer to contract memory. + /// + /// If the contract supplied buffer is smaller than the passed `buffer` an `Err` is returned. + /// If `allow_skip` is set to true the contract is allowed to skip the copying of the buffer + /// by supplying the guard value of `pallet-revive::SENTINEL` as `out_ptr`. The + /// `weight_per_byte` is only charged when the write actually happens and is not skipped or + /// failed due to a too small output buffer. + pub fn write( + &mut self, + buffer: &[u8], + allow_skip: bool, + weight_per_byte: Option, + ) -> Result<()> { + self.runtime.write_sandbox_output( + self.memory, + self.output_ptr, + self.output_len_ptr, + buffer, + allow_skip, + |len| { + weight_per_byte.map(|w| RuntimeCosts::ChainExtension(w.saturating_mul(len.into()))) + }, + ) + } +} diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs new file mode 100644 index 000000000000..467f4e1ad491 --- /dev/null +++ b/substrate/frame/revive/src/debug.rs @@ -0,0 +1,112 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use crate::{ + exec::{ExecResult, ExportedFunction}, + primitives::ExecReturnValue, +}; +use crate::{Config, LOG_TARGET}; + +/// Umbrella trait for all interfaces that serves for debugging. +pub trait Debugger: Tracing + CallInterceptor {} + +impl Debugger for V where V: Tracing + CallInterceptor {} + +/// Defines methods to capture contract calls, enabling external observers to +/// measure, trace, and react to contract interactions. +pub trait Tracing { + /// The type of [`CallSpan`] that is created by this trait. + type CallSpan: CallSpan; + + /// Creates a new call span to encompass the upcoming contract execution. + /// + /// This method should be invoked just before the execution of a contract and + /// marks the beginning of a traceable span of execution. + /// + /// # Arguments + /// + /// * `contract_address` - The address of the contract that is about to be executed. + /// * `entry_point` - Describes whether the call is the constructor or a regular call. + /// * `input_data` - The raw input data of the call. + fn new_call_span( + contract_address: &T::AccountId, + entry_point: ExportedFunction, + input_data: &[u8], + ) -> Self::CallSpan; +} + +/// Defines a span of execution for a contract call. +pub trait CallSpan { + /// Called just after the execution of a contract. + /// + /// # Arguments + /// + /// * `output` - The raw output of the call. + fn after_call(self, output: &ExecReturnValue); +} + +impl Tracing for () { + type CallSpan = (); + + fn new_call_span( + contract_address: &T::AccountId, + entry_point: ExportedFunction, + input_data: &[u8], + ) { + log::trace!(target: LOG_TARGET, "call {entry_point:?} account: {contract_address:?}, input_data: {input_data:?}") + } +} + +impl CallSpan for () { + fn after_call(self, output: &ExecReturnValue) { + log::trace!(target: LOG_TARGET, "call result {output:?}") + } +} + +/// Provides an interface for intercepting contract calls. +pub trait CallInterceptor { + /// Allows to intercept contract calls and decide whether they should be executed or not. + /// If the call is intercepted, the mocked result of the call is returned. + /// + /// # Arguments + /// + /// * `contract_address` - The address of the contract that is about to be executed. + /// * `entry_point` - Describes whether the call is the constructor or a regular call. + /// * `input_data` - The raw input data of the call. + /// + /// # Expected behavior + /// + /// This method should return: + /// * `Some(ExecResult)` - if the call should be intercepted and the mocked result of the call + /// is returned. + /// * `None` - otherwise, i.e. the call should be executed normally. + fn intercept_call( + contract_address: &T::AccountId, + entry_point: ExportedFunction, + input_data: &[u8], + ) -> Option; +} + +impl CallInterceptor for () { + fn intercept_call( + _contract_address: &T::AccountId, + _entry_point: ExportedFunction, + _input_data: &[u8], + ) -> Option { + None + } +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs new file mode 100644 index 000000000000..9740707ae706 --- /dev/null +++ b/substrate/frame/revive/src/exec.rs @@ -0,0 +1,3948 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + debug::{CallInterceptor, CallSpan, Tracing}, + gas::GasMeter, + limits, + primitives::{ExecReturnValue, StorageDeposit}, + runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, + storage::{self, meter::Diff, WriteOutcome}, + transient_storage::TransientStorage, + BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, DebugBuffer, + Error, Event, Pallet as Contracts, LOG_TARGET, +}; +use alloc::vec::Vec; +use core::{fmt::Debug, marker::PhantomData, mem}; +use frame_support::{ + crypto::ecdsa::ECDSAExt, + dispatch::{DispatchResult, DispatchResultWithPostInfo}, + ensure, + storage::{with_transaction, TransactionOutcome}, + traits::{ + fungible::{Inspect, Mutate}, + tokens::{Fortitude, Preservation}, + Contains, OriginTrait, Time, + }, + weights::Weight, + Blake2_128Concat, BoundedVec, StorageHasher, +}; +use frame_system::{ + pallet_prelude::{BlockNumberFor, OriginFor}, + Pallet as System, RawOrigin, +}; +use sp_core::{ + ecdsa::Public as ECDSAPublic, + sr25519::{Public as SR25519Public, Signature as SR25519Signature}, + ConstU32, Get, +}; +use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; +use sp_runtime::{ + traits::{BadOrigin, Convert, Dispatchable, Zero}, + DispatchError, +}; + +pub type AccountIdOf = ::AccountId; +pub type MomentOf = <::Time as Time>::Moment; +pub type ExecResult = Result; + +/// A type that represents a topic of an event. At the moment a hash is used. +pub type TopicOf = ::Hash; + +/// Type for variable sized storage key. Used for transparent hashing. +type VarSizedKey = BoundedVec>; + +/// Combined key type for both fixed and variable sized storage keys. +pub enum Key { + /// Variant for fixed sized keys. + Fix([u8; 32]), + /// Variant for variable sized keys. + Var(VarSizedKey), +} + +impl Key { + /// Reference to the raw unhashed key. + /// + /// # Note + /// + /// Only used by benchmarking in order to generate storage collisions on purpose. + #[cfg(feature = "runtime-benchmarks")] + pub fn unhashed(&self) -> &[u8] { + match self { + Key::Fix(v) => v.as_ref(), + Key::Var(v) => v.as_ref(), + } + } + + /// The hashed key that has be used as actual key to the storage trie. + pub fn hash(&self) -> Vec { + match self { + Key::Fix(v) => blake2_256(v.as_slice()).to_vec(), + Key::Var(v) => Blake2_128Concat::hash(v.as_slice()), + } + } + + pub fn from_fixed(v: [u8; 32]) -> Self { + Self::Fix(v) + } + + pub fn try_from_var(v: Vec) -> Result { + VarSizedKey::try_from(v).map(Self::Var).map_err(|_| ()) + } +} + +/// Origin of the error. +/// +/// Call or instantiate both called into other contracts and pass through errors happening +/// in those to the caller. This enum is for the caller to distinguish whether the error +/// happened during the execution of the callee or in the current execution context. +#[derive(Copy, Clone, PartialEq, Eq, Debug, codec::Decode, codec::Encode)] +pub enum ErrorOrigin { + /// Caller error origin. + /// + /// The error happened in the current execution context rather than in the one + /// of the contract that is called into. + Caller, + /// The error happened during execution of the called contract. + Callee, +} + +/// Error returned by contract execution. +#[derive(Copy, Clone, PartialEq, Eq, Debug, codec::Decode, codec::Encode)] +pub struct ExecError { + /// The reason why the execution failed. + pub error: DispatchError, + /// Origin of the error. + pub origin: ErrorOrigin, +} + +impl> From for ExecError { + fn from(error: T) -> Self { + Self { error: error.into(), origin: ErrorOrigin::Caller } + } +} + +/// The type of origins supported by the contracts pallet. +#[derive(Clone, Encode, Decode, PartialEq, TypeInfo, RuntimeDebugNoBound)] +pub enum Origin { + Root, + Signed(T::AccountId), +} + +impl Origin { + /// Creates a new Signed Caller from an AccountId. + pub fn from_account_id(account_id: T::AccountId) -> Self { + Origin::Signed(account_id) + } + /// Creates a new Origin from a `RuntimeOrigin`. + pub fn from_runtime_origin(o: OriginFor) -> Result { + match o.into() { + Ok(RawOrigin::Root) => Ok(Self::Root), + Ok(RawOrigin::Signed(t)) => Ok(Self::Signed(t)), + _ => Err(BadOrigin.into()), + } + } + /// Returns the AccountId of a Signed Origin or an error if the origin is Root. + pub fn account_id(&self) -> Result<&T::AccountId, DispatchError> { + match self { + Origin::Signed(id) => Ok(id), + Origin::Root => Err(DispatchError::RootNotAllowed), + } + } +} + +/// An interface that provides access to the external environment in which the +/// smart-contract is executed. +/// +/// This interface is specialized to an account of the executing code, so all +/// operations are implicitly performed on that account. +/// +/// # Note +/// +/// This trait is sealed and cannot be implemented by downstream crates. +pub trait Ext: sealing::Sealed { + type T: Config; + + /// Call (possibly transferring some amount of funds) into the specified account. + /// + /// Returns the code size of the called contract. + fn call( + &mut self, + gas_limit: Weight, + deposit_limit: BalanceOf, + to: AccountIdOf, + value: BalanceOf, + input_data: Vec, + allows_reentry: bool, + read_only: bool, + ) -> Result; + + /// Execute code in the current frame. + /// + /// Returns the code size of the called contract. + fn delegate_call( + &mut self, + code: CodeHash, + input_data: Vec, + ) -> Result; + + /// Instantiate a contract from the given code. + /// + /// Returns the original code size of the called contract. + /// The newly created account will be associated with `code`. `value` specifies the amount of + /// value transferred from the caller to the newly created account. + fn instantiate( + &mut self, + gas_limit: Weight, + deposit_limit: BalanceOf, + code: CodeHash, + value: BalanceOf, + input_data: Vec, + salt: &[u8], + ) -> Result<(AccountIdOf, ExecReturnValue), ExecError>; + + /// Transfer all funds to `beneficiary` and delete the contract. + /// + /// Since this function removes the self contract eagerly, if succeeded, no further actions + /// should be performed on this `Ext` instance. + /// + /// This function will fail if the same contract is present on the contract + /// call stack. + fn terminate(&mut self, beneficiary: &AccountIdOf) -> DispatchResult; + + /// Transfer some amount of funds into the specified account. + fn transfer(&mut self, to: &AccountIdOf, value: BalanceOf) -> DispatchResult; + + /// Returns the storage entry of the executing account by the given `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + fn get_storage(&mut self, key: &Key) -> Option>; + + /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + fn get_storage_size(&mut self, key: &Key) -> Option; + + /// Sets the storage entry by the given key to the specified value. If `value` is `None` then + /// the storage entry is deleted. + fn set_storage( + &mut self, + key: &Key, + value: Option>, + take_old: bool, + ) -> Result; + + /// Returns the transient storage entry of the executing account for the given `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_transient_storage` or + /// was deleted. + fn get_transient_storage(&self, key: &Key) -> Option>; + + /// Returns `Some(len)` (in bytes) if a transient storage item exists at `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_transient_storage` or + /// was deleted. + fn get_transient_storage_size(&self, key: &Key) -> Option; + + /// Sets the transient storage entry for the given key to the specified value. If `value` is + /// `None` then the storage entry is deleted. + fn set_transient_storage( + &mut self, + key: &Key, + value: Option>, + take_old: bool, + ) -> Result; + + /// Returns the caller. + fn caller(&self) -> Origin; + + /// Check if a contract lives at the specified `address`. + fn is_contract(&self, address: &AccountIdOf) -> bool; + + /// Returns the code hash of the contract for the given `address`. + /// + /// Returns `None` if the `address` does not belong to a contract. + fn code_hash(&self, address: &AccountIdOf) -> Option>; + + /// Returns the code hash of the contract being executed. + fn own_code_hash(&mut self) -> &CodeHash; + + /// Check if the caller of the current contract is the origin of the whole call stack. + /// + /// This can be checked with `is_contract(self.caller())` as well. + /// However, this function does not require any storage lookup and therefore uses less weight. + fn caller_is_origin(&self) -> bool; + + /// Check if the caller is origin, and this origin is root. + fn caller_is_root(&self) -> bool; + + /// Returns a reference to the account id of the current contract. + fn address(&self) -> &AccountIdOf; + + /// Returns the balance of the current contract. + /// + /// The `value_transferred` is already added. + fn balance(&self) -> BalanceOf; + + /// Returns the value transferred along with this call. + fn value_transferred(&self) -> BalanceOf; + + /// Returns a reference to the timestamp of the current block + fn now(&self) -> &MomentOf; + + /// Returns the minimum balance that is required for creating an account. + fn minimum_balance(&self) -> BalanceOf; + + /// Deposit an event with the given topics. + /// + /// There should not be any duplicates in `topics`. + fn deposit_event(&mut self, topics: Vec>, data: Vec); + + /// Returns the current block number. + fn block_number(&self) -> BlockNumberFor; + + /// Returns the maximum allowed size of a storage item. + fn max_value_size(&self) -> u32; + + /// Returns the price for the specified amount of weight. + fn get_weight_price(&self, weight: Weight) -> BalanceOf; + + /// Get an immutable reference to the nested gas meter. + fn gas_meter(&self) -> &GasMeter; + + /// Get a mutable reference to the nested gas meter. + fn gas_meter_mut(&mut self) -> &mut GasMeter; + + /// Charges `diff` from the meter. + fn charge_storage(&mut self, diff: &Diff); + + /// Append a string to the debug buffer. + /// + /// It is added as-is without any additional new line. + /// + /// This is a no-op if debug message recording is disabled which is always the case + /// when the code is executing on-chain. + /// + /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. + fn append_debug_buffer(&mut self, msg: &str) -> bool; + + /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. + fn debug_buffer_enabled(&self) -> bool; + + /// Call some dispatchable and return the result. + fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; + + /// Recovers ECDSA compressed public key based on signature and message hash. + fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>; + + /// Verify a sr25519 signature. + fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool; + + /// Returns Ethereum address from the ECDSA compressed public key. + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>; + + /// Tests sometimes need to modify and inspect the contract info directly. + #[cfg(any(test, feature = "runtime-benchmarks"))] + fn contract_info(&mut self) -> &mut ContractInfo; + + /// Get a mutable reference to the transient storage. + /// Useful in benchmarks when it is sometimes necessary to modify and inspect the transient + /// storage directly. + #[cfg(feature = "runtime-benchmarks")] + fn transient_storage(&mut self) -> &mut TransientStorage; + + /// Sets new code hash for existing contract. + fn set_code_hash(&mut self, hash: CodeHash) -> DispatchResult; + + /// Returns the number of times the specified contract exists on the call stack. Delegated calls + /// Increment the reference count of a of a stored code by one. + /// + /// # Errors + /// + /// [`Error::CodeNotFound`] is returned if no stored code found having the specified + /// `code_hash`. + fn increment_refcount(code_hash: CodeHash) -> DispatchResult; + + /// Decrement the reference count of a stored code by one. + /// + /// # Note + /// + /// A contract whose reference count dropped to zero isn't automatically removed. A + /// `remove_code` transaction must be submitted by the original uploader to do so. + fn decrement_refcount(code_hash: CodeHash); + + /// Adds a delegate dependency to [`ContractInfo`]'s `delegate_dependencies` field. + /// + /// This ensures that the delegated contract is not removed while it is still in use. It + /// increases the reference count of the code hash and charges a fraction (see + /// [`Config::CodeHashLockupDepositPercent`]) of the code deposit. + /// + /// # Errors + /// + /// - [`Error::MaxDelegateDependenciesReached`] + /// - [`Error::CannotAddSelfAsDelegateDependency`] + /// - [`Error::DelegateDependencyAlreadyExists`] + fn lock_delegate_dependency(&mut self, code_hash: CodeHash) -> DispatchResult; + + /// Removes a delegate dependency from [`ContractInfo`]'s `delegate_dependencies` field. + /// + /// This is the counterpart of [`Self::lock_delegate_dependency`]. It decreases the reference + /// count and refunds the deposit that was charged by [`Self::lock_delegate_dependency`]. + /// + /// # Errors + /// + /// - [`Error::DelegateDependencyNotFound`] + fn unlock_delegate_dependency(&mut self, code_hash: &CodeHash) -> DispatchResult; + + /// Returns the number of locked delegate dependencies. + /// + /// Note: Requires &mut self to access the contract info. + fn locked_delegate_dependencies_count(&mut self) -> usize; + + /// Check if running in read-only context. + fn is_read_only(&self) -> bool; +} + +/// Describes the different functions that can be exported by an [`Executable`]. +#[derive( + Copy, + Clone, + PartialEq, + Eq, + sp_core::RuntimeDebug, + codec::Decode, + codec::Encode, + codec::MaxEncodedLen, + scale_info::TypeInfo, +)] +pub enum ExportedFunction { + /// The constructor function which is executed on deployment of a contract. + Constructor, + /// The function which is executed when a contract is called. + Call, +} + +/// A trait that represents something that can be executed. +/// +/// In the on-chain environment this would be represented by a wasm module. This trait exists in +/// order to be able to mock the wasm logic for testing. +pub trait Executable: Sized { + /// Load the executable from storage. + /// + /// # Note + /// Charges size base load weight from the gas meter. + fn from_storage( + code_hash: CodeHash, + gas_meter: &mut GasMeter, + ) -> Result; + + /// Execute the specified exported function and return the result. + /// + /// When the specified function is `Constructor` the executable is stored and its + /// refcount incremented. + /// + /// # Note + /// + /// This functions expects to be executed in a storage transaction that rolls back + /// all of its emitted storage changes. + fn execute>( + self, + ext: &mut E, + function: ExportedFunction, + input_data: Vec, + ) -> ExecResult; + + /// The code info of the executable. + fn code_info(&self) -> &CodeInfo; + + /// The code hash of the executable. + fn code_hash(&self) -> &CodeHash; +} + +/// The complete call stack of a contract execution. +/// +/// The call stack is initiated by either a signed origin or one of the contract RPC calls. +/// This type implements `Ext` and by that exposes the business logic of contract execution to +/// the runtime module which interfaces with the contract (the wasm blob) itself. +pub struct Stack<'a, T: Config, E> { + /// The origin that initiated the call stack. It could either be a Signed plain account that + /// holds an account id or Root. + /// + /// # Note + /// + /// Please note that it is possible that the id of a Signed origin belongs to a contract rather + /// than a plain account when being called through one of the contract RPCs where the + /// client can freely choose the origin. This usually makes no sense but is still possible. + origin: Origin, + /// The gas meter where costs are charged to. + gas_meter: &'a mut GasMeter, + /// The storage meter makes sure that the storage deposit limit is obeyed. + storage_meter: &'a mut storage::meter::Meter, + /// The timestamp at the point of call stack instantiation. + timestamp: MomentOf, + /// The block number at the time of call stack instantiation. + block_number: BlockNumberFor, + /// The actual call stack. One entry per nested contract called/instantiated. + /// This does **not** include the [`Self::first_frame`]. + frames: BoundedVec, ConstU32<{ limits::CALL_STACK_DEPTH }>>, + /// Statically guarantee that each call stack has at least one frame. + first_frame: Frame, + /// A text buffer used to output human readable information. + /// + /// All the bytes added to this field should be valid UTF-8. The buffer has no defined + /// structure and is intended to be shown to users as-is for debugging purposes. + debug_message: Option<&'a mut DebugBuffer>, + /// Transient storage used to store data, which is kept for the duration of a transaction. + transient_storage: TransientStorage, + /// No executable is held by the struct but influences its behaviour. + _phantom: PhantomData, +} + +/// Represents one entry in the call stack. +/// +/// For each nested contract call or instantiate one frame is created. It holds specific +/// information for the said call and caches the in-storage `ContractInfo` data structure. +struct Frame { + /// The account id of the executing contract. + account_id: T::AccountId, + /// The cached in-storage data of the contract. + contract_info: CachedContract, + /// The amount of balance transferred by the caller as part of the call. + value_transferred: BalanceOf, + /// Determines whether this is a call or instantiate frame. + entry_point: ExportedFunction, + /// The gas meter capped to the supplied gas limit. + nested_gas: GasMeter, + /// The storage meter for the individual call. + nested_storage: storage::meter::NestedMeter, + /// If `false` the contract enabled its defense against reentrance attacks. + allows_reentry: bool, + /// If `true` subsequent calls cannot modify storage. + read_only: bool, + /// The caller of the currently executing frame which was spawned by `delegate_call`. + delegate_caller: Option>, +} + +/// Used in a delegate call frame arguments in order to override the executable and caller. +struct DelegatedCall { + /// The executable which is run instead of the contracts own `executable`. + executable: E, + /// The caller of the contract. + caller: Origin, +} + +/// Parameter passed in when creating a new `Frame`. +/// +/// It determines whether the new frame is for a call or an instantiate. +enum FrameArgs<'a, T: Config, E> { + Call { + /// The account id of the contract that is to be called. + dest: T::AccountId, + /// If `None` the contract info needs to be reloaded from storage. + cached_info: Option>, + /// This frame was created by `seal_delegate_call` and hence uses different code than + /// what is stored at [`Self::Call::dest`]. Its caller ([`DelegatedCall::caller`]) is the + /// account which called the caller contract + delegated_call: Option>, + }, + Instantiate { + /// The contract or signed origin which instantiates the new contract. + sender: T::AccountId, + /// The executable whose `deploy` function is run. + executable: E, + /// A salt used in the contract address derivation of the new contract. + salt: &'a [u8], + /// The input data is used in the contract address derivation of the new contract. + input_data: &'a [u8], + }, +} + +/// Describes the different states of a contract as contained in a `Frame`. +enum CachedContract { + /// The cached contract is up to date with the in-storage value. + Cached(ContractInfo), + /// A recursive call into the same contract did write to the contract info. + /// + /// In this case the cached contract is stale and needs to be reloaded from storage. + Invalidated, + /// The current contract executed `terminate` and removed the contract. + /// + /// In this case a reload is neither allowed nor possible. Please note that recursive + /// calls cannot remove a contract as this is checked and denied. + Terminated, +} + +impl CachedContract { + /// Return `Some(ContractInfo)` if the contract is in cached state. `None` otherwise. + fn into_contract(self) -> Option> { + if let CachedContract::Cached(contract) = self { + Some(contract) + } else { + None + } + } + + /// Return `Some(&mut ContractInfo)` if the contract is in cached state. `None` otherwise. + fn as_contract(&mut self) -> Option<&mut ContractInfo> { + if let CachedContract::Cached(contract) = self { + Some(contract) + } else { + None + } + } +} + +impl Frame { + /// Return the `contract_info` of the current contract. + fn contract_info(&mut self) -> &mut ContractInfo { + self.contract_info.get(&self.account_id) + } + + /// Terminate and return the `contract_info` of the current contract. + /// + /// # Note + /// + /// Under no circumstances the contract is allowed to access the `contract_info` after + /// a call to this function. This would constitute a programming error in the exec module. + fn terminate(&mut self) -> ContractInfo { + self.contract_info.terminate(&self.account_id) + } +} + +/// Extract the contract info after loading it from storage. +/// +/// This assumes that `load` was executed before calling this macro. +macro_rules! get_cached_or_panic_after_load { + ($c:expr) => {{ + if let CachedContract::Cached(contract) = $c { + contract + } else { + panic!( + "It is impossible to remove a contract that is on the call stack;\ + See implementations of terminate;\ + Therefore fetching a contract will never fail while using an account id + that is currently active on the call stack;\ + qed" + ); + } + }}; +} + +/// Same as [`Stack::top_frame`]. +/// +/// We need this access as a macro because sometimes hiding the lifetimes behind +/// a function won't work out. +macro_rules! top_frame { + ($stack:expr) => { + $stack.frames.last().unwrap_or(&$stack.first_frame) + }; +} + +/// Same as [`Stack::top_frame_mut`]. +/// +/// We need this access as a macro because sometimes hiding the lifetimes behind +/// a function won't work out. +macro_rules! top_frame_mut { + ($stack:expr) => { + $stack.frames.last_mut().unwrap_or(&mut $stack.first_frame) + }; +} + +impl CachedContract { + /// Load the `contract_info` from storage if necessary. + fn load(&mut self, account_id: &T::AccountId) { + if let CachedContract::Invalidated = self { + let contract = >::get(&account_id); + if let Some(contract) = contract { + *self = CachedContract::Cached(contract); + } + } + } + + /// Return the cached contract_info. + fn get(&mut self, account_id: &T::AccountId) -> &mut ContractInfo { + self.load(account_id); + get_cached_or_panic_after_load!(self) + } + + /// Terminate and return the contract info. + fn terminate(&mut self, account_id: &T::AccountId) -> ContractInfo { + self.load(account_id); + get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated)) + } +} + +impl<'a, T, E> Stack<'a, T, E> +where + T: Config, + E: Executable, +{ + /// Create and run a new call stack by calling into `dest`. + /// + /// # Note + /// + /// `debug_message` should only ever be set to `Some` when executing as an RPC because + /// it adds allocations and could be abused to drive the runtime into an OOM panic. + /// + /// # Return Value + /// + /// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)> + pub fn run_call( + origin: Origin, + dest: T::AccountId, + gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, + value: BalanceOf, + input_data: Vec, + debug_message: Option<&'a mut DebugBuffer>, + ) -> Result { + let (mut stack, executable) = Self::new( + FrameArgs::Call { dest, cached_info: None, delegated_call: None }, + origin, + gas_meter, + storage_meter, + value, + debug_message, + )?; + stack.run(executable, input_data) + } + + /// Create and run a new call stack by instantiating a new contract. + /// + /// # Note + /// + /// `debug_message` should only ever be set to `Some` when executing as an RPC because + /// it adds allocations and could be abused to drive the runtime into an OOM panic. + /// + /// # Return Value + /// + /// Result<(NewContractAccountId, ExecReturnValue), ExecError)> + pub fn run_instantiate( + origin: T::AccountId, + executable: E, + gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, + value: BalanceOf, + input_data: Vec, + salt: &[u8], + debug_message: Option<&'a mut DebugBuffer>, + ) -> Result<(T::AccountId, ExecReturnValue), ExecError> { + let (mut stack, executable) = Self::new( + FrameArgs::Instantiate { + sender: origin.clone(), + executable, + salt, + input_data: input_data.as_ref(), + }, + Origin::from_account_id(origin), + gas_meter, + storage_meter, + value, + debug_message, + )?; + let account_id = stack.top_frame().account_id.clone(); + stack.run(executable, input_data).map(|ret| (account_id, ret)) + } + + #[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] + pub fn bench_new_call( + dest: T::AccountId, + origin: Origin, + gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, + value: BalanceOf, + debug_message: Option<&'a mut DebugBuffer>, + ) -> (Self, E) { + Self::new( + FrameArgs::Call { dest, cached_info: None, delegated_call: None }, + origin, + gas_meter, + storage_meter, + value, + debug_message, + ) + .unwrap() + } + + /// Create a new call stack. + fn new( + args: FrameArgs, + origin: Origin, + gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, + value: BalanceOf, + debug_message: Option<&'a mut DebugBuffer>, + ) -> Result<(Self, E), ExecError> { + let (first_frame, executable) = Self::new_frame( + args, + value, + gas_meter, + Weight::zero(), + storage_meter, + BalanceOf::::zero(), + false, + )?; + + let stack = Self { + origin, + gas_meter, + storage_meter, + timestamp: T::Time::now(), + block_number: >::block_number(), + first_frame, + frames: Default::default(), + debug_message, + transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), + _phantom: Default::default(), + }; + + Ok((stack, executable)) + } + + /// Construct a new frame. + /// + /// This does not take `self` because when constructing the first frame `self` is + /// not initialized, yet. + fn new_frame( + frame_args: FrameArgs, + value_transferred: BalanceOf, + gas_meter: &mut GasMeter, + gas_limit: Weight, + storage_meter: &mut storage::meter::GenericMeter, + deposit_limit: BalanceOf, + read_only: bool, + ) -> Result<(Frame, E), ExecError> { + let (account_id, contract_info, executable, delegate_caller, entry_point) = match frame_args + { + FrameArgs::Call { dest, cached_info, delegated_call } => { + let contract = if let Some(contract) = cached_info { + contract + } else { + >::get(&dest).ok_or(>::ContractNotFound)? + }; + + let (executable, delegate_caller) = + if let Some(DelegatedCall { executable, caller }) = delegated_call { + (executable, Some(caller)) + } else { + (E::from_storage(contract.code_hash, gas_meter)?, None) + }; + + (dest, contract, executable, delegate_caller, ExportedFunction::Call) + }, + FrameArgs::Instantiate { sender, executable, salt, input_data } => { + let account_id = Contracts::::contract_address( + &sender, + &executable.code_hash(), + input_data, + salt, + ); + let contract = ContractInfo::new( + &account_id, + >::account_nonce(&sender), + *executable.code_hash(), + )?; + (account_id, contract, executable, None, ExportedFunction::Constructor) + }, + }; + + let frame = Frame { + delegate_caller, + value_transferred, + contract_info: CachedContract::Cached(contract_info), + account_id, + entry_point, + nested_gas: gas_meter.nested(gas_limit), + nested_storage: storage_meter.nested(deposit_limit), + allows_reentry: true, + read_only, + }; + + Ok((frame, executable)) + } + + /// Create a subsequent nested frame. + fn push_frame( + &mut self, + frame_args: FrameArgs, + value_transferred: BalanceOf, + gas_limit: Weight, + deposit_limit: BalanceOf, + read_only: bool, + ) -> Result { + if self.frames.len() as u32 == limits::CALL_STACK_DEPTH { + return Err(Error::::MaxCallDepthReached.into()) + } + + // We need to make sure that changes made to the contract info are not discarded. + // See the `in_memory_changes_not_discarded` test for more information. + // We do not store on instantiate because we do not allow to call into a contract + // from its own constructor. + let frame = self.top_frame(); + if let (CachedContract::Cached(contract), ExportedFunction::Call) = + (&frame.contract_info, frame.entry_point) + { + >::insert(frame.account_id.clone(), contract.clone()); + } + + let frame = top_frame_mut!(self); + let nested_gas = &mut frame.nested_gas; + let nested_storage = &mut frame.nested_storage; + let (frame, executable) = Self::new_frame( + frame_args, + value_transferred, + nested_gas, + gas_limit, + nested_storage, + deposit_limit, + read_only, + )?; + self.frames.try_push(frame).map_err(|_| Error::::MaxCallDepthReached)?; + Ok(executable) + } + + /// Run the current (top) frame. + /// + /// This can be either a call or an instantiate. + fn run(&mut self, executable: E, input_data: Vec) -> Result { + let frame = self.top_frame(); + let entry_point = frame.entry_point; + let delegated_code_hash = + if frame.delegate_caller.is_some() { Some(*executable.code_hash()) } else { None }; + + self.transient_storage.start_transaction(); + + let do_transaction = || { + // We need to charge the storage deposit before the initial transfer so that + // it can create the account in case the initial transfer is < ed. + if entry_point == ExportedFunction::Constructor { + // Root origin can't be used to instantiate a contract, so it is safe to assume that + // if we reached this point the origin has an associated account. + let origin = &self.origin.account_id()?; + let frame = top_frame_mut!(self); + frame.nested_storage.charge_instantiate( + origin, + &frame.account_id, + frame.contract_info.get(&frame.account_id), + executable.code_info(), + )?; + // Needs to be incremented before calling into the code so that it is visible + // in case of recursion. + >::inc_account_nonce(self.caller().account_id()?); + } + + // Every non delegate call or instantiate also optionally transfers the balance. + self.initial_transfer()?; + + let contract_address = &top_frame!(self).account_id; + + let call_span = T::Debug::new_call_span(contract_address, entry_point, &input_data); + + let output = T::Debug::intercept_call(contract_address, entry_point, &input_data) + .unwrap_or_else(|| { + executable + .execute(self, entry_point, input_data) + .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee }) + })?; + + call_span.after_call(&output); + + // Avoid useless work that would be reverted anyways. + if output.did_revert() { + return Ok(output) + } + + // Storage limit is normally enforced as late as possible (when the last frame returns) + // so that the ordering of storage accesses does not matter. + // (However, if a special limit was set for a sub-call, it should be enforced right + // after the sub-call returned. See below for this case of enforcement). + if self.frames.is_empty() { + let frame = &mut self.first_frame; + frame.contract_info.load(&frame.account_id); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_limit(contract)?; + } + + let frame = self.top_frame(); + let account_id = &frame.account_id.clone(); + match (entry_point, delegated_code_hash) { + (ExportedFunction::Constructor, _) => { + // It is not allowed to terminate a contract inside its constructor. + if matches!(frame.contract_info, CachedContract::Terminated) { + return Err(Error::::TerminatedInConstructor.into()) + } + + // If a special limit was set for the sub-call, we enforce it here. + // This is needed because contract constructor might write to storage. + // The sub-call will be rolled back in case the limit is exhausted. + let frame = self.top_frame_mut(); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_subcall_limit(contract)?; + + let caller = self.caller().account_id()?.clone(); + + // Deposit an instantiation event. + Contracts::::deposit_event(Event::Instantiated { + deployer: caller, + contract: account_id.clone(), + }); + }, + (ExportedFunction::Call, Some(code_hash)) => { + Contracts::::deposit_event(Event::DelegateCalled { + contract: account_id.clone(), + code_hash, + }); + }, + (ExportedFunction::Call, None) => { + // If a special limit was set for the sub-call, we enforce it here. + // The sub-call will be rolled back in case the limit is exhausted. + let frame = self.top_frame_mut(); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_subcall_limit(contract)?; + + let caller = self.caller(); + Contracts::::deposit_event(Event::Called { + caller: caller.clone(), + contract: account_id.clone(), + }); + }, + } + + Ok(output) + }; + + // All changes performed by the contract are executed under a storage transaction. + // This allows for roll back on error. Changes to the cached contract_info are + // committed or rolled back when popping the frame. + // + // `with_transactional` may return an error caused by a limit in the + // transactional storage depth. + let transaction_outcome = + with_transaction(|| -> TransactionOutcome> { + let output = do_transaction(); + match &output { + Ok(result) if !result.did_revert() => + TransactionOutcome::Commit(Ok((true, output))), + _ => TransactionOutcome::Rollback(Ok((false, output))), + } + }); + + let (success, output) = match transaction_outcome { + // `with_transactional` executed successfully, and we have the expected output. + Ok((success, output)) => (success, output), + // `with_transactional` returned an error, and we propagate that error and note no state + // has changed. + Err(error) => (false, Err(error.into())), + }; + + if success { + self.transient_storage.commit_transaction(); + } else { + self.transient_storage.rollback_transaction(); + } + + self.pop_frame(success); + output + } + + /// Remove the current (top) frame from the stack. + /// + /// This is called after running the current frame. It commits cached values to storage + /// and invalidates all stale references to it that might exist further down the call stack. + fn pop_frame(&mut self, persist: bool) { + // Pop the current frame from the stack and return it in case it needs to interact + // with duplicates that might exist on the stack. + // A `None` means that we are returning from the `first_frame`. + let frame = self.frames.pop(); + + // Both branches do essentially the same with the exception. The difference is that + // the else branch does consume the hardcoded `first_frame`. + if let Some(mut frame) = frame { + let account_id = &frame.account_id; + let prev = top_frame_mut!(self); + + prev.nested_gas.absorb_nested(frame.nested_gas); + + // Only gas counter changes are persisted in case of a failure. + if !persist { + return + } + + // Record the storage meter changes of the nested call into the parent meter. + // If the dropped frame's contract wasn't terminated we update the deposit counter + // in its contract info. The load is necessary to pull it from storage in case + // it was invalidated. + frame.contract_info.load(account_id); + let mut contract = frame.contract_info.into_contract(); + prev.nested_storage.absorb(frame.nested_storage, account_id, contract.as_mut()); + + // In case the contract wasn't terminated we need to persist changes made to it. + if let Some(contract) = contract { + // optimization: Predecessor is the same contract. + // We can just copy the contract into the predecessor without a storage write. + // This is possible when there is no other contract in-between that could + // trigger a rollback. + if prev.account_id == *account_id { + prev.contract_info = CachedContract::Cached(contract); + return + } + + // Predecessor is a different contract: We persist the info and invalidate the first + // stale cache we find. This triggers a reload from storage on next use. We skip(1) + // because that case is already handled by the optimization above. Only the first + // cache needs to be invalidated because that one will invalidate the next cache + // when it is popped from the stack. + >::insert(account_id, contract); + if let Some(c) = self.frames_mut().skip(1).find(|f| f.account_id == *account_id) { + c.contract_info = CachedContract::Invalidated; + } + } + } else { + if let Some((msg, false)) = self.debug_message.as_ref().map(|m| (m, m.is_empty())) { + log::debug!( + target: LOG_TARGET, + "Execution finished with debug buffer: {}", + core::str::from_utf8(msg).unwrap_or(""), + ); + } + self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas)); + if !persist { + return + } + let mut contract = self.first_frame.contract_info.as_contract(); + self.storage_meter.absorb( + mem::take(&mut self.first_frame.nested_storage), + &self.first_frame.account_id, + contract.as_deref_mut(), + ); + if let Some(contract) = contract { + >::insert(&self.first_frame.account_id, contract); + } + } + } + + /// Transfer some funds from `from` to `to`. + fn transfer( + preservation: Preservation, + from: &T::AccountId, + to: &T::AccountId, + value: BalanceOf, + ) -> DispatchResult { + if !value.is_zero() && from != to { + T::Currency::transfer(from, to, value, preservation) + .map_err(|_| Error::::TransferFailed)?; + } + Ok(()) + } + + // The transfer as performed by a call or instantiate. + fn initial_transfer(&self) -> DispatchResult { + let frame = self.top_frame(); + + // If it is a delegate call, then we've already transferred tokens in the + // last non-delegate frame. + if frame.delegate_caller.is_some() { + return Ok(()) + } + + let value = frame.value_transferred; + + // Get the account id from the caller. + // If the caller is root there is no account to transfer from, and therefore we can't take + // any `value` other than 0. + let caller = match self.caller() { + Origin::Signed(caller) => caller, + Origin::Root if value.is_zero() => return Ok(()), + Origin::Root => return DispatchError::RootNotAllowed.into(), + }; + Self::transfer(Preservation::Preserve, &caller, &frame.account_id, value) + } + + /// Reference to the current (top) frame. + fn top_frame(&self) -> &Frame { + top_frame!(self) + } + + /// Mutable reference to the current (top) frame. + fn top_frame_mut(&mut self) -> &mut Frame { + top_frame_mut!(self) + } + + /// Iterator over all frames. + /// + /// The iterator starts with the top frame and ends with the root frame. + fn frames(&self) -> impl Iterator> { + core::iter::once(&self.first_frame).chain(&self.frames).rev() + } + + /// Same as `frames` but with a mutable reference as iterator item. + fn frames_mut(&mut self) -> impl Iterator> { + core::iter::once(&mut self.first_frame).chain(&mut self.frames).rev() + } + + /// Returns whether the current contract is on the stack multiple times. + fn is_recursive(&self) -> bool { + let account_id = &self.top_frame().account_id; + self.frames().skip(1).any(|f| &f.account_id == account_id) + } + + /// Returns whether the specified contract allows to be reentered right now. + fn allows_reentry(&self, id: &AccountIdOf) -> bool { + !self.frames().any(|f| &f.account_id == id && !f.allows_reentry) + } +} + +impl<'a, T, E> Ext for Stack<'a, T, E> +where + T: Config, + E: Executable, +{ + type T = T; + + fn call( + &mut self, + gas_limit: Weight, + deposit_limit: BalanceOf, + to: T::AccountId, + value: BalanceOf, + input_data: Vec, + allows_reentry: bool, + read_only: bool, + ) -> Result { + // Before pushing the new frame: Protect the caller contract against reentrancy attacks. + // It is important to do this before calling `allows_reentry` so that a direct recursion + // is caught by it. + self.top_frame_mut().allows_reentry = allows_reentry; + + let try_call = || { + if !self.allows_reentry(&to) { + return Err(>::ReentranceDenied.into()) + } + + // We ignore instantiate frames in our search for a cached contract. + // Otherwise it would be possible to recursively call a contract from its own + // constructor: We disallow calling not fully constructed contracts. + let cached_info = self + .frames() + .find(|f| f.entry_point == ExportedFunction::Call && f.account_id == to) + .and_then(|f| match &f.contract_info { + CachedContract::Cached(contract) => Some(contract.clone()), + _ => None, + }); + let executable = self.push_frame( + FrameArgs::Call { dest: to, cached_info, delegated_call: None }, + value, + gas_limit, + deposit_limit, + // Enable read-only access if requested; cannot disable it if already set. + read_only || self.is_read_only(), + )?; + self.run(executable, input_data) + }; + + // We need to make sure to reset `allows_reentry` even on failure. + let result = try_call(); + + // Protection is on a per call basis. + self.top_frame_mut().allows_reentry = true; + + result + } + + fn delegate_call( + &mut self, + code_hash: CodeHash, + input_data: Vec, + ) -> Result { + let executable = E::from_storage(code_hash, self.gas_meter_mut())?; + let top_frame = self.top_frame_mut(); + let contract_info = top_frame.contract_info().clone(); + let account_id = top_frame.account_id.clone(); + let value = top_frame.value_transferred; + let executable = self.push_frame( + FrameArgs::Call { + dest: account_id, + cached_info: Some(contract_info), + delegated_call: Some(DelegatedCall { executable, caller: self.caller().clone() }), + }, + value, + Weight::zero(), + BalanceOf::::zero(), + self.is_read_only(), + )?; + self.run(executable, input_data) + } + + fn instantiate( + &mut self, + gas_limit: Weight, + deposit_limit: BalanceOf, + code_hash: CodeHash, + value: BalanceOf, + input_data: Vec, + salt: &[u8], + ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { + let executable = E::from_storage(code_hash, self.gas_meter_mut())?; + let sender = &self.top_frame().account_id; + let executable = self.push_frame( + FrameArgs::Instantiate { + sender: sender.clone(), + executable, + salt, + input_data: input_data.as_ref(), + }, + value, + gas_limit, + deposit_limit, + self.is_read_only(), + )?; + let account_id = self.top_frame().account_id.clone(); + self.run(executable, input_data).map(|ret| (account_id, ret)) + } + + fn terminate(&mut self, beneficiary: &AccountIdOf) -> DispatchResult { + if self.is_recursive() { + return Err(Error::::TerminatedWhileReentrant.into()) + } + let frame = self.top_frame_mut(); + let info = frame.terminate(); + frame.nested_storage.terminate(&info, beneficiary.clone()); + + info.queue_trie_for_deletion(); + ContractInfoOf::::remove(&frame.account_id); + Self::decrement_refcount(info.code_hash); + + for (code_hash, deposit) in info.delegate_dependencies() { + Self::decrement_refcount(*code_hash); + frame + .nested_storage + .charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(*deposit)); + } + + Contracts::::deposit_event(Event::Terminated { + contract: frame.account_id.clone(), + beneficiary: beneficiary.clone(), + }); + Ok(()) + } + + fn transfer(&mut self, to: &T::AccountId, value: BalanceOf) -> DispatchResult { + Self::transfer(Preservation::Preserve, &self.top_frame().account_id, to, value) + } + + fn get_storage(&mut self, key: &Key) -> Option> { + self.top_frame_mut().contract_info().read(key) + } + + fn get_storage_size(&mut self, key: &Key) -> Option { + self.top_frame_mut().contract_info().size(key.into()) + } + + fn set_storage( + &mut self, + key: &Key, + value: Option>, + take_old: bool, + ) -> Result { + let frame = self.top_frame_mut(); + frame.contract_info.get(&frame.account_id).write( + key.into(), + value, + Some(&mut frame.nested_storage), + take_old, + ) + } + + fn get_transient_storage(&self, key: &Key) -> Option> { + self.transient_storage.read(self.address(), key) + } + + fn get_transient_storage_size(&self, key: &Key) -> Option { + self.transient_storage.read(self.address(), key).map(|value| value.len() as _) + } + + fn set_transient_storage( + &mut self, + key: &Key, + value: Option>, + take_old: bool, + ) -> Result { + let account_id = self.address().clone(); + self.transient_storage.write(&account_id, key, value, take_old) + } + + fn address(&self) -> &T::AccountId { + &self.top_frame().account_id + } + + fn caller(&self) -> Origin { + if let Some(caller) = &self.top_frame().delegate_caller { + caller.clone() + } else { + self.frames() + .nth(1) + .map(|f| Origin::from_account_id(f.account_id.clone())) + .unwrap_or(self.origin.clone()) + } + } + + fn is_contract(&self, address: &T::AccountId) -> bool { + ContractInfoOf::::contains_key(&address) + } + + fn code_hash(&self, address: &T::AccountId) -> Option> { + >::get(&address).map(|contract| contract.code_hash) + } + + fn own_code_hash(&mut self) -> &CodeHash { + &self.top_frame_mut().contract_info().code_hash + } + + fn caller_is_origin(&self) -> bool { + self.origin == self.caller() + } + + fn caller_is_root(&self) -> bool { + // if the caller isn't origin, then it can't be root. + self.caller_is_origin() && self.origin == Origin::Root + } + + fn balance(&self) -> BalanceOf { + T::Currency::reducible_balance( + &self.top_frame().account_id, + Preservation::Preserve, + Fortitude::Polite, + ) + } + + fn value_transferred(&self) -> BalanceOf { + self.top_frame().value_transferred + } + + fn now(&self) -> &MomentOf { + &self.timestamp + } + + fn minimum_balance(&self) -> BalanceOf { + T::Currency::minimum_balance() + } + + fn deposit_event(&mut self, topics: Vec, data: Vec) { + Contracts::::deposit_indexed_event( + topics, + Event::ContractEmitted { contract: self.top_frame().account_id.clone(), data }, + ); + } + + fn block_number(&self) -> BlockNumberFor { + self.block_number + } + + fn max_value_size(&self) -> u32 { + limits::PAYLOAD_BYTES + } + + fn get_weight_price(&self, weight: Weight) -> BalanceOf { + T::WeightPrice::convert(weight) + } + + fn gas_meter(&self) -> &GasMeter { + &self.top_frame().nested_gas + } + + fn gas_meter_mut(&mut self) -> &mut GasMeter { + &mut self.top_frame_mut().nested_gas + } + + fn charge_storage(&mut self, diff: &Diff) { + self.top_frame_mut().nested_storage.charge(diff) + } + + fn debug_buffer_enabled(&self) -> bool { + self.debug_message.is_some() + } + + fn append_debug_buffer(&mut self, msg: &str) -> bool { + if let Some(buffer) = &mut self.debug_message { + buffer + .try_extend(&mut msg.bytes()) + .map_err(|_| { + log::debug!( + target: LOG_TARGET, + "Debug buffer (of {} bytes) exhausted!", + limits::DEBUG_BUFFER_BYTES, + ) + }) + .ok(); + true + } else { + false + } + } + + fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo { + let mut origin: T::RuntimeOrigin = RawOrigin::Signed(self.address().clone()).into(); + origin.add_filter(T::CallFilter::contains); + call.dispatch(origin) + } + + fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()> { + secp256k1_ecdsa_recover_compressed(signature, message_hash).map_err(|_| ()) + } + + fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool { + sp_io::crypto::sr25519_verify( + &SR25519Signature::from(*signature), + message, + &SR25519Public::from(*pub_key), + ) + } + + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> { + ECDSAPublic::from(*pk).to_eth_address() + } + + #[cfg(any(test, feature = "runtime-benchmarks"))] + fn contract_info(&mut self) -> &mut ContractInfo { + self.top_frame_mut().contract_info() + } + + #[cfg(feature = "runtime-benchmarks")] + fn transient_storage(&mut self) -> &mut TransientStorage { + &mut self.transient_storage + } + + fn set_code_hash(&mut self, hash: CodeHash) -> DispatchResult { + let frame = top_frame_mut!(self); + + let info = frame.contract_info(); + + let prev_hash = info.code_hash; + info.code_hash = hash; + + let code_info = CodeInfoOf::::get(hash).ok_or(Error::::CodeNotFound)?; + + let old_base_deposit = info.storage_base_deposit(); + let new_base_deposit = info.update_base_deposit(&code_info); + let deposit = StorageDeposit::Charge(new_base_deposit) + .saturating_sub(&StorageDeposit::Charge(old_base_deposit)); + + frame.nested_storage.charge_deposit(frame.account_id.clone(), deposit); + + Self::increment_refcount(hash)?; + Self::decrement_refcount(prev_hash); + Contracts::::deposit_event(Event::ContractCodeUpdated { + contract: frame.account_id.clone(), + new_code_hash: hash, + old_code_hash: prev_hash, + }); + Ok(()) + } + + fn increment_refcount(code_hash: CodeHash) -> DispatchResult { + >::mutate(code_hash, |existing| -> Result<(), DispatchError> { + if let Some(info) = existing { + *info.refcount_mut() = info.refcount().saturating_add(1); + Ok(()) + } else { + Err(Error::::CodeNotFound.into()) + } + }) + } + + fn decrement_refcount(code_hash: CodeHash) { + >::mutate(code_hash, |existing| { + if let Some(info) = existing { + *info.refcount_mut() = info.refcount().saturating_sub(1); + } + }); + } + + fn lock_delegate_dependency(&mut self, code_hash: CodeHash) -> DispatchResult { + let frame = self.top_frame_mut(); + let info = frame.contract_info.get(&frame.account_id); + ensure!(code_hash != info.code_hash, Error::::CannotAddSelfAsDelegateDependency); + + let code_info = CodeInfoOf::::get(code_hash).ok_or(Error::::CodeNotFound)?; + let deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit()); + + info.lock_delegate_dependency(code_hash, deposit)?; + Self::increment_refcount(code_hash)?; + frame + .nested_storage + .charge_deposit(frame.account_id.clone(), StorageDeposit::Charge(deposit)); + Ok(()) + } + + fn unlock_delegate_dependency(&mut self, code_hash: &CodeHash) -> DispatchResult { + let frame = self.top_frame_mut(); + let info = frame.contract_info.get(&frame.account_id); + + let deposit = info.unlock_delegate_dependency(code_hash)?; + Self::decrement_refcount(*code_hash); + frame + .nested_storage + .charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(deposit)); + Ok(()) + } + + fn locked_delegate_dependencies_count(&mut self) -> usize { + self.top_frame_mut().contract_info().delegate_dependencies_count() + } + + fn is_read_only(&self) -> bool { + self.top_frame().read_only + } +} + +mod sealing { + use super::*; + + pub trait Sealed {} + + impl<'a, T: Config, E> Sealed for Stack<'a, T, E> {} +} + +/// These tests exercise the executive layer. +/// +/// In these tests the VM/loader are mocked. Instead of dealing with wasm bytecode they use simple +/// closures. This allows you to tackle executive logic more thoroughly without writing a +/// wasm VM code. +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + exec::ExportedFunction::*, + gas::GasMeter, + test_utils::*, + tests::{ + test_utils::{get_balance, place_contract, set_balance}, + ExtBuilder, RuntimeCall, RuntimeEvent as MetaEvent, Test, TestFilter, + }, + Error, + }; + use assert_matches::assert_matches; + use codec::{Decode, Encode}; + use frame_support::{assert_err, assert_ok, parameter_types}; + use frame_system::{EventRecord, Phase}; + use pallet_revive_uapi::ReturnFlags; + use pretty_assertions::assert_eq; + use sp_runtime::{traits::Hash, DispatchError}; + use std::{cell::RefCell, collections::hash_map::HashMap, rc::Rc}; + + type System = frame_system::Pallet; + + type MockStack<'a> = Stack<'a, Test, MockExecutable>; + + parameter_types! { + static Loader: MockLoader = MockLoader::default(); + } + + fn events() -> Vec> { + System::events() + .into_iter() + .filter_map(|meta| match meta.event { + MetaEvent::Contracts(contract_event) => Some(contract_event), + _ => None, + }) + .collect() + } + + struct MockCtx<'a> { + ext: &'a mut MockStack<'a>, + input_data: Vec, + } + + #[derive(Clone)] + struct MockExecutable { + func: Rc Fn(MockCtx<'a>, &Self) -> ExecResult + 'static>, + func_type: ExportedFunction, + code_hash: CodeHash, + code_info: CodeInfo, + } + + #[derive(Default, Clone)] + pub struct MockLoader { + map: HashMap, MockExecutable>, + counter: u64, + } + + impl MockLoader { + fn code_hashes() -> Vec> { + Loader::get().map.keys().copied().collect() + } + + fn insert( + func_type: ExportedFunction, + f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static, + ) -> CodeHash { + Loader::mutate(|loader| { + // Generate code hashes as monotonically increasing values. + let hash = ::Hash::from_low_u64_be(loader.counter); + loader.counter += 1; + loader.map.insert( + hash, + MockExecutable { + func: Rc::new(f), + func_type, + code_hash: hash, + code_info: CodeInfo::::new(ALICE), + }, + ); + hash + }) + } + } + + impl Executable for MockExecutable { + fn from_storage( + code_hash: CodeHash, + _gas_meter: &mut GasMeter, + ) -> Result { + Loader::mutate(|loader| { + loader.map.get(&code_hash).cloned().ok_or(Error::::CodeNotFound.into()) + }) + } + + fn execute>( + self, + ext: &mut E, + function: ExportedFunction, + input_data: Vec, + ) -> ExecResult { + if let Constructor = function { + E::increment_refcount(self.code_hash).unwrap(); + } + // # Safety + // + // We know that we **always** call execute with a `MockStack` in this test. + // + // # Note + // + // The transmute is necessary because `execute` has to be generic over all + // `E: Ext`. However, `MockExecutable` can't be generic over `E` as it would + // constitute a cycle. + let ext = unsafe { mem::transmute(ext) }; + if function == self.func_type { + (self.func)(MockCtx { ext, input_data }, &self) + } else { + exec_success() + } + } + + fn code_hash(&self) -> &CodeHash { + &self.code_hash + } + + fn code_info(&self) -> &CodeInfo { + &self.code_info + } + } + + fn exec_success() -> ExecResult { + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + } + + fn exec_trapped() -> ExecResult { + Err(ExecError { error: >::ContractTrapped.into(), origin: ErrorOrigin::Callee }) + } + + #[test] + fn it_works() { + parameter_types! { + static TestData: Vec = vec![0]; + } + + let value = Default::default(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let exec_ch = MockLoader::insert(Call, |_ctx, _executable| { + TestData::mutate(|data| data.push(1)); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, exec_ch); + let mut storage_meter = + storage::meter::Meter::new(&Origin::from_account_id(ALICE), 0, value).unwrap(); + + assert_matches!( + MockStack::run_call( + Origin::from_account_id(ALICE), + BOB, + &mut gas_meter, + &mut storage_meter, + value, + vec![], + None, + ), + Ok(_) + ); + }); + + assert_eq!(TestData::get(), vec![0, 1]); + } + + #[test] + fn transfer_works() { + // This test verifies that a contract is able to transfer + // some funds to another account. + let origin = ALICE; + let dest = BOB; + + ExtBuilder::default().build().execute_with(|| { + set_balance(&origin, 100); + set_balance(&dest, 0); + + MockStack::transfer(Preservation::Preserve, &origin, &dest, 55).unwrap(); + + assert_eq!(get_balance(&origin), 45); + assert_eq!(get_balance(&dest), 55); + }); + } + + #[test] + fn correct_transfer_on_call() { + let origin = ALICE; + let dest = BOB; + let value = 55; + + let success_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&dest, success_ch); + set_balance(&origin, 100); + let balance = get_balance(&dest); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, value).unwrap(); + + let _ = MockStack::run_call( + contract_origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + value, + vec![], + None, + ) + .unwrap(); + + assert_eq!(get_balance(&origin), 100 - value); + assert_eq!(get_balance(&dest), balance + value); + }); + } + + #[test] + fn correct_transfer_on_delegate_call() { + let origin = ALICE; + let dest = BOB; + let value = 35; + + let success_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + }); + + let delegate_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + let _ = ctx.ext.delegate_call(success_ch, Vec::new())?; + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&dest, delegate_ch); + set_balance(&origin, 100); + let balance = get_balance(&dest); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 55).unwrap(); + + let _ = MockStack::run_call( + contract_origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + value, + vec![], + None, + ) + .unwrap(); + + assert_eq!(get_balance(&origin), 100 - value); + assert_eq!(get_balance(&dest), balance + value); + }); + } + + #[test] + fn changes_are_reverted_on_failing_call() { + // This test verifies that changes are reverted on a call which fails (or equally, returns + // a non-zero status code). + let origin = ALICE; + let dest = BOB; + + let return_ch = MockLoader::insert(Call, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&dest, return_ch); + set_balance(&origin, 100); + let balance = get_balance(&dest); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 55).unwrap(); + + let output = MockStack::run_call( + contract_origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 55, + vec![], + None, + ) + .unwrap(); + + assert!(output.did_revert()); + assert_eq!(get_balance(&origin), 100); + assert_eq!(get_balance(&dest), balance); + }); + } + + #[test] + fn balance_too_low() { + // This test verifies that a contract can't send value if it's + // balance is too low. + let origin = ALICE; + let dest = BOB; + + ExtBuilder::default().build().execute_with(|| { + set_balance(&origin, 0); + + let result = MockStack::transfer(Preservation::Preserve, &origin, &dest, 100); + + assert_eq!(result, Err(Error::::TransferFailed.into())); + assert_eq!(get_balance(&origin), 0); + assert_eq!(get_balance(&dest), 0); + }); + } + + #[test] + fn output_is_returned_on_success() { + // Verifies that if a contract returns data with a successful exit status, this data + // is returned from the execution context. + let origin = ALICE; + let dest = BOB; + let return_ch = MockLoader::insert(Call, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] }) + }); + + ExtBuilder::default().build().execute_with(|| { + let contract_origin = Origin::from_account_id(origin); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + place_contract(&BOB, return_ch); + + let result = MockStack::run_call( + contract_origin, + dest, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + + let output = result.unwrap(); + assert!(!output.did_revert()); + assert_eq!(output.data, vec![1, 2, 3, 4]); + }); + } + + #[test] + fn output_is_returned_on_failure() { + // Verifies that if a contract returns data with a failing exit status, this data + // is returned from the execution context. + let origin = ALICE; + let dest = BOB; + let return_ch = MockLoader::insert(Call, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, return_ch); + let contract_origin = Origin::from_account_id(origin); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + dest, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + + let output = result.unwrap(); + assert!(output.did_revert()); + assert_eq!(output.data, vec![1, 2, 3, 4]); + }); + } + + #[test] + fn input_data_to_call() { + let input_data_ch = MockLoader::insert(Call, |ctx, _| { + assert_eq!(ctx.input_data, &[1, 2, 3, 4]); + exec_success() + }); + + // This one tests passing the input data into a contract via call. + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, input_data_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![1, 2, 3, 4], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn input_data_to_instantiate() { + let input_data_ch = MockLoader::insert(Constructor, |ctx, _| { + assert_eq!(ctx.input_data, &[1, 2, 3, 4]); + exec_success() + }); + + // This one tests passing the input data into a contract via instantiate. + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = + MockExecutable::from_storage(input_data_ch, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 10_000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new( + &contract_origin, + deposit_limit::(), + min_balance, + ) + .unwrap(); + + let result = MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + min_balance, + vec![1, 2, 3, 4], + &[], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn max_depth() { + // This test verifies that when we reach the maximal depth creation of an + // yet another context fails. + parameter_types! { + static ReachedBottom: bool = false; + } + let value = Default::default(); + let recurse_ch = MockLoader::insert(Call, |ctx, _| { + // Try to call into yourself. + let r = ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + BOB, + 0, + vec![], + true, + false, + ); + + ReachedBottom::mutate(|reached_bottom| { + if !*reached_bottom { + // We are first time here, it means we just reached bottom. + // Verify that we've got proper error and set `reached_bottom`. + assert_eq!(r, Err(Error::::MaxCallDepthReached.into())); + *reached_bottom = true; + } else { + // We just unwinding stack here. + assert_matches!(r, Ok(_)); + } + }); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + set_balance(&BOB, 1); + place_contract(&BOB, recurse_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, value).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + value, + vec![], + None, + ); + + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn caller_returns_proper_values() { + let origin = ALICE; + let dest = BOB; + + parameter_types! { + static WitnessedCallerBob: Option> = None; + static WitnessedCallerCharlie: Option> = None; + } + + let bob_ch = MockLoader::insert(Call, |ctx, _| { + // Record the caller for bob. + WitnessedCallerBob::mutate(|caller| { + *caller = Some(ctx.ext.caller().account_id().unwrap().clone()) + }); + + // Call into CHARLIE contract. + assert_matches!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true, + false + ), + Ok(_) + ); + exec_success() + }); + let charlie_ch = MockLoader::insert(Call, |ctx, _| { + // Record the caller for charlie. + WitnessedCallerCharlie::mutate(|caller| { + *caller = Some(ctx.ext.caller().account_id().unwrap().clone()) + }); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&dest, bob_ch); + place_contract(&CHARLIE, charlie_ch); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + + assert_matches!(result, Ok(_)); + }); + + assert_eq!(WitnessedCallerBob::get(), Some(origin)); + assert_eq!(WitnessedCallerCharlie::get(), Some(dest)); + } + + #[test] + fn is_contract_returns_proper_values() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + // Verify that BOB is a contract + assert!(ctx.ext.is_contract(&BOB)); + // Verify that ALICE is not a contract + assert!(!ctx.ext.is_contract(&ALICE)); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_ch); + + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn code_hash_returns_proper_values() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + // ALICE is not a contract and hence they do not have a code_hash + assert!(ctx.ext.code_hash(&ALICE).is_none()); + // BOB is a contract and hence it has a code_hash + assert!(ctx.ext.code_hash(&BOB).is_some()); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // ALICE (not contract) -> BOB (contract) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn own_code_hash_returns_proper_values() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + let code_hash = ctx.ext.code_hash(&BOB).unwrap(); + assert_eq!(*ctx.ext.own_code_hash(), code_hash); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // ALICE (not contract) -> BOB (contract) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn caller_is_origin_returns_proper_values() { + let code_charlie = MockLoader::insert(Call, |ctx, _| { + // BOB is not the origin of the stack call + assert!(!ctx.ext.caller_is_origin()); + exec_success() + }); + + let code_bob = MockLoader::insert(Call, |ctx, _| { + // ALICE is the origin of the call stack + assert!(ctx.ext.caller_is_origin()); + // BOB calls CHARLIE + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true, false) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // ALICE -> BOB (caller is origin) -> CHARLIE (caller is not origin) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn root_caller_succeeds() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + // root is the origin of the call stack. + assert!(ctx.ext.caller_is_root()); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + let contract_origin = Origin::Root; + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // root -> BOB (caller is root) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn root_caller_does_not_succeed_when_value_not_zero() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + // root is the origin of the call stack. + assert!(ctx.ext.caller_is_root()); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + let contract_origin = Origin::Root; + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // root -> BOB (caller is root) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 1, + vec![0], + None, + ); + assert_matches!(result, Err(_)); + }); + } + + #[test] + fn root_caller_succeeds_with_consecutive_calls() { + let code_charlie = MockLoader::insert(Call, |ctx, _| { + // BOB is not root, even though the origin is root. + assert!(!ctx.ext.caller_is_root()); + exec_success() + }); + + let code_bob = MockLoader::insert(Call, |ctx, _| { + // root is the origin of the call stack. + assert!(ctx.ext.caller_is_root()); + // BOB calls CHARLIE. + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true, false) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::Root; + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + // root -> BOB (caller is root) -> CHARLIE (caller is not root) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn address_returns_proper_values() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + // Verify that address matches BOB. + assert_eq!(*ctx.ext.address(), BOB); + + // Call into charlie contract. + assert_matches!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true, + false + ), + Ok(_) + ); + exec_success() + }); + let charlie_ch = MockLoader::insert(Call, |ctx, _| { + assert_eq!(*ctx.ext.address(), CHARLIE); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_ch); + place_contract(&CHARLIE, charlie_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn refuse_instantiate_with_value_below_existential_deposit() { + let dummy_ch = MockLoader::insert(Constructor, |_, _| exec_success()); + + ExtBuilder::default().existential_deposit(15).build().execute_with(|| { + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + assert_matches!( + MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + 0, // <- zero value + vec![], + &[], + None, + ), + Err(_) + ); + }); + } + + #[test] + fn instantiation_work_with_success_output() { + let dummy_ch = MockLoader::insert(Constructor, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] }) + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 1000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, min_balance * 100, min_balance) + .unwrap(); + + let instantiated_contract_address = assert_matches!( + MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + + min_balance, + vec![], + &[], + None, + ), + Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address + ); + + // Check that the newly created account has the expected code hash and + // there are instantiation event. + assert_eq!( + ContractInfo::::load_code_hash(&instantiated_contract_address).unwrap(), + dummy_ch + ); + assert_eq!( + &events(), + &[Event::Instantiated { + deployer: ALICE, + contract: instantiated_contract_address + }] + ); + }); + } + + #[test] + fn instantiation_fails_with_failing_output() { + let dummy_ch = MockLoader::insert(Constructor, |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] }) + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 1000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, min_balance * 100, min_balance) + .unwrap(); + + let instantiated_contract_address = assert_matches!( + MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + + min_balance, + vec![], + &[], + None, + ), + Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address + ); + + // Check that the account has not been created. + assert!( + ContractInfo::::load_code_hash(&instantiated_contract_address).is_none() + ); + assert!(events().is_empty()); + }); + } + + #[test] + fn instantiation_from_contract() { + let dummy_ch = MockLoader::insert(Call, |_, _| exec_success()); + let instantiated_contract_address = Rc::new(RefCell::new(None::>)); + let instantiator_ch = MockLoader::insert(Call, { + let instantiated_contract_address = Rc::clone(&instantiated_contract_address); + move |ctx, _| { + // Instantiate a contract and save it's address in `instantiated_contract_address`. + let (address, output) = ctx + .ext + .instantiate( + Weight::zero(), + BalanceOf::::zero(), + dummy_ch, + ::Currency::minimum_balance(), + vec![], + &[48, 49, 50], + ) + .unwrap(); + + *instantiated_contract_address.borrow_mut() = address.into(); + Ok(output) + } + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + set_balance(&ALICE, min_balance * 100); + place_contract(&BOB, instantiator_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new( + &contract_origin, + min_balance * 10, + min_balance * 10, + ) + .unwrap(); + + assert_matches!( + MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + min_balance * 10, + vec![], + None, + ), + Ok(_) + ); + + let instantiated_contract_address = + instantiated_contract_address.borrow().as_ref().unwrap().clone(); + + // Check that the newly created account has the expected code hash and + // there are instantiation event. + assert_eq!( + ContractInfo::::load_code_hash(&instantiated_contract_address).unwrap(), + dummy_ch + ); + assert_eq!( + &events(), + &[ + Event::Instantiated { + deployer: BOB, + contract: instantiated_contract_address + }, + Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB }, + ] + ); + }); + } + + #[test] + fn instantiation_traps() { + let dummy_ch = MockLoader::insert(Constructor, |_, _| Err("It's a trap!".into())); + let instantiator_ch = MockLoader::insert(Call, { + move |ctx, _| { + // Instantiate a contract and save it's address in `instantiated_contract_address`. + assert_matches!( + ctx.ext.instantiate( + Weight::zero(), + BalanceOf::::zero(), + dummy_ch, + ::Currency::minimum_balance(), + vec![], + &[], + ), + Err(ExecError { + error: DispatchError::Other("It's a trap!"), + origin: ErrorOrigin::Callee, + }) + ); + + exec_success() + } + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + set_balance(&ALICE, 1000); + set_balance(&BOB, 100); + place_contract(&BOB, instantiator_ch); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, 200, 0).unwrap(); + + assert_matches!( + MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ), + Ok(_) + ); + + // The contract wasn't instantiated so we don't expect to see an instantiation + // event here. + assert_eq!( + &events(), + &[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },] + ); + }); + } + + #[test] + fn termination_from_instantiate_fails() { + let terminate_ch = MockLoader::insert(Constructor, |ctx, _| { + ctx.ext.terminate(&ALICE).unwrap(); + exec_success() + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = + MockExecutable::from_storage(terminate_ch, &mut gas_meter).unwrap(); + set_balance(&ALICE, 10_000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 100) + .unwrap(); + + assert_eq!( + MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + 100, + vec![], + &[], + None, + ), + Err(Error::::TerminatedInConstructor.into()) + ); + + assert_eq!(&events(), &[]); + }); + } + + #[test] + fn in_memory_changes_not_discarded() { + // Call stack: BOB -> CHARLIE (trap) -> BOB' (success) + // This tests verifies some edge case of the contract info cache: + // We change some value in our contract info before calling into a contract + // that calls into ourself. This triggers a case where BOBs contract info + // is written to storage and invalidated by the successful execution of BOB'. + // The trap of CHARLIE reverts the storage changes to BOB. When the root BOB regains + // control it reloads its contract info from storage. We check that changes that + // are made before calling into CHARLIE are not discarded. + let code_bob = MockLoader::insert(Call, |ctx, _| { + if ctx.input_data[0] == 0 { + let info = ctx.ext.contract_info(); + assert_eq!(info.storage_byte_deposit, 0); + info.storage_byte_deposit = 42; + assert_eq!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true, + false + ), + exec_trapped() + ); + assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42); + } + exec_success() + }); + let code_charlie = MockLoader::insert(Call, |ctx, _| { + assert!(ctx + .ext + .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![99], true, false) + .is_ok()); + exec_trapped() + }); + + // This one tests passing the input data into a contract via call. + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn recursive_call_during_constructor_fails() { + let code = MockLoader::insert(Constructor, |ctx, _| { + assert_matches!( + ctx.ext.call(Weight::zero(), BalanceOf::::zero(), ctx.ext.address().clone(), 0, vec![], true, false), + Err(ExecError{error, ..}) if error == >::ContractNotFound.into() + ); + exec_success() + }); + + // This one tests passing the input data into a contract via instantiate. + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let executable = MockExecutable::from_storage(code, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 10_000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new( + &contract_origin, + deposit_limit::(), + min_balance, + ) + .unwrap(); + + let result = MockStack::run_instantiate( + ALICE, + executable, + &mut gas_meter, + &mut storage_meter, + min_balance, + vec![], + &[], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn printing_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + ctx.ext.append_debug_buffer("This is a test"); + ctx.ext.append_debug_buffer("More text"); + exec_success() + }); + + let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + Some(&mut debug_buffer), + ) + .unwrap(); + }); + + assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); + } + + #[test] + fn printing_works_on_fail() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + ctx.ext.append_debug_buffer("This is a test"); + ctx.ext.append_debug_buffer("More text"); + exec_trapped() + }); + + let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + let result = MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + Some(&mut debug_buffer), + ); + assert!(result.is_err()); + }); + + assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); + } + + #[test] + fn debug_buffer_is_limited() { + let code_hash = MockLoader::insert(Call, move |ctx, _| { + ctx.ext.append_debug_buffer("overflowing bytes"); + exec_success() + }); + + // Pre-fill the buffer almost up to its limit, leaving not enough space to the message + let debug_buf_before = DebugBuffer::try_from(vec![0u8; DebugBuffer::bound() - 5]).unwrap(); + let mut debug_buf_after = debug_buf_before.clone(); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + Some(&mut debug_buf_after), + ) + .unwrap(); + assert_eq!(debug_buf_before, debug_buf_after); + }); + } + + #[test] + fn call_reentry_direct_recursion() { + // call the contract passed as input with disabled reentry + let code_bob = MockLoader::insert(Call, |ctx, _| { + let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap(); + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), dest, 0, vec![], false, false) + }); + + let code_charlie = MockLoader::insert(Call, |_, _| exec_success()); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + // Calling another contract should succeed + assert_ok!(MockStack::run_call( + contract_origin.clone(), + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + CHARLIE.encode(), + None, + )); + + // Calling into oneself fails + assert_err!( + MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + BOB.encode(), + None, + ) + .map_err(|e| e.error), + >::ReentranceDenied, + ); + }); + } + + #[test] + fn call_deny_reentry() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + if ctx.input_data[0] == 0 { + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + false, + false, + ) + } else { + exec_success() + } + }); + + // call BOB with input set to '1' + let code_charlie = MockLoader::insert(Call, |ctx, _| { + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![1], true, false) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + // BOB -> CHARLIE -> BOB fails as BOB denies reentry. + assert_err!( + MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ) + .map_err(|e| e.error), + >::ReentranceDenied, + ); + }); + } + + #[test] + fn call_runtime_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + let call = RuntimeCall::System(frame_system::Call::remark_with_event { + remark: b"Hello World".to_vec(), + }); + ctx.ext.call_runtime(call).unwrap(); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + System::reset_events(); + MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + ) + .unwrap(); + + let remark_hash = ::Hashing::hash(b"Hello World"); + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::System(frame_system::Event::Remarked { + sender: BOB, + hash: remark_hash + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: BOB, + }), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn call_runtime_filter() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + use frame_system::Call as SysCall; + use pallet_balances::Call as BalanceCall; + use pallet_utility::Call as UtilCall; + + // remark should still be allowed + let allowed_call = + RuntimeCall::System(SysCall::remark_with_event { remark: b"Hello".to_vec() }); + + // transfers are disallowed by the `TestFiler` (see below) + let forbidden_call = RuntimeCall::Balances(BalanceCall::transfer_allow_death { + dest: CHARLIE, + value: 22, + }); + + // simple cases: direct call + assert_err!( + ctx.ext.call_runtime(forbidden_call.clone()), + frame_system::Error::::CallFiltered + ); + + // as part of a patch: return is OK (but it interrupted the batch) + assert_ok!(ctx.ext.call_runtime(RuntimeCall::Utility(UtilCall::batch { + calls: vec![allowed_call.clone(), forbidden_call, allowed_call] + })),); + + // the transfer wasn't performed + assert_eq!(get_balance(&CHARLIE), 0); + + exec_success() + }); + + TestFilter::set_filter(|call| match call { + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) => false, + _ => true, + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 10); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + System::reset_events(); + MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + ) + .unwrap(); + + let remark_hash = ::Hashing::hash(b"Hello"); + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::System(frame_system::Event::Remarked { + sender: BOB, + hash: remark_hash + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::Utility(pallet_utility::Event::ItemCompleted), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::Utility(pallet_utility::Event::BatchInterrupted { + index: 1, + error: frame_system::Error::::CallFiltered.into() + },), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: MetaEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: BOB, + }), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn nonce() { + let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped()); + let success_code = MockLoader::insert(Constructor, |_, _| exec_success()); + let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| { + ctx.ext + .instantiate( + Weight::zero(), + BalanceOf::::zero(), + fail_code, + ctx.ext.minimum_balance() * 100, + vec![], + &[], + ) + .ok(); + exec_success() + }); + let succ_succ_code = MockLoader::insert(Constructor, move |ctx, _| { + let alice_nonce = System::account_nonce(&ALICE); + assert_eq!(System::account_nonce(ctx.ext.address()), 0); + assert_eq!(ctx.ext.caller().account_id().unwrap(), &ALICE); + let (account_id, _) = ctx + .ext + .instantiate( + Weight::zero(), + BalanceOf::::zero(), + success_code, + ctx.ext.minimum_balance() * 100, + vec![], + &[], + ) + .unwrap(); + + assert_eq!(System::account_nonce(&ALICE), alice_nonce); + assert_eq!(System::account_nonce(ctx.ext.address()), 1); + assert_eq!(System::account_nonce(&account_id), 0); + + // a plain call should not influence the account counter + ctx.ext + .call( + Weight::zero(), + BalanceOf::::zero(), + account_id.clone(), + 0, + vec![], + false, + false, + ) + .unwrap(); + + assert_eq!(System::account_nonce(ALICE), alice_nonce); + assert_eq!(System::account_nonce(ctx.ext.address()), 1); + assert_eq!(System::account_nonce(&account_id), 0); + + exec_success() + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .build() + .execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + let fail_executable = + MockExecutable::from_storage(fail_code, &mut gas_meter).unwrap(); + let success_executable = + MockExecutable::from_storage(success_code, &mut gas_meter).unwrap(); + let succ_fail_executable = + MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap(); + let succ_succ_executable = + MockExecutable::from_storage(succ_succ_code, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 10_000); + set_balance(&BOB, min_balance * 10_000); + let contract_origin = Origin::from_account_id(BOB); + let mut storage_meter = storage::meter::Meter::new( + &contract_origin, + deposit_limit::(), + min_balance * 100, + ) + .unwrap(); + + // fail should not increment + MockStack::run_instantiate( + ALICE, + fail_executable, + &mut gas_meter, + &mut storage_meter, + min_balance * 100, + vec![], + &[], + None, + ) + .ok(); + assert_eq!(System::account_nonce(&ALICE), 0); + + assert_ok!(MockStack::run_instantiate( + ALICE, + success_executable, + &mut gas_meter, + &mut storage_meter, + min_balance * 100, + vec![], + &[], + None, + )); + assert_eq!(System::account_nonce(&ALICE), 1); + + assert_ok!(MockStack::run_instantiate( + ALICE, + succ_fail_executable, + &mut gas_meter, + &mut storage_meter, + min_balance * 200, + vec![], + &[], + None, + )); + assert_eq!(System::account_nonce(&ALICE), 2); + + assert_ok!(MockStack::run_instantiate( + ALICE, + succ_succ_executable, + &mut gas_meter, + &mut storage_meter, + min_balance * 200, + vec![], + &[], + None, + )); + assert_eq!(System::account_nonce(&ALICE), 3); + }); + } + + #[test] + fn set_storage_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + // Write + assert_eq!( + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![4, 5, 6]), true), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.set_storage(&Key::Fix([3; 32]), None, false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&Key::Fix([4; 32]), None, true), Ok(WriteOutcome::New)); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([5; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([6; 32]), Some(vec![]), true), + Ok(WriteOutcome::New) + ); + + // Overwrite + assert_eq!( + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![42]), false), + Ok(WriteOutcome::Overwritten(3)) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![48]), true), + Ok(WriteOutcome::Taken(vec![4, 5, 6])) + ); + assert_eq!(ctx.ext.set_storage(&Key::Fix([3; 32]), None, false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&Key::Fix([4; 32]), None, true), Ok(WriteOutcome::New)); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([5; 32]), Some(vec![]), false), + Ok(WriteOutcome::Overwritten(0)) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([6; 32]), Some(vec![]), true), + Ok(WriteOutcome::Taken(vec![])) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn set_storage_varsized_key_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + // Write + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([1; 64].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([2; 19].to_vec()).unwrap(), + Some(vec![4, 5, 6]), + true + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::try_from_var([3; 19].to_vec()).unwrap(), None, false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::try_from_var([4; 64].to_vec()).unwrap(), None, true), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([5; 30].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([6; 128].to_vec()).unwrap(), + Some(vec![]), + true + ), + Ok(WriteOutcome::New) + ); + + // Overwrite + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([1; 64].to_vec()).unwrap(), + Some(vec![42, 43, 44]), + false + ), + Ok(WriteOutcome::Overwritten(3)) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([2; 19].to_vec()).unwrap(), + Some(vec![48]), + true + ), + Ok(WriteOutcome::Taken(vec![4, 5, 6])) + ); + assert_eq!( + ctx.ext.set_storage(&Key::try_from_var([3; 19].to_vec()).unwrap(), None, false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::try_from_var([4; 64].to_vec()).unwrap(), None, true), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([5; 30].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::Overwritten(0)) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([6; 128].to_vec()).unwrap(), + Some(vec![]), + true + ), + Ok(WriteOutcome::Taken(vec![])) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_storage_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.get_storage(&Key::Fix([1; 32])), Some(vec![1, 2, 3])); + assert_eq!(ctx.ext.get_storage(&Key::Fix([2; 32])), Some(vec![])); + assert_eq!(ctx.ext.get_storage(&Key::Fix([3; 32])), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_storage_size_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.get_storage_size(&Key::Fix([1; 32])), Some(3)); + assert_eq!(ctx.ext.get_storage_size(&Key::Fix([2; 32])), Some(0)); + assert_eq!(ctx.ext.get_storage_size(&Key::Fix([3; 32])), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_storage_varsized_key_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([1; 19].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([2; 16].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.get_storage(&Key::try_from_var([1; 19].to_vec()).unwrap()), + Some(vec![1, 2, 3]) + ); + assert_eq!( + ctx.ext.get_storage(&Key::try_from_var([2; 16].to_vec()).unwrap()), + Some(vec![]) + ); + assert_eq!(ctx.ext.get_storage(&Key::try_from_var([3; 8].to_vec()).unwrap()), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_storage_size_varsized_key_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([1; 19].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage( + &Key::try_from_var([2; 16].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.get_storage_size(&Key::try_from_var([1; 19].to_vec()).unwrap()), + Some(3) + ); + assert_eq!( + ctx.ext.get_storage_size(&Key::try_from_var([2; 16].to_vec()).unwrap()), + Some(0) + ); + assert_eq!( + ctx.ext.get_storage_size(&Key::try_from_var([3; 8].to_vec()).unwrap()), + None + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut gas_meter, + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn set_transient_storage_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + // Write + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([2; 32]), Some(vec![4, 5, 6]), true), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([3; 32]), None, false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([4; 32]), None, true), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([5; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([6; 32]), Some(vec![]), true), + Ok(WriteOutcome::New) + ); + + // Overwrite + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([1; 32]), Some(vec![42]), false), + Ok(WriteOutcome::Overwritten(3)) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([2; 32]), Some(vec![48]), true), + Ok(WriteOutcome::Taken(vec![4, 5, 6])) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([3; 32]), None, false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([4; 32]), None, true), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([5; 32]), Some(vec![]), false), + Ok(WriteOutcome::Overwritten(0)) + ); + assert_eq!( + ctx.ext.set_transient_storage(&Key::Fix([6; 32]), Some(vec![]), true), + Ok(WriteOutcome::Taken(vec![])) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, deposit_limit::(), 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_transient_storage_works() { + // Call stack: BOB -> CHARLIE(success) -> BOB' (success) + let storage_key_1 = &Key::Fix([1; 32]); + let storage_key_2 = &Key::Fix([2; 32]); + let storage_key_3 = &Key::Fix([3; 32]); + let code_bob = MockLoader::insert(Call, |ctx, _| { + if ctx.input_data[0] == 0 { + assert_eq!( + ctx.ext.set_transient_storage(storage_key_1, Some(vec![1, 2]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true, + false, + ), + exec_success() + ); + assert_eq!(ctx.ext.get_transient_storage(storage_key_1), Some(vec![3])); + assert_eq!(ctx.ext.get_transient_storage(storage_key_2), Some(vec![])); + assert_eq!(ctx.ext.get_transient_storage(storage_key_3), None); + } else { + assert_eq!( + ctx.ext.set_transient_storage(storage_key_1, Some(vec![3]), true), + Ok(WriteOutcome::Taken(vec![1, 2])) + ); + assert_eq!( + ctx.ext.set_transient_storage(storage_key_2, Some(vec![]), false), + Ok(WriteOutcome::New) + ); + } + exec_success() + }); + let code_charlie = MockLoader::insert(Call, |ctx, _| { + assert!(ctx + .ext + .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![99], true, false) + .is_ok()); + // CHARLIE can not read BOB`s storage. + assert_eq!(ctx.ext.get_transient_storage(storage_key_1), None); + exec_success() + }); + + // This one tests passing the input data into a contract via call. + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn get_transient_storage_size_works() { + let storage_key_1 = &Key::Fix([1; 32]); + let storage_key_2 = &Key::Fix([2; 32]); + let storage_key_3 = &Key::Fix([3; 32]); + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_transient_storage(storage_key_1, Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_transient_storage(storage_key_2, Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.get_transient_storage_size(storage_key_1), Some(3)); + assert_eq!(ctx.ext.get_transient_storage_size(storage_key_2), Some(0)); + assert_eq!(ctx.ext.get_transient_storage_size(storage_key_3), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_hash); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + assert_ok!(MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn rollback_transient_storage_works() { + // Call stack: BOB -> CHARLIE (trap) -> BOB' (success) + let storage_key = &Key::Fix([1; 32]); + let code_bob = MockLoader::insert(Call, |ctx, _| { + if ctx.input_data[0] == 0 { + assert_eq!( + ctx.ext.set_transient_storage(storage_key, Some(vec![1, 2]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true, + false + ), + exec_trapped() + ); + assert_eq!(ctx.ext.get_transient_storage(storage_key), Some(vec![1, 2])); + } else { + let overwritten_length = ctx.ext.get_transient_storage_size(storage_key).unwrap(); + assert_eq!( + ctx.ext.set_transient_storage(storage_key, Some(vec![3]), false), + Ok(WriteOutcome::Overwritten(overwritten_length)) + ); + assert_eq!(ctx.ext.get_transient_storage(storage_key), Some(vec![3])); + } + exec_success() + }); + let code_charlie = MockLoader::insert(Call, |ctx, _| { + assert!(ctx + .ext + .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![99], true, false) + .is_ok()); + exec_trapped() + }); + + // This one tests passing the input data into a contract via call. + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn ecdsa_to_eth_address_returns_proper_value() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + let pubkey_compressed = array_bytes::hex2array_unchecked( + "028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd91", + ); + assert_eq!( + ctx.ext.ecdsa_to_eth_address(&pubkey_compressed).unwrap(), + array_bytes::hex2array_unchecked::<_, 20>( + "09231da7b19A016f9e576d23B16277062F4d46A8" + ) + ); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_ch); + + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, 0, 0).unwrap(); + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + assert_matches!(result, Ok(_)); + }); + } +} diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs new file mode 100644 index 000000000000..2034f39e9bc5 --- /dev/null +++ b/substrate/frame/revive/src/gas.rs @@ -0,0 +1,416 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{exec::ExecError, weights::WeightInfo, Config, Error}; +use core::marker::PhantomData; +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo}, + weights::Weight, + DefaultNoBound, +}; +use sp_runtime::{traits::Zero, DispatchError}; + +#[cfg(test)] +use std::{any::Any, fmt::Debug}; + +#[derive(Debug, PartialEq, Eq)] +pub struct ChargedAmount(Weight); + +impl ChargedAmount { + pub fn amount(&self) -> Weight { + self.0 + } +} + +/// Meter for syncing the gas between the executor and the gas meter. +#[derive(DefaultNoBound)] +struct EngineMeter { + fuel: u64, + _phantom: PhantomData, +} + +impl EngineMeter { + /// Create a meter with the given fuel limit. + fn new(limit: Weight) -> Self { + Self { + fuel: limit.ref_time().saturating_div(Self::ref_time_per_fuel()), + _phantom: PhantomData, + } + } + + /// Set the fuel left to the given value. + /// Returns the amount of Weight consumed since the last update. + fn set_fuel(&mut self, fuel: u64) -> Weight { + let consumed = self.fuel.saturating_sub(fuel).saturating_mul(Self::ref_time_per_fuel()); + self.fuel = fuel; + Weight::from_parts(consumed, 0) + } + + /// Charge the given amount of gas. + /// Returns the amount of fuel left. + fn charge_ref_time(&mut self, ref_time: u64) -> Result { + let amount = ref_time + .checked_div(Self::ref_time_per_fuel()) + .ok_or(Error::::InvalidSchedule)?; + + self.fuel.checked_sub(amount).ok_or_else(|| Error::::OutOfGas)?; + Ok(Syncable(self.fuel.try_into().map_err(|_| Error::::OutOfGas)?)) + } + + /// How much ref time does each PolkaVM gas correspond to. + fn ref_time_per_fuel() -> u64 { + // We execute 6 different instructions therefore we have to divide the actual + // computed gas costs by 6 to have a rough estimate as to how expensive each + // single executed instruction is going to be. + let instr_cost = T::WeightInfo::instr_i64_load_store(1) + .saturating_sub(T::WeightInfo::instr_i64_load_store(0)) + .ref_time(); + instr_cost / 6 + } +} + +/// Used to capture the gas left before entering a host function. +/// +/// Has to be consumed in order to sync back the gas after leaving the host function. +#[must_use] +pub struct RefTimeLeft(u64); + +/// Resource that needs to be synced to the executor. +/// +/// Wrapped to make sure that the resource will be synced back the the executor. +#[must_use] +pub struct Syncable(polkavm::Gas); + +impl From for polkavm::Gas { + fn from(from: Syncable) -> Self { + from.0 + } +} + +#[cfg(not(test))] +pub trait TestAuxiliaries {} +#[cfg(not(test))] +impl TestAuxiliaries for T {} + +#[cfg(test)] +pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {} +#[cfg(test)] +impl TestAuxiliaries for T {} + +/// This trait represents a token that can be used for charging `GasMeter`. +/// There is no other way of charging it. +/// +/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added +/// for consistency). If inlined there should be no observable difference compared +/// to a hand-written code. +pub trait Token: Copy + Clone + TestAuxiliaries { + /// Return the amount of gas that should be taken by this token. + /// + /// This function should be really lightweight and must not fail. It is not + /// expected that implementors will query the storage or do any kinds of heavy operations. + /// + /// That said, implementors of this function still can run into overflows + /// while calculating the amount. In this case it is ok to use saturating operations + /// since on overflow they will return `max_value` which should consume all gas. + fn weight(&self) -> Weight; + + /// Returns true if this token is expected to influence the lowest gas limit. + fn influence_lowest_gas_limit(&self) -> bool { + true + } +} + +/// A wrapper around a type-erased trait object of what used to be a `Token`. +#[cfg(test)] +pub struct ErasedToken { + pub description: String, + pub token: Box, +} + +#[derive(DefaultNoBound)] +pub struct GasMeter { + gas_limit: Weight, + /// Amount of gas left from initial gas limit. Can reach zero. + gas_left: Weight, + /// Due to `adjust_gas` and `nested` the `gas_left` can temporarily dip below its final value. + gas_left_lowest: Weight, + /// The amount of resources that was consumed by the execution engine. + /// We have to track it separately in order to avoid the loss of precision that happens when + /// converting from ref_time to the execution engine unit. + engine_meter: EngineMeter, + _phantom: PhantomData, + #[cfg(test)] + tokens: Vec, +} + +impl GasMeter { + pub fn new(gas_limit: Weight) -> Self { + GasMeter { + gas_limit, + gas_left: gas_limit, + gas_left_lowest: gas_limit, + engine_meter: EngineMeter::new(gas_limit), + _phantom: PhantomData, + #[cfg(test)] + tokens: Vec::new(), + } + } + + /// Create a new gas meter by removing gas from the current meter. + /// + /// # Note + /// + /// Passing `0` as amount is interpreted as "all remaining gas". + pub fn nested(&mut self, amount: Weight) -> Self { + let amount = Weight::from_parts( + if amount.ref_time().is_zero() { + self.gas_left().ref_time() + } else { + amount.ref_time() + }, + if amount.proof_size().is_zero() { + self.gas_left().proof_size() + } else { + amount.proof_size() + }, + ) + .min(self.gas_left); + self.gas_left -= amount; + GasMeter::new(amount) + } + + /// Absorb the remaining gas of a nested meter after we are done using it. + pub fn absorb_nested(&mut self, nested: Self) { + self.gas_left_lowest = (self.gas_left + nested.gas_limit) + .saturating_sub(nested.gas_required()) + .min(self.gas_left_lowest); + self.gas_left += nested.gas_left; + } + + /// Account for used gas. + /// + /// Amount is calculated by the given `token`. + /// + /// Returns `OutOfGas` if there is not enough gas or addition of the specified + /// amount of gas has lead to overflow. + /// + /// NOTE that amount isn't consumed if there is not enough gas. This is considered + /// safe because we always charge gas before performing any resource-spending action. + #[inline] + pub fn charge>(&mut self, token: Tok) -> Result { + #[cfg(test)] + { + // Unconditionally add the token to the storage. + let erased_tok = + ErasedToken { description: format!("{:?}", token), token: Box::new(token) }; + self.tokens.push(erased_tok); + } + let amount = token.weight(); + // It is OK to not charge anything on failure because we always charge _before_ we perform + // any action + self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| Error::::OutOfGas)?; + Ok(ChargedAmount(amount)) + } + + /// Adjust a previously charged amount down to its actual amount. + /// + /// This is when a maximum a priori amount was charged and then should be partially + /// refunded to match the actual amount. + pub fn adjust_gas>(&mut self, charged_amount: ChargedAmount, token: Tok) { + if token.influence_lowest_gas_limit() { + self.gas_left_lowest = self.gas_left_lowest(); + } + let adjustment = charged_amount.0.saturating_sub(token.weight()); + self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit); + } + + /// Hand over the gas metering responsibility from the executor to this meter. + /// + /// Needs to be called when entering a host function to update this meter with the + /// gas that was tracked by the executor. It tracks the latest seen total value + /// in order to compute the delta that needs to be charged. + pub fn sync_from_executor( + &mut self, + engine_fuel: polkavm::Gas, + ) -> Result { + let weight_consumed = self + .engine_meter + .set_fuel(engine_fuel.try_into().map_err(|_| Error::::OutOfGas)?); + self.gas_left + .checked_reduce(weight_consumed) + .ok_or_else(|| Error::::OutOfGas)?; + Ok(RefTimeLeft(self.gas_left.ref_time())) + } + + /// Hand over the gas metering responsibility from this meter to the executor. + /// + /// Needs to be called when leaving a host function in order to calculate how much + /// gas needs to be charged from the **executor**. It updates the last seen executor + /// total value so that it is correct when `sync_from_executor` is called the next time. + /// + /// It is important that this does **not** actually sync with the executor. That has + /// to be done by the caller. + pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result { + let ref_time_consumed = before.0.saturating_sub(self.gas_left().ref_time()); + self.engine_meter.charge_ref_time(ref_time_consumed) + } + + /// Returns the amount of gas that is required to run the same call. + /// + /// This can be different from `gas_spent` because due to `adjust_gas` the amount of + /// spent gas can temporarily drop and be refunded later. + pub fn gas_required(&self) -> Weight { + self.gas_limit.saturating_sub(self.gas_left_lowest()) + } + + /// Returns how much gas was spent + pub fn gas_consumed(&self) -> Weight { + self.gas_limit.saturating_sub(self.gas_left) + } + + /// Returns how much gas left from the initial budget. + pub fn gas_left(&self) -> Weight { + self.gas_left + } + + /// The amount of gas in terms of engine gas. + pub fn engine_fuel_left(&self) -> Result { + self.engine_meter.fuel.try_into().map_err(|_| >::OutOfGas.into()) + } + + /// Turn this GasMeter into a DispatchResult that contains the actually used gas. + pub fn into_dispatch_result( + self, + result: Result, + base_weight: Weight, + ) -> DispatchResultWithPostInfo + where + E: Into, + { + let post_info = PostDispatchInfo { + actual_weight: Some(self.gas_consumed().saturating_add(base_weight)), + pays_fee: Default::default(), + }; + + result + .map(|_| post_info) + .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into().error }) + } + + fn gas_left_lowest(&self) -> Weight { + self.gas_left_lowest.min(self.gas_left) + } + + #[cfg(test)] + pub fn tokens(&self) -> &[ErasedToken] { + &self.tokens + } +} + +#[cfg(test)] +mod tests { + use super::{GasMeter, Token, Weight}; + use crate::tests::Test; + + /// A simple utility macro that helps to match against a + /// list of tokens. + macro_rules! match_tokens { + ($tokens_iter:ident,) => { + }; + ($tokens_iter:ident, $x:expr, $($rest:tt)*) => { + { + let next = ($tokens_iter).next().unwrap(); + let pattern = $x; + + // Note that we don't specify the type name directly in this macro, + // we only have some expression $x of some type. At the same time, we + // have an iterator of Box and to downcast we need to specify + // the type which we want downcast to. + // + // So what we do is we assign `_pattern_typed_next_ref` to a variable which has + // the required type. + // + // Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes + // rustc infer the type `T` (in `downcast_ref`) to be the same as in $x. + + let mut _pattern_typed_next_ref = &pattern; + _pattern_typed_next_ref = match next.token.downcast_ref() { + Some(p) => { + assert_eq!(p, &pattern); + p + } + None => { + panic!("expected type {} got {}", stringify!($x), next.description); + } + }; + } + + match_tokens!($tokens_iter, $($rest)*); + }; + } + + /// A trivial token that charges the specified number of gas units. + #[derive(Copy, Clone, PartialEq, Eq, Debug)] + struct SimpleToken(u64); + impl Token for SimpleToken { + fn weight(&self) -> Weight { + Weight::from_parts(self.0, 0) + } + } + + #[test] + fn it_works() { + let gas_meter = GasMeter::::new(Weight::from_parts(50000, 0)); + assert_eq!(gas_meter.gas_left(), Weight::from_parts(50000, 0)); + } + + #[test] + fn tracing() { + let mut gas_meter = GasMeter::::new(Weight::from_parts(50000, 0)); + assert!(!gas_meter.charge(SimpleToken(1)).is_err()); + + let mut tokens = gas_meter.tokens().iter(); + match_tokens!(tokens, SimpleToken(1),); + } + + // This test makes sure that nothing can be executed if there is no gas. + #[test] + fn refuse_to_execute_anything_if_zero() { + let mut gas_meter = GasMeter::::new(Weight::zero()); + assert!(gas_meter.charge(SimpleToken(1)).is_err()); + } + + // Make sure that the gas meter does not charge in case of overcharge + #[test] + fn overcharge_does_not_charge() { + let mut gas_meter = GasMeter::::new(Weight::from_parts(200, 0)); + + // The first charge is should lead to OOG. + assert!(gas_meter.charge(SimpleToken(300)).is_err()); + + // The gas meter should still contain the full 200. + assert!(gas_meter.charge(SimpleToken(200)).is_ok()); + } + + // Charging the exact amount that the user paid for should be + // possible. + #[test] + fn charge_exact_amount() { + let mut gas_meter = GasMeter::::new(Weight::from_parts(25, 0)); + assert!(!gas_meter.charge(SimpleToken(25)).is_err()); + } +} diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs new file mode 100644 index 000000000000..303c649bc8cf --- /dev/null +++ b/substrate/frame/revive/src/lib.rs @@ -0,0 +1,1354 @@ +// 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. + +#![doc = include_str!("../README.md")] +#![allow(rustdoc::private_intra_doc_links)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "1024")] + +extern crate alloc; +mod address; +mod benchmarking; +mod benchmarking_dummy; +mod exec; +mod gas; +mod primitives; +pub use primitives::*; + +mod limits; +mod storage; +mod transient_storage; +mod wasm; + +pub mod chain_extension; +pub mod debug; +pub mod migration; +pub mod test_utils; +pub mod weights; + +#[cfg(test)] +mod tests; +use crate::{ + exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, + gas::GasMeter, + storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, + wasm::{CodeInfo, RuntimeCosts, WasmBlob}, +}; +use codec::{Codec, Decode, Encode, HasCompact}; +use core::fmt::Debug; +use environmental::*; +use frame_support::{ + dispatch::{ + DispatchErrorWithPostInfo, DispatchResultWithPostInfo, GetDispatchInfo, Pays, + PostDispatchInfo, RawOrigin, WithPostDispatchInfo, + }, + ensure, + traits::{ + fungible::{Inspect, Mutate, MutateHold}, + ConstU32, Contains, EnsureOrigin, Get, Time, + }, + weights::{Weight, WeightMeter}, + BoundedVec, RuntimeDebugNoBound, +}; +use frame_system::{ + ensure_signed, + pallet_prelude::{BlockNumberFor, OriginFor}, + EventRecord, Pallet as System, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{BadOrigin, Convert, Dispatchable, Saturating, StaticLookup}, + DispatchError, +}; + +pub use crate::{ + address::{AddressGenerator, DefaultAddressGenerator}, + debug::Tracing, + migration::{MigrateSequence, Migration, NoopMigration}, + pallet::*, +}; +pub use weights::WeightInfo; + +#[cfg(doc)] +pub use crate::wasm::SyscallDoc; + +type CodeHash = ::Hash; +type TrieId = BoundedVec>; +type BalanceOf = + <::Currency as Inspect<::AccountId>>::Balance; +type CodeVec = BoundedVec::MaxCodeLen>; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +type EventRecordOf = + EventRecord<::RuntimeEvent, ::Hash>; +type DebugBuffer = BoundedVec>; + +/// Used as a sentinel value when reading and writing contract memory. +/// +/// It is usually used to signal `None` to a contract when only a primitive is allowed +/// and we don't want to go through encoding a full Rust type. Using `u32::Max` is a safe +/// sentinel because contracts are never allowed to use such a large amount of resources +/// that this value makes sense for a memory location or length. +const SENTINEL: u32 = u32::MAX; + +/// The target that is used for the log output emitted by this crate. +/// +/// Hence you can use this target to selectively increase the log level for this crate. +/// +/// Example: `RUST_LOG=runtime::revive=debug my_code --dev` +const LOG_TARGET: &str = "runtime::revive"; + +/// This version determines which syscalls are available to contracts. +/// +/// Needs to be bumped every time a versioned syscall is added. +const API_VERSION: u16 = 0; + +#[test] +fn api_version_up_to_date() { + assert!( + API_VERSION == crate::wasm::HIGHEST_API_VERSION, + "A new versioned API has been added. The `API_VERSION` needs to be bumped." + ); +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use crate::debug::Debugger; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_runtime::Perbill; + + /// The in-code storage version. + pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + /// The time implementation used to supply timestamps to contracts through `seal_now`. + type Time: Time; + + /// The fungible in which fees are paid and contract balances are held. + #[pallet::no_default] + type Currency: Inspect + + Mutate + + MutateHold; + + /// The overarching event type. + #[pallet::no_default_bounds] + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The overarching call type. + #[pallet::no_default_bounds] + type RuntimeCall: Dispatchable + + GetDispatchInfo + + codec::Decode + + IsType<::RuntimeCall>; + + /// Overarching hold reason. + #[pallet::no_default_bounds] + type RuntimeHoldReason: From; + + /// Filter that is applied to calls dispatched by contracts. + /// + /// Use this filter to control which dispatchables are callable by contracts. + /// This is applied in **addition** to [`frame_system::Config::BaseCallFilter`]. + /// It is recommended to treat this as a whitelist. + /// + /// # Stability + /// + /// The runtime **must** make sure that all dispatchables that are callable by + /// contracts remain stable. In addition [`Self::RuntimeCall`] itself must remain stable. + /// This means that no existing variants are allowed to switch their positions. + /// + /// # Note + /// + /// Note that dispatchables that are called via contracts do not spawn their + /// own wasm instance for each call (as opposed to when called via a transaction). + /// Therefore please make sure to be restrictive about which dispatchables are allowed + /// in order to not introduce a new DoS vector like memory allocation patterns that can + /// be exploited to drive the runtime into a panic. + /// + /// This filter does not apply to XCM transact calls. To impose restrictions on XCM transact + /// calls, you must configure them separately within the XCM pallet itself. + #[pallet::no_default_bounds] + type CallFilter: Contains<::RuntimeCall>; + + /// Used to answer contracts' queries regarding the current weight price. This is **not** + /// used to calculate the actual fee and is only for informational purposes. + #[pallet::no_default_bounds] + type WeightPrice: Convert>; + + /// Describes the weights of the dispatchables of this module and is also used to + /// construct a default cost schedule. + type WeightInfo: WeightInfo; + + /// Type that allows the runtime authors to add new host functions for a contract to call. + #[pallet::no_default_bounds] + type ChainExtension: chain_extension::ChainExtension + Default; + + /// The amount of balance a caller has to pay for each byte of storage. + /// + /// # Note + /// + /// It is safe to chage this value on a live chain as all refunds are pro rata. + #[pallet::constant] + #[pallet::no_default_bounds] + type DepositPerByte: Get>; + + /// The amount of balance a caller has to pay for each storage item. + /// + /// # Note + /// + /// It is safe to chage this value on a live chain as all refunds are pro rata. + #[pallet::constant] + #[pallet::no_default_bounds] + type DepositPerItem: Get>; + + /// The percentage of the storage deposit that should be held for using a code hash. + /// Instantiating a contract, or calling [`chain_extension::Ext::lock_delegate_dependency`] + /// protects the code from being removed. In order to prevent abuse these actions are + /// protected with a percentage of the code deposit. + #[pallet::constant] + type CodeHashLockupDepositPercent: Get; + + /// The address generator used to generate the addresses of contracts. + #[pallet::no_default_bounds] + type AddressGenerator: AddressGenerator; + + /// The maximum length of a contract code in bytes. + /// + /// This value hugely affects the memory requirements of this pallet since all the code of + /// all contracts on the call stack will need to be held in memory. Setting of a correct + /// value will be enforced in [`Pallet::integrity_test`]. + #[pallet::constant] + type MaxCodeLen: Get; + + /// Make contract callable functions marked as `#[unstable]` available. + /// + /// Contracts that use `#[unstable]` functions won't be able to be uploaded unless + /// this is set to `true`. This is only meant for testnets and dev nodes in order to + /// experiment with new features. + /// + /// # Warning + /// + /// Do **not** set to `true` on productions chains. + #[pallet::constant] + type UnsafeUnstableInterface: Get; + + /// Origin allowed to upload code. + /// + /// By default, it is safe to set this to `EnsureSigned`, allowing anyone to upload contract + /// code. + #[pallet::no_default_bounds] + type UploadOrigin: EnsureOrigin; + + /// Origin allowed to instantiate code. + /// + /// # Note + /// + /// This is not enforced when a contract instantiates another contract. The + /// [`Self::UploadOrigin`] should make sure that no code is deployed that does unwanted + /// instantiations. + /// + /// By default, it is safe to set this to `EnsureSigned`, allowing anyone to instantiate + /// contract code. + #[pallet::no_default_bounds] + type InstantiateOrigin: EnsureOrigin; + + /// The sequence of migration steps that will be applied during a migration. + /// + /// # Examples + /// ```ignore + /// use pallet_revive::migration::{v10, v11}; + /// # struct Runtime {}; + /// # struct Currency {}; + /// type Migrations = (v10::Migration, v11::Migration); + /// ``` + /// + /// If you have a single migration step, you can use a tuple with a single element: + /// ```ignore + /// use pallet_revive::migration::v10; + /// # struct Runtime {}; + /// # struct Currency {}; + /// type Migrations = (v10::Migration,); + /// ``` + type Migrations: MigrateSequence; + + /// For most production chains, it's recommended to use the `()` implementation of this + /// trait. This implementation offers additional logging when the log target + /// "runtime::revive" is set to trace. + #[pallet::no_default_bounds] + type Debug: Debugger; + + /// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and + /// execute XCM programs. + #[pallet::no_default_bounds] + type Xcm: xcm_builder::Controller< + OriginFor, + ::RuntimeCall, + BlockNumberFor, + >; + + /// The amount of memory in bytes that parachain nodes alot to the runtime. + /// + /// This is used in [`Pallet::integrity_test`] to make sure that the runtime has enough + /// memory to support this pallet if set to the correct value. + type RuntimeMemory: Get; + + /// The amount of memory in bytes that relay chain validators alot to the PoV. + /// + /// This is used in [`Pallet::integrity_test`] to make sure that the runtime has enough + /// memory to support this pallet if set to the correct value. + /// + /// This value is usually higher than [`Self::RuntimeMemory`] to account for the fact + /// that validators have to hold all storage items in PvF memory. + type PVFMemory: Get; + } + + /// Container for different types that implement [`DefaultConfig`]` of this pallet. + pub mod config_preludes { + use super::*; + use frame_support::{ + derive_impl, + traits::{ConstBool, ConstU32}, + }; + use frame_system::EnsureSigned; + use sp_core::parameter_types; + + type AccountId = sp_runtime::AccountId32; + type Balance = u64; + const UNITS: Balance = 10_000_000_000; + const CENTS: Balance = UNITS / 100; + + pub const fn deposit(items: u32, bytes: u32) -> Balance { + items as Balance * 1 * CENTS + (bytes as Balance) * 1 * CENTS + } + + parameter_types! { + pub const DepositPerItem: Balance = deposit(1, 0); + pub const DepositPerByte: Balance = deposit(0, 1); + pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); + } + + /// A type providing default configurations for this pallet in testing environment. + pub struct TestDefaultConfig; + + impl Time for TestDefaultConfig { + type Moment = u64; + fn now() -> Self::Moment { + unimplemented!("No default `now` implementation in `TestDefaultConfig` provide a custom `T::Time` type.") + } + } + + impl> Convert for TestDefaultConfig { + fn convert(w: Weight) -> T { + w.ref_time().into() + } + } + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)] + impl frame_system::DefaultConfig for TestDefaultConfig {} + + #[frame_support::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + #[inject_runtime_type] + type RuntimeEvent = (); + + #[inject_runtime_type] + type RuntimeHoldReason = (); + + #[inject_runtime_type] + type RuntimeCall = (); + + type AddressGenerator = DefaultAddressGenerator; + type CallFilter = (); + type ChainExtension = (); + type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type DepositPerByte = DepositPerByte; + type DepositPerItem = DepositPerItem; + type MaxCodeLen = ConstU32<{ 123 * 1024 }>; + type Migrations = (); + type Time = Self; + type UnsafeUnstableInterface = ConstBool; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; + type WeightInfo = (); + type WeightPrice = Self; + type Debug = (); + type Xcm = (); + type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; + type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; + } + } + + #[pallet::event] + pub enum Event { + /// Contract deployed by address at the specified address. + Instantiated { deployer: T::AccountId, contract: T::AccountId }, + + /// Contract has been removed. + /// + /// # Note + /// + /// The only way for a contract to be removed and emitting this event is by calling + /// `seal_terminate`. + Terminated { + /// The contract that was terminated. + contract: T::AccountId, + /// The account that received the contracts remaining balance + beneficiary: T::AccountId, + }, + + /// Code with the specified hash has been stored. + CodeStored { code_hash: T::Hash, deposit_held: BalanceOf, uploader: T::AccountId }, + + /// A custom event emitted by the contract. + ContractEmitted { + /// The contract that emitted the event. + contract: T::AccountId, + /// Data supplied by the contract. Metadata generated during contract compilation + /// is needed to decode it. + data: Vec, + }, + + /// A code with the specified hash was removed. + CodeRemoved { code_hash: T::Hash, deposit_released: BalanceOf, remover: T::AccountId }, + + /// A contract's code was updated. + ContractCodeUpdated { + /// The contract that has been updated. + contract: T::AccountId, + /// New code hash that was set for the contract. + new_code_hash: T::Hash, + /// Previous code hash of the contract. + old_code_hash: T::Hash, + }, + + /// A contract was called either by a plain account or another contract. + /// + /// # Note + /// + /// Please keep in mind that like all events this is only emitted for successful + /// calls. This is because on failure all storage changes including events are + /// rolled back. + Called { + /// The caller of the `contract`. + caller: Origin, + /// The contract that was called. + contract: T::AccountId, + }, + + /// A contract delegate called a code hash. + /// + /// # Note + /// + /// Please keep in mind that like all events this is only emitted for successful + /// calls. This is because on failure all storage changes including events are + /// rolled back. + DelegateCalled { + /// The contract that performed the delegate call and hence in whose context + /// the `code_hash` is executed. + contract: T::AccountId, + /// The code hash that was delegate called. + code_hash: CodeHash, + }, + + /// Some funds have been transferred and held as storage deposit. + StorageDepositTransferredAndHeld { + from: T::AccountId, + to: T::AccountId, + amount: BalanceOf, + }, + + /// Some storage deposit funds have been transferred and released. + StorageDepositTransferredAndReleased { + from: T::AccountId, + to: T::AccountId, + amount: BalanceOf, + }, + } + + #[pallet::error] + pub enum Error { + /// Invalid schedule supplied, e.g. with zero weight of a basic operation. + InvalidSchedule, + /// Invalid combination of flags supplied to `seal_call` or `seal_delegate_call`. + InvalidCallFlags, + /// The executed contract exhausted its gas limit. + OutOfGas, + /// The output buffer supplied to a contract API call was too small. + OutputBufferTooSmall, + /// Performing the requested transfer failed. Probably because there isn't enough + /// free balance in the sender's account. + TransferFailed, + /// Performing a call was denied because the calling depth reached the limit + /// of what is specified in the schedule. + MaxCallDepthReached, + /// No contract was found at the specified address. + ContractNotFound, + /// The code supplied to `instantiate_with_code` exceeds the limit specified in the + /// current schedule. + CodeTooLarge, + /// No code could be found at the supplied code hash. + CodeNotFound, + /// No code info could be found at the supplied code hash. + CodeInfoNotFound, + /// A buffer outside of sandbox memory was passed to a contract API function. + OutOfBounds, + /// Input passed to a contract API function failed to decode as expected type. + DecodingFailed, + /// Contract trapped during execution. + ContractTrapped, + /// The size defined in `T::MaxValueSize` was exceeded. + ValueTooLarge, + /// Termination of a contract is not allowed while the contract is already + /// on the call stack. Can be triggered by `seal_terminate`. + TerminatedWhileReentrant, + /// `seal_call` forwarded this contracts input. It therefore is no longer available. + InputForwarded, + /// The amount of topics passed to `seal_deposit_events` exceeds the limit. + TooManyTopics, + /// The chain does not provide a chain extension. Calling the chain extension results + /// in this error. Note that this usually shouldn't happen as deploying such contracts + /// is rejected. + NoChainExtension, + /// Failed to decode the XCM program. + XCMDecodeFailed, + /// A contract with the same AccountId already exists. + DuplicateContract, + /// A contract self destructed in its constructor. + /// + /// This can be triggered by a call to `seal_terminate`. + TerminatedInConstructor, + /// A call tried to invoke a contract that is flagged as non-reentrant. + ReentranceDenied, + /// A contract called into the runtime which then called back into this pallet. + ReenteredPallet, + /// A contract attempted to invoke a state modifying API while being in read-only mode. + StateChangeDenied, + /// Origin doesn't have enough balance to pay the required storage deposits. + StorageDepositNotEnoughFunds, + /// More storage was created than allowed by the storage deposit limit. + StorageDepositLimitExhausted, + /// Code removal was denied because the code is still in use by at least one contract. + CodeInUse, + /// The contract ran to completion but decided to revert its storage changes. + /// Please note that this error is only returned from extrinsics. When called directly + /// or via RPC an `Ok` will be returned. In this case the caller needs to inspect the flags + /// to determine whether a reversion has taken place. + ContractReverted, + /// The contract failed to compile or is missing the correct entry points. + /// + /// A more detailed error can be found on the node console if debug messages are enabled + /// by supplying `-lruntime::revive=debug`. + CodeRejected, + /// A pending migration needs to complete before the extrinsic can be called. + MigrationInProgress, + /// Migrate dispatch call was attempted but no migration was performed. + NoMigrationPerformed, + /// The contract has reached its maximum number of delegate dependencies. + MaxDelegateDependenciesReached, + /// The dependency was not found in the contract's delegate dependencies. + DelegateDependencyNotFound, + /// The contract already depends on the given delegate dependency. + DelegateDependencyAlreadyExists, + /// Can not add a delegate dependency to the code hash of the contract itself. + CannotAddSelfAsDelegateDependency, + /// Can not add more data to transient storage. + OutOfTransientStorage, + /// The contract tried to call a syscall which does not exist (at its current api level). + InvalidSyscall, + /// Invalid storage flags were passed to one of the storage syscalls. + InvalidStorageFlags, + /// PolkaVM failed during code execution. Probably due to a malformed program. + ExecutionFailed, + } + + /// A reason for the pallet contracts placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// The Pallet has reserved it for storing code on-chain. + CodeUploadDepositReserve, + /// The Pallet has reserved it for storage deposit. + StorageDepositReserve, + } + + /// A mapping from a contract's code hash to its code. + #[pallet::storage] + pub(crate) type PristineCode = StorageMap<_, Identity, CodeHash, CodeVec>; + + /// A mapping from a contract's code hash to its code info. + #[pallet::storage] + pub(crate) type CodeInfoOf = StorageMap<_, Identity, CodeHash, CodeInfo>; + + /// The code associated with a given account. + #[pallet::storage] + pub(crate) type ContractInfoOf = + StorageMap<_, Identity, T::AccountId, ContractInfo>; + + /// Evicted contracts that await child trie deletion. + /// + /// Child trie deletion is a heavy operation depending on the amount of storage items + /// stored in said trie. Therefore this operation is performed lazily in `on_idle`. + #[pallet::storage] + pub(crate) type DeletionQueue = StorageMap<_, Twox64Concat, u32, TrieId>; + + /// A pair of monotonic counters used to track the latest contract marked for deletion + /// and the latest deleted contract in queue. + #[pallet::storage] + pub(crate) type DeletionQueueCounter = + StorageValue<_, DeletionQueueManager, ValueQuery>; + + /// A migration can span across multiple blocks. This storage defines a cursor to track the + /// progress of the migration, enabling us to resume from the last completed position. + #[pallet::storage] + pub(crate) type MigrationInProgress = + StorageValue<_, migration::Cursor, OptionQuery>; + + #[pallet::extra_constants] + impl Pallet { + #[pallet::constant_name(ApiVersion)] + fn api_version() -> u16 { + API_VERSION + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { + use migration::MigrateResult::*; + let mut meter = WeightMeter::with_limit(limit); + + loop { + match Migration::::migrate(&mut meter) { + // There is not enough weight to perform a migration. + // We can't do anything more, so we return the used weight. + NoMigrationPerformed | InProgress { steps_done: 0 } => return meter.consumed(), + // Migration is still in progress, we can start the next step. + InProgress { .. } => continue, + // Either no migration is in progress, or we are done with all migrations, we + // can do some more other work with the remaining weight. + Completed | NoMigrationInProgress => break, + } + } + + ContractInfo::::process_deletion_queue_batch(&mut meter); + meter.consumed() + } + + fn integrity_test() { + Migration::::integrity_test(); + + // Total runtime memory limit + let max_runtime_mem: u32 = T::RuntimeMemory::get(); + // Memory limits for a single contract: + // Value stack size: 1Mb per contract, default defined in wasmi + const MAX_STACK_SIZE: u32 = 1024 * 1024; + // Heap limit is normally 16 mempages of 64kb each = 1Mb per contract + let max_heap_size = limits::MEMORY_BYTES; + // The root frame is not accounted in CALL_STACK_DEPTH + let max_call_depth = + limits::CALL_STACK_DEPTH.checked_add(1).expect("CallStack size is too big"); + // Transient storage uses a BTreeMap, which has overhead compared to the raw size of + // key-value data. To ensure safety, a margin of 2x the raw key-value size is used. + let max_transient_storage_size = limits::TRANSIENT_STORAGE_BYTES + .checked_mul(2) + .expect("MaxTransientStorageSize is too large"); + + // Check that given configured `MaxCodeLen`, runtime heap memory limit can't be broken. + // + // In worst case, the decoded Wasm contract code would be `x16` times larger than the + // encoded one. This is because even a single-byte wasm instruction has 16-byte size in + // wasmi. This gives us `MaxCodeLen*16` safety margin. + // + // Next, the pallet keeps the Wasm blob for each + // contract, hence we add up `MaxCodeLen` to the safety margin. + // + // The inefficiencies of the freeing-bump allocator + // being used in the client for the runtime memory allocations, could lead to possible + // memory allocations for contract code grow up to `x4` times in some extreme cases, + // which gives us total multiplier of `17*4` for `MaxCodeLen`. + // + // That being said, for every contract executed in runtime, at least `MaxCodeLen*17*4` + // memory should be available. Note that maximum allowed heap memory and stack size per + // each contract (stack frame) should also be counted. + // + // The pallet holds transient storage with a size up to `max_transient_storage_size`. + // + // Finally, we allow 50% of the runtime memory to be utilized by the contracts call + // stack, keeping the rest for other facilities, such as PoV, etc. + // + // This gives us the following formula: + // + // `(MaxCodeLen * 17 * 4 + MAX_STACK_SIZE + max_heap_size) * max_call_depth + + // max_transient_storage_size < max_runtime_mem/2` + // + // Hence the upper limit for the `MaxCodeLen` can be defined as follows: + let code_len_limit = max_runtime_mem + .saturating_div(2) + .saturating_sub(max_transient_storage_size) + .saturating_div(max_call_depth) + .saturating_sub(max_heap_size) + .saturating_sub(MAX_STACK_SIZE) + .saturating_div(17 * 4); + + assert!( + T::MaxCodeLen::get() < code_len_limit, + "Given `CallStack` height {:?}, `MaxCodeLen` should be set less than {:?} \ + (current value is {:?}), to avoid possible runtime oom issues.", + max_call_depth, + code_len_limit, + T::MaxCodeLen::get(), + ); + + // Validators are configured to be able to use more memory than block builders. This is + // because in addition to `max_runtime_mem` they need to hold additional data in + // memory: PoV in multiple copies (1x encoded + 2x decoded) and all storage which + // includes emitted events. The assumption is that storage/events size + // can be a maximum of half of the validator runtime memory - max_runtime_mem. + let max_block_ref_time = T::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap_or_else(|| T::BlockWeights::get().max_block) + .ref_time(); + let max_payload_size = limits::PAYLOAD_BYTES; + let max_key_size = + Key::try_from_var(alloc::vec![0u8; limits::STORAGE_KEY_BYTES as usize]) + .expect("Key of maximal size shall be created") + .hash() + .len() as u32; + + // We can use storage to store items using the available block ref_time with the + // `set_storage` host function. + let max_storage_size: u32 = ((max_block_ref_time / + (>::weight(&RuntimeCosts::SetStorage { + new_bytes: max_payload_size, + old_bytes: 0, + }) + .ref_time())) + .saturating_mul(max_payload_size.saturating_add(max_key_size) as u64)) + .try_into() + .expect("Storage size too big"); + + let max_pvf_mem: u32 = T::PVFMemory::get(); + let storage_size_limit = max_pvf_mem.saturating_sub(max_runtime_mem) / 2; + + assert!( + max_storage_size < storage_size_limit, + "Maximal storage size {} exceeds the storage limit {}", + max_storage_size, + storage_size_limit + ); + + // We can use storage to store events using the available block ref_time with the + // `deposit_event` host function. The overhead of stored events, which is around 100B, + // is not taken into account to simplify calculations, as it does not change much. + let max_events_size: u32 = ((max_block_ref_time / + (>::weight(&RuntimeCosts::DepositEvent { + num_topic: 0, + len: max_payload_size, + }) + .ref_time())) + .saturating_mul(max_payload_size as u64)) + .try_into() + .expect("Events size too big"); + + assert!( + max_events_size < storage_size_limit, + "Maximal events size {} exceeds the events limit {}", + max_events_size, + storage_size_limit + ); + } + } + + #[pallet::call] + impl Pallet + where + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + { + /// Makes a call to an account, optionally transferring some balance. + /// + /// # Parameters + /// + /// * `dest`: Address of the contract to call. + /// * `value`: The balance to transfer from the `origin` to `dest`. + /// * `gas_limit`: The gas limit enforced when executing the constructor. + /// * `storage_deposit_limit`: The maximum amount of balance that can be charged from the + /// caller to pay for the storage consumed. + /// * `data`: The input data to pass to the contract. + /// + /// * If the account is a smart-contract account, the associated code will be + /// executed and any value will be transferred. + /// * If the account is a regular account, any value will be transferred. + /// * If no account exists and the call value is not less than `existential_deposit`, + /// a regular account will be created and any value will be transferred. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))] + pub fn call( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] value: BalanceOf, + gas_limit: Weight, + #[pallet::compact] storage_deposit_limit: BalanceOf, + data: Vec, + ) -> DispatchResultWithPostInfo { + let dest = T::Lookup::lookup(dest)?; + let mut output = Self::bare_call( + origin, + dest, + value, + gas_limit, + storage_deposit_limit, + data, + DebugInfo::Skip, + CollectEvents::Skip, + ); + if let Ok(return_value) = &output.result { + if return_value.did_revert() { + output.result = Err(>::ContractReverted.into()); + } + } + dispatch_result(output.result, output.gas_consumed, T::WeightInfo::call()) + } + + /// Instantiates a contract from a previously deployed wasm binary. + /// + /// This function is identical to [`Self::instantiate_with_code`] but without the + /// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary + /// must be supplied. + #[pallet::call_index(1)] + #[pallet::weight( + T::WeightInfo::instantiate(data.len() as u32, salt.len() as u32).saturating_add(*gas_limit) + )] + pub fn instantiate( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + gas_limit: Weight, + #[pallet::compact] storage_deposit_limit: BalanceOf, + code_hash: CodeHash, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo { + let data_len = data.len() as u32; + let salt_len = salt.len() as u32; + let mut output = Self::bare_instantiate( + origin, + value, + gas_limit, + storage_deposit_limit, + Code::Existing(code_hash), + data, + salt, + DebugInfo::Skip, + CollectEvents::Skip, + ); + if let Ok(retval) = &output.result { + if retval.result.did_revert() { + output.result = Err(>::ContractReverted.into()); + } + } + dispatch_result( + output.result.map(|result| result.result), + output.gas_consumed, + T::WeightInfo::instantiate(data_len, salt_len), + ) + } + + /// Instantiates a new contract from the supplied `code` optionally transferring + /// some balance. + /// + /// This dispatchable has the same effect as calling [`Self::upload_code`] + + /// [`Self::instantiate`]. Bundling them together provides efficiency gains. Please + /// also check the documentation of [`Self::upload_code`]. + /// + /// # Parameters + /// + /// * `value`: The balance to transfer from the `origin` to the newly created contract. + /// * `gas_limit`: The gas limit enforced when executing the constructor. + /// * `storage_deposit_limit`: The maximum amount of balance that can be charged/reserved + /// from the caller to pay for the storage consumed. + /// * `code`: The contract code to deploy in raw bytes. + /// * `data`: The input data to pass to the contract constructor. + /// * `salt`: Used for the address derivation. See [`Pallet::contract_address`]. + /// + /// Instantiation is executed as follows: + /// + /// - The supplied `code` is deployed, and a `code_hash` is created for that code. + /// - If the `code_hash` already exists on the chain the underlying `code` will be shared. + /// - The destination address is computed based on the sender, code_hash and the salt. + /// - The smart-contract account is created at the computed address. + /// - The `value` is transferred to the new account. + /// - The `deploy` function is executed in the context of the newly-created account. + #[pallet::call_index(2)] + #[pallet::weight( + T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32, salt.len() as u32) + .saturating_add(*gas_limit) + )] + pub fn instantiate_with_code( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + gas_limit: Weight, + #[pallet::compact] storage_deposit_limit: BalanceOf, + code: Vec, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo { + let code_len = code.len() as u32; + let data_len = data.len() as u32; + let salt_len = salt.len() as u32; + let mut output = Self::bare_instantiate( + origin, + value, + gas_limit, + storage_deposit_limit, + Code::Upload(code), + data, + salt, + DebugInfo::Skip, + CollectEvents::Skip, + ); + if let Ok(retval) = &output.result { + if retval.result.did_revert() { + output.result = Err(>::ContractReverted.into()); + } + } + dispatch_result( + output.result.map(|result| result.result), + output.gas_consumed, + T::WeightInfo::instantiate_with_code(code_len, data_len, salt_len), + ) + } + + /// Upload new `code` without instantiating a contract from it. + /// + /// If the code does not already exist a deposit is reserved from the caller + /// and unreserved only when [`Self::remove_code`] is called. The size of the reserve + /// depends on the size of the supplied `code`. + /// + /// # Note + /// + /// Anyone can instantiate a contract from any uploaded code and thus prevent its removal. + /// To avoid this situation a constructor could employ access control so that it can + /// only be instantiated by permissioned entities. The same is true when uploading + /// through [`Self::instantiate_with_code`]. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::upload_code_determinism_enforced(code.len() as u32))] + pub fn upload_code( + origin: OriginFor, + code: Vec, + #[pallet::compact] storage_deposit_limit: BalanceOf, + ) -> DispatchResult { + Self::bare_upload_code(origin, code, storage_deposit_limit).map(|_| ()) + } + + /// Remove the code stored under `code_hash` and refund the deposit to its owner. + /// + /// A code can only be removed by its original uploader (its owner) and only if it is + /// not used by any contract. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::remove_code())] + pub fn remove_code( + origin: OriginFor, + code_hash: CodeHash, + ) -> DispatchResultWithPostInfo { + Migration::::ensure_migrated()?; + let origin = ensure_signed(origin)?; + >::remove(&origin, code_hash)?; + // we waive the fee because removing unused code is beneficial + Ok(Pays::No.into()) + } + + /// Privileged function that changes the code of an existing contract. + /// + /// This takes care of updating refcounts and all other necessary operations. Returns + /// an error if either the `code_hash` or `dest` do not exist. + /// + /// # Note + /// + /// This does **not** change the address of the contract in question. This means + /// that the contract address is no longer derived from its code hash after calling + /// this dispatchable. + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::set_code())] + pub fn set_code( + origin: OriginFor, + dest: AccountIdLookupOf, + code_hash: CodeHash, + ) -> DispatchResult { + Migration::::ensure_migrated()?; + ensure_root(origin)?; + let dest = T::Lookup::lookup(dest)?; + >::try_mutate(&dest, |contract| { + let contract = if let Some(contract) = contract { + contract + } else { + return Err(>::ContractNotFound.into()) + }; + >>::increment_refcount(code_hash)?; + >>::decrement_refcount(contract.code_hash); + Self::deposit_event(Event::ContractCodeUpdated { + contract: dest.clone(), + new_code_hash: code_hash, + old_code_hash: contract.code_hash, + }); + contract.code_hash = code_hash; + Ok(()) + }) + } + + /// When a migration is in progress, this dispatchable can be used to run migration steps. + /// Calls that contribute to advancing the migration have their fees waived, as it's helpful + /// for the chain. Note that while the migration is in progress, the pallet will also + /// leverage the `on_idle` hooks to run migration steps. + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::migrate().saturating_add(*weight_limit))] + pub fn migrate(origin: OriginFor, weight_limit: Weight) -> DispatchResultWithPostInfo { + use migration::MigrateResult::*; + ensure_signed(origin)?; + + let weight_limit = weight_limit.saturating_add(T::WeightInfo::migrate()); + let mut meter = WeightMeter::with_limit(weight_limit); + let result = Migration::::migrate(&mut meter); + + match result { + Completed => Ok(PostDispatchInfo { + actual_weight: Some(meter.consumed()), + pays_fee: Pays::No, + }), + InProgress { steps_done, .. } if steps_done > 0 => Ok(PostDispatchInfo { + actual_weight: Some(meter.consumed()), + pays_fee: Pays::No, + }), + InProgress { .. } => Ok(PostDispatchInfo { + actual_weight: Some(meter.consumed()), + pays_fee: Pays::Yes, + }), + NoMigrationInProgress | NoMigrationPerformed => { + let err: DispatchError = >::NoMigrationPerformed.into(); + Err(err.with_weight(meter.consumed())) + }, + } + } + } +} + +/// Create a dispatch result reflecting the amount of consumed gas. +fn dispatch_result( + result: Result, + gas_consumed: Weight, + base_weight: Weight, +) -> DispatchResultWithPostInfo { + let post_info = PostDispatchInfo { + actual_weight: Some(gas_consumed.saturating_add(base_weight)), + pays_fee: Default::default(), + }; + + result + .map(|_| post_info) + .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e }) +} + +impl Pallet { + /// A generalized version of [`Self::call`]. + /// + /// Identical to [`Self::call`] but tailored towards being called by other code within the + /// runtime as opposed to from an extrinsic. It returns more information and allows the + /// enablement of features that are not suitable for an extrinsic (debugging, event + /// collection). + pub fn bare_call( + origin: OriginFor, + dest: T::AccountId, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + data: Vec, + debug: DebugInfo, + collect_events: CollectEvents, + ) -> ContractExecResult, EventRecordOf> { + let mut gas_meter = GasMeter::new(gas_limit); + let mut storage_deposit = Default::default(); + let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) { + Some(DebugBuffer::default()) + } else { + None + }; + let try_call = || { + Migration::::ensure_migrated()?; + let origin = Origin::from_runtime_origin(origin)?; + let mut storage_meter = StorageMeter::new(&origin, storage_deposit_limit, value)?; + let result = ExecStack::>::run_call( + origin.clone(), + dest, + &mut gas_meter, + &mut storage_meter, + value, + data, + debug_message.as_mut(), + )?; + storage_deposit = storage_meter.try_into_deposit(&origin)?; + Ok(result) + }; + let result = Self::run_guarded(try_call); + let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { + Some(System::::read_events_no_consensus().map(|e| *e).collect()) + } else { + None + }; + ContractExecResult { + result: result.map_err(|r| r.error), + gas_consumed: gas_meter.gas_consumed(), + gas_required: gas_meter.gas_required(), + storage_deposit, + debug_message: debug_message.unwrap_or_default().to_vec(), + events, + } + } + + /// A generalized version of [`Self::instantiate`] or [`Self::instantiate_with_code`]. + /// + /// Identical to [`Self::instantiate`] or [`Self::instantiate_with_code`] but tailored towards + /// being called by other code within the runtime as opposed to from an extrinsic. It returns + /// more information and allows the enablement of features that are not suitable for an + /// extrinsic (debugging, event collection). + pub fn bare_instantiate( + origin: OriginFor, + value: BalanceOf, + gas_limit: Weight, + mut storage_deposit_limit: BalanceOf, + code: Code>, + data: Vec, + salt: Vec, + debug: DebugInfo, + collect_events: CollectEvents, + ) -> ContractInstantiateResult, EventRecordOf> { + let mut gas_meter = GasMeter::new(gas_limit); + let mut storage_deposit = Default::default(); + let mut debug_message = + if debug == DebugInfo::UnsafeDebug { Some(DebugBuffer::default()) } else { None }; + let try_instantiate = || { + Migration::::ensure_migrated()?; + let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?; + let (executable, upload_deposit) = match code { + Code::Upload(code) => { + let upload_account = T::UploadOrigin::ensure_origin(origin)?; + let (executable, upload_deposit) = Self::try_upload_code( + upload_account, + code, + storage_deposit_limit, + debug_message.as_mut(), + )?; + storage_deposit_limit.saturating_reduce(upload_deposit); + (executable, upload_deposit) + }, + Code::Existing(code_hash) => + (WasmBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()), + }; + let instantiate_origin = Origin::from_account_id(instantiate_account.clone()); + let mut storage_meter = + StorageMeter::new(&instantiate_origin, storage_deposit_limit, value)?; + let result = ExecStack::>::run_instantiate( + instantiate_account, + executable, + &mut gas_meter, + &mut storage_meter, + value, + data, + &salt, + debug_message.as_mut(), + ); + storage_deposit = storage_meter + .try_into_deposit(&instantiate_origin)? + .saturating_add(&StorageDeposit::Charge(upload_deposit)); + result + }; + let output = Self::run_guarded(try_instantiate); + let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { + Some(System::::read_events_no_consensus().map(|e| *e).collect()) + } else { + None + }; + ContractInstantiateResult { + result: output + .map(|(account_id, result)| InstantiateReturnValue { result, account_id }) + .map_err(|e| e.error), + gas_consumed: gas_meter.gas_consumed(), + gas_required: gas_meter.gas_required(), + storage_deposit, + debug_message: debug_message.unwrap_or_default().to_vec(), + events, + } + } + + /// A generalized version of [`Self::upload_code`]. + /// + /// It is identical to [`Self::upload_code`] and only differs in the information it returns. + pub fn bare_upload_code( + origin: OriginFor, + code: Vec, + storage_deposit_limit: BalanceOf, + ) -> CodeUploadResult, BalanceOf> { + Migration::::ensure_migrated()?; + let origin = T::UploadOrigin::ensure_origin(origin)?; + let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit, None)?; + Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) + } + + /// Query storage of a specified contract under a specified key. + pub fn get_storage(address: T::AccountId, key: Vec) -> GetStorageResult { + if Migration::::in_progress() { + return Err(ContractAccessError::MigrationInProgress) + } + let contract_info = + ContractInfoOf::::get(&address).ok_or(ContractAccessError::DoesntExist)?; + + let maybe_value = contract_info.read( + &Key::try_from_var(key) + .map_err(|_| ContractAccessError::KeyDecodingFailed)? + .into(), + ); + Ok(maybe_value) + } + + /// Determine the address of a contract. + /// + /// This is the address generation function used by contract instantiation. See + /// [`DefaultAddressGenerator`] for the default implementation. + pub fn contract_address( + deploying_address: &T::AccountId, + code_hash: &CodeHash, + input_data: &[u8], + salt: &[u8], + ) -> T::AccountId { + T::AddressGenerator::contract_address(deploying_address, code_hash, input_data, salt) + } + + /// Uploads new code and returns the Wasm blob and deposit amount collected. + fn try_upload_code( + origin: T::AccountId, + code: Vec, + storage_deposit_limit: BalanceOf, + mut debug_message: Option<&mut DebugBuffer>, + ) -> Result<(WasmBlob, BalanceOf), DispatchError> { + let mut module = WasmBlob::from_code(code, origin).map_err(|(err, msg)| { + debug_message.as_mut().map(|d| d.try_extend(msg.bytes())); + err + })?; + let deposit = module.store_code()?; + ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); + Ok((module, deposit)) + } + + /// Deposit a pallet contracts event. + fn deposit_event(event: Event) { + >::deposit_event(::RuntimeEvent::from(event)) + } + + /// Deposit a pallet contracts indexed event. + fn deposit_indexed_event(topics: Vec, event: Event) { + >::deposit_event_indexed( + &topics, + ::RuntimeEvent::from(event).into(), + ) + } + + /// Return the existential deposit of [`Config::Currency`]. + fn min_balance() -> BalanceOf { + >>::minimum_balance() + } + + /// Run the supplied function `f` if no other instance of this pallet is on the stack. + fn run_guarded Result>(f: F) -> Result { + executing_contract::using_once(&mut false, || { + executing_contract::with(|f| { + // Fail if already entered contract execution + if *f { + return Err(()) + } + // We are entering contract execution + *f = true; + Ok(()) + }) + .expect("Returns `Ok` if called within `using_once`. It is syntactically obvious that this is the case; qed") + .map_err(|_| >::ReenteredPallet.into()) + .map(|_| f()) + .and_then(|r| r) + }) + } +} + +// Set up a global reference to the boolean flag used for the re-entrancy guard. +environmental!(executing_contract: bool); + +sp_api::decl_runtime_apis! { + /// The API used to dry-run contract interactions. + #[api_version(1)] + pub trait ReviveApi where + AccountId: Codec, + Balance: Codec, + BlockNumber: Codec, + Hash: Codec, + EventRecord: Codec, + { + /// Perform a call from a specified account to a given contract. + /// + /// See [`crate::Pallet::bare_call`]. + fn call( + origin: AccountId, + dest: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + input_data: Vec, + ) -> ContractExecResult; + + /// Instantiate a new contract. + /// + /// See `[crate::Pallet::bare_instantiate]`. + fn instantiate( + origin: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + code: Code, + data: Vec, + salt: Vec, + ) -> ContractInstantiateResult; + + /// Upload new code without instantiating a contract from it. + /// + /// See [`crate::Pallet::bare_upload_code`]. + fn upload_code( + origin: AccountId, + code: Vec, + storage_deposit_limit: Option, + ) -> CodeUploadResult; + + /// Query a given storage key in a given contract. + /// + /// Returns `Ok(Some(Vec))` if the storage value exists under the given key in the + /// specified account and `Ok(None)` if it doesn't. If the account specified by the address + /// doesn't exist, or doesn't have a contract then `Err` is returned. + fn get_storage( + address: AccountId, + key: Vec, + ) -> GetStorageResult; + } +} diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs new file mode 100644 index 000000000000..1a714a89d486 --- /dev/null +++ b/substrate/frame/revive/src/limits.rs @@ -0,0 +1,60 @@ +// 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. + +//! Limits that are observeable by contract code. +//! +//! It is important to never change this limits without supporting the old limits +//! for already deployed contracts. This is what the [`crate::Contract::behaviour_version`] +//! is meant for. This is true for either increasing or decreasing the limit. +//! +//! Limits in this file are different from the limits configured on the [`Config`] trait which are +//! generally only affect actions that cannot be performed by a contract: For example, uploading new +//! code only be done via a transaction but not by a contract. Hence the maximum contract size can +//! be raised (but not lowered) by the runtime configuration. + +/// The maximum depth of the call stack. +/// +/// A 0 means that no callings of other contracts are possible. In other words only the origin +/// called "root contract" is allowed to execute then. +pub const CALL_STACK_DEPTH: u32 = 5; + +/// The maximum number of topics a call to [`crate::SyscallDoc::deposit_event`] can emit. +/// +/// We set it to the same limit that ethereum has. It is unlikely to change. +pub const NUM_EVENT_TOPICS: u32 = 4; + +/// The maximum number of code hashes a contract can lock. +pub const DELEGATE_DEPENDENCIES: u32 = 32; + +/// How much memory do we allow the contract to allocate. +pub const MEMORY_BYTES: u32 = 16 * 64 * 1024; + +/// Maximum size of events (excluding topics) and storage values. +pub const PAYLOAD_BYTES: u32 = 512; + +/// The maximum size of the transient storage in bytes. +/// +/// This includes keys, values, and previous entries used for storage rollback. +pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024; + +/// The maximum allowable length in bytes for (transient) storage keys. +pub const STORAGE_KEY_BYTES: u32 = 128; + +/// The maximum size of the debug buffer contracts can write messages to. +/// +/// The buffer will always be disabled for on-chain execution. +pub const DEBUG_BUFFER_BYTES: u32 = 2 * 1024 * 1024; diff --git a/substrate/frame/revive/src/migration.rs b/substrate/frame/revive/src/migration.rs new file mode 100644 index 000000000000..b67467b322f5 --- /dev/null +++ b/substrate/frame/revive/src/migration.rs @@ -0,0 +1,650 @@ +// 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. + +//! Multi-block Migration framework for pallet-revive. +//! +//! This module allows us to define a migration as a sequence of [`MigrationStep`]s that can be +//! executed across multiple blocks. +//! +//! # Usage +//! +//! A migration step is defined under `src/migration/vX.rs`, where `X` is the version number. +//! For example, `vX.rs` defines a migration from version `X - 1` to version `X`. +//! +//! ## Example: +//! +//! To configure a migration to `v11` for a runtime using `v10` of pallet-revive on the chain, +//! you would set the `Migrations` type as follows: +//! +//! ```ignore +//! use pallet_revive::migration::{v10, v11}; +//! # pub enum Runtime {}; +//! # struct Currency; +//! type Migrations = (v10::Migration, v11::Migration); +//! ``` +//! +//! ## Notes: +//! +//! - Migrations should always be tested with `try-runtime` before being deployed. +//! - By testing with `try-runtime` against a live network, you ensure that all migration steps work +//! and that you have included the required steps. +//! +//! ## Low Level / Implementation Details +//! +//! When a migration starts and [`OnRuntimeUpgrade::on_runtime_upgrade`] is called, instead of +//! performing the actual migration, we set a custom storage item [`MigrationInProgress`]. +//! This storage item defines a [`Cursor`] for the current migration. +//! +//! If the [`MigrationInProgress`] storage item exists, it means a migration is in progress, and its +//! value holds a cursor for the current migration step. These migration steps are executed during +//! [`Hooks::on_idle`] or when the [`Pallet::migrate`] dispatchable is +//! called. +//! +//! While the migration is in progress, all dispatchables except `migrate`, are blocked, and returns +//! a `MigrationInProgress` error. + +include!(concat!(env!("OUT_DIR"), "/migration_codegen.rs")); + +use crate::{weights::WeightInfo, Config, Error, MigrationInProgress, Pallet, Weight, LOG_TARGET}; +use codec::{Codec, Decode}; +use core::marker::PhantomData; +use frame_support::{ + pallet_prelude::*, + traits::{ConstU32, OnRuntimeUpgrade}, + weights::WeightMeter, +}; +use sp_runtime::Saturating; + +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +const PROOF_ENCODE: &str = "Tuple::max_encoded_len() < Cursor::max_encoded_len()` is verified in `Self::integrity_test()`; qed"; +const PROOF_DECODE: &str = + "We encode to the same type in this trait only. No other code touches this item; qed"; + +fn invalid_version(version: StorageVersion) -> ! { + panic!("Required migration {version:?} not supported by this runtime. This is a bug."); +} + +/// The cursor used to encode the position (usually the last iterated key) of the current migration +/// step. +pub type Cursor = BoundedVec>; + +/// IsFinished describes whether a migration is finished or not. +pub enum IsFinished { + Yes, + No, +} + +/// A trait that allows to migrate storage from one version to another. +/// +/// The migration is done in steps. The migration is finished when +/// `step()` returns `IsFinished::Yes`. +pub trait MigrationStep: Codec + MaxEncodedLen + Default { + /// Returns the version of the migration. + const VERSION: u16; + + /// Returns the maximum weight that can be consumed in a single step. + fn max_step_weight() -> Weight; + + /// Process one step of the migration. + /// + /// Returns whether the migration is finished. + fn step(&mut self, meter: &mut WeightMeter) -> IsFinished; + + /// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater + /// than `max_block_weight`. + fn integrity_test(max_block_weight: Weight) { + if Self::max_step_weight().any_gt(max_block_weight) { + panic!( + "Invalid max_step_weight for Migration {}. Value should be lower than {}", + Self::VERSION, + max_block_weight + ); + } + + let len = ::max_encoded_len(); + let max = Cursor::bound(); + if len > max { + panic!( + "Migration {} has size {} which is bigger than the maximum of {}", + Self::VERSION, + len, + max, + ); + } + } + + /// Execute some pre-checks prior to running the first step of this migration. + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step() -> Result, TryRuntimeError> { + Ok(Vec::new()) + } + + /// Execute some post-checks after running the last step of this migration. + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(_state: Vec) -> Result<(), TryRuntimeError> { + Ok(()) + } +} + +/// A noop migration that can be used when there is no migration to be done for a given version. +#[doc(hidden)] +#[derive(frame_support::DefaultNoBound, Encode, Decode, MaxEncodedLen)] +pub struct NoopMigration; + +impl MigrationStep for NoopMigration { + const VERSION: u16 = N; + fn max_step_weight() -> Weight { + Weight::zero() + } + fn step(&mut self, _meter: &mut WeightMeter) -> IsFinished { + log::debug!(target: LOG_TARGET, "Noop migration for version {}", N); + IsFinished::Yes + } +} + +mod private { + use crate::migration::MigrationStep; + pub trait Sealed {} + #[impl_trait_for_tuples::impl_for_tuples(10)] + #[tuple_types_custom_trait_bound(MigrationStep)] + impl Sealed for Tuple {} +} + +/// Defines a sequence of migrations. +/// +/// The sequence must be defined by a tuple of migrations, each of which must implement the +/// `MigrationStep` trait. Migrations must be ordered by their versions with no gaps. +pub trait MigrateSequence: private::Sealed { + /// Returns the range of versions that this migrations sequence can handle. + /// Migrations must be ordered by their versions with no gaps. + /// + /// The following code will fail to compile: + /// + /// ```compile_fail + /// # use pallet_revive::{NoopMigration, MigrateSequence}; + /// let _ = <(NoopMigration<1>, NoopMigration<3>)>::VERSION_RANGE; + /// ``` + /// The following code will compile: + /// ``` + /// # use pallet_revive::{NoopMigration, MigrateSequence}; + /// let _ = <(NoopMigration<1>, NoopMigration<2>)>::VERSION_RANGE; + /// ``` + const VERSION_RANGE: (u16, u16); + + /// Returns the default cursor for the given version. + fn new(version: StorageVersion) -> Cursor; + + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step(_version: StorageVersion) -> Result, TryRuntimeError> { + Ok(Vec::new()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(_version: StorageVersion, _state: Vec) -> Result<(), TryRuntimeError> { + Ok(()) + } + + /// Execute the migration step until the available weight is consumed. + fn steps(version: StorageVersion, cursor: &[u8], meter: &mut WeightMeter) -> StepResult; + + /// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater + /// than `max_block_weight`. + fn integrity_test(max_block_weight: Weight); + + /// Returns whether migrating from `in_storage` to `target` is supported. + /// + /// A migration is supported if `VERSION_RANGE` is (in_storage + 1, target). + fn is_upgrade_supported(in_storage: StorageVersion, target: StorageVersion) -> bool { + let (low, high) = Self::VERSION_RANGE; + target == high && in_storage + 1 == low + } +} + +/// Performs all necessary migrations based on `StorageVersion`. +/// +/// If `TEST_ALL_STEPS == true` and `try-runtime` is enabled, this will run all the migrations +/// inside `on_runtime_upgrade`. This should be set to false in tests that want to ensure the step +/// by step migration works. +pub struct Migration(PhantomData); + +#[cfg(feature = "try-runtime")] +impl Migration { + fn run_all_steps() -> Result<(), TryRuntimeError> { + let mut meter = &mut WeightMeter::new(); + let name = >::name(); + loop { + let in_progress_version = >::on_chain_storage_version() + 1; + let state = T::Migrations::pre_upgrade_step(in_progress_version)?; + let before = meter.consumed(); + let status = Self::migrate(&mut meter); + log::info!( + target: LOG_TARGET, + "{name}: Migration step {:?} weight = {}", + in_progress_version, + meter.consumed() - before + ); + T::Migrations::post_upgrade_step(in_progress_version, state)?; + if matches!(status, MigrateResult::Completed) { + break + } + } + + let name = >::name(); + log::info!(target: LOG_TARGET, "{name}: Migration steps weight = {}", meter.consumed()); + Ok(()) + } +} + +impl OnRuntimeUpgrade for Migration { + fn on_runtime_upgrade() -> Weight { + let name = >::name(); + let in_code_version = >::in_code_storage_version(); + let on_chain_version = >::on_chain_storage_version(); + + if on_chain_version == in_code_version { + log::warn!( + target: LOG_TARGET, + "{name}: No Migration performed storage_version = latest_version = {:?}", + &on_chain_version + ); + return T::WeightInfo::on_runtime_upgrade_noop() + } + + // In case a migration is already in progress we create the next migration + // (if any) right when the current one finishes. + if Self::in_progress() { + log::warn!( + target: LOG_TARGET, + "{name}: Migration already in progress {:?}", + &on_chain_version + ); + + return T::WeightInfo::on_runtime_upgrade_in_progress() + } + + log::info!( + target: LOG_TARGET, + "{name}: Upgrading storage from {on_chain_version:?} to {in_code_version:?}.", + ); + + let cursor = T::Migrations::new(on_chain_version + 1); + MigrationInProgress::::set(Some(cursor)); + + #[cfg(feature = "try-runtime")] + if TEST_ALL_STEPS { + Self::run_all_steps().unwrap(); + } + + T::WeightInfo::on_runtime_upgrade() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + // We can't really do much here as our migrations do not happen during the runtime upgrade. + // Instead, we call the migrations `pre_upgrade` and `post_upgrade` hooks when we iterate + // over our migrations. + let on_chain_version = >::on_chain_storage_version(); + let in_code_version = >::in_code_storage_version(); + + if on_chain_version == in_code_version { + return Ok(Default::default()) + } + + log::debug!( + target: LOG_TARGET, + "Requested migration of {} from {:?}(on-chain storage version) to {:?}(in-code storage version)", + >::name(), on_chain_version, in_code_version + ); + + ensure!( + T::Migrations::is_upgrade_supported(on_chain_version, in_code_version), + "Unsupported upgrade: VERSION_RANGE should be (on-chain storage version + 1, in-code storage version)" + ); + + Ok(Default::default()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + if !TEST_ALL_STEPS { + return Ok(()) + } + + log::info!(target: LOG_TARGET, "=== POST UPGRADE CHECKS ==="); + + // Ensure that the hashing algorithm is correct for each storage map. + if let Some(hash) = crate::CodeInfoOf::::iter_keys().next() { + crate::CodeInfoOf::::get(hash).expect("CodeInfo exists for hash; qed"); + } + if let Some(hash) = crate::PristineCode::::iter_keys().next() { + crate::PristineCode::::get(hash).expect("PristineCode exists for hash; qed"); + } + if let Some(account_id) = crate::ContractInfoOf::::iter_keys().next() { + crate::ContractInfoOf::::get(account_id) + .expect("ContractInfo exists for account_id; qed"); + } + if let Some(nonce) = crate::DeletionQueue::::iter_keys().next() { + crate::DeletionQueue::::get(nonce).expect("DeletionQueue exists for nonce; qed"); + } + + Ok(()) + } +} + +/// The result of running the migration. +#[derive(Debug, PartialEq)] +pub enum MigrateResult { + /// No migration was performed + NoMigrationPerformed, + /// No migration currently in progress + NoMigrationInProgress, + /// A migration is in progress + InProgress { steps_done: u32 }, + /// All migrations are completed + Completed, +} + +/// The result of running a migration step. +#[derive(Debug, PartialEq)] +pub enum StepResult { + InProgress { cursor: Cursor, steps_done: u32 }, + Completed { steps_done: u32 }, +} + +impl Migration { + /// Verify that each migration's step of the [`Config::Migrations`] sequence fits into + /// `Cursor`. + pub(crate) fn integrity_test() { + let max_weight = ::BlockWeights::get().max_block; + T::Migrations::integrity_test(max_weight) + } + + /// Execute the multi-step migration. + /// Returns whether or not a migration is in progress + pub(crate) fn migrate(mut meter: &mut WeightMeter) -> MigrateResult { + let name = >::name(); + + if meter.try_consume(T::WeightInfo::migrate()).is_err() { + return MigrateResult::NoMigrationPerformed + } + + MigrationInProgress::::mutate_exists(|progress| { + let Some(cursor_before) = progress.as_mut() else { + meter.consume(T::WeightInfo::migration_noop()); + return MigrateResult::NoMigrationInProgress + }; + + // if a migration is running it is always upgrading to the next version + let storage_version = >::on_chain_storage_version(); + let in_progress_version = storage_version + 1; + + log::info!( + target: LOG_TARGET, + "{name}: Migrating from {:?} to {:?},", + storage_version, + in_progress_version, + ); + + let result = + match T::Migrations::steps(in_progress_version, cursor_before.as_ref(), &mut meter) + { + StepResult::InProgress { cursor, steps_done } => { + *progress = Some(cursor); + MigrateResult::InProgress { steps_done } + }, + StepResult::Completed { steps_done } => { + in_progress_version.put::>(); + if >::in_code_storage_version() != in_progress_version { + log::info!( + target: LOG_TARGET, + "{name}: Next migration is {:?},", + in_progress_version + 1 + ); + *progress = Some(T::Migrations::new(in_progress_version + 1)); + MigrateResult::InProgress { steps_done } + } else { + log::info!( + target: LOG_TARGET, + "{name}: All migrations done. At version {:?},", + in_progress_version + ); + *progress = None; + MigrateResult::Completed + } + }, + }; + + result + }) + } + + pub(crate) fn ensure_migrated() -> DispatchResult { + if Self::in_progress() { + Err(Error::::MigrationInProgress.into()) + } else { + Ok(()) + } + } + + pub(crate) fn in_progress() -> bool { + MigrationInProgress::::exists() + } +} + +#[impl_trait_for_tuples::impl_for_tuples(10)] +#[tuple_types_custom_trait_bound(MigrationStep)] +impl MigrateSequence for Tuple { + const VERSION_RANGE: (u16, u16) = { + let mut versions: (u16, u16) = (0, 0); + for_tuples!( + #( + match versions { + (0, 0) => { + versions = (Tuple::VERSION, Tuple::VERSION); + }, + (min_version, last_version) if Tuple::VERSION == last_version + 1 => { + versions = (min_version, Tuple::VERSION); + }, + _ => panic!("Migrations must be ordered by their versions with no gaps.") + } + )* + ); + versions + }; + + fn new(version: StorageVersion) -> Cursor { + for_tuples!( + #( + if version == Tuple::VERSION { + return Tuple::default().encode().try_into().expect(PROOF_ENCODE) + } + )* + ); + invalid_version(version) + } + + #[cfg(feature = "try-runtime")] + /// Execute the pre-checks of the step associated with this version. + fn pre_upgrade_step(version: StorageVersion) -> Result, TryRuntimeError> { + for_tuples!( + #( + if version == Tuple::VERSION { + return Tuple::pre_upgrade_step() + } + )* + ); + invalid_version(version) + } + + #[cfg(feature = "try-runtime")] + /// Execute the post-checks of the step associated with this version. + fn post_upgrade_step(version: StorageVersion, state: Vec) -> Result<(), TryRuntimeError> { + for_tuples!( + #( + if version == Tuple::VERSION { + return Tuple::post_upgrade_step(state) + } + )* + ); + invalid_version(version) + } + + fn steps(version: StorageVersion, mut cursor: &[u8], meter: &mut WeightMeter) -> StepResult { + for_tuples!( + #( + if version == Tuple::VERSION { + let mut migration = ::decode(&mut cursor) + .expect(PROOF_DECODE); + let max_weight = Tuple::max_step_weight(); + let mut steps_done = 0; + while meter.can_consume(max_weight) { + steps_done.saturating_accrue(1); + if matches!(migration.step(meter), IsFinished::Yes) { + return StepResult::Completed{ steps_done } + } + } + return StepResult::InProgress{cursor: migration.encode().try_into().expect(PROOF_ENCODE), steps_done } + } + )* + ); + invalid_version(version) + } + + fn integrity_test(max_block_weight: Weight) { + for_tuples!( + #( + Tuple::integrity_test(max_block_weight); + )* + ); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + migration::codegen::LATEST_MIGRATION_VERSION, + tests::{ExtBuilder, Test}, + }; + + #[derive(Default, Encode, Decode, MaxEncodedLen)] + struct MockMigration { + // MockMigration needs `N` steps to finish + count: u16, + } + + impl MigrationStep for MockMigration { + const VERSION: u16 = N; + fn max_step_weight() -> Weight { + Weight::from_all(1) + } + fn step(&mut self, meter: &mut WeightMeter) -> IsFinished { + assert!(self.count != N); + self.count += 1; + meter.consume(Weight::from_all(1)); + if self.count == N { + IsFinished::Yes + } else { + IsFinished::No + } + } + } + + #[test] + fn test_storage_version_matches_last_migration_file() { + assert_eq!(StorageVersion::new(LATEST_MIGRATION_VERSION), crate::pallet::STORAGE_VERSION); + } + + #[test] + fn version_range_works() { + let range = <(MockMigration<1>, MockMigration<2>)>::VERSION_RANGE; + assert_eq!(range, (1, 2)); + } + + #[test] + fn is_upgrade_supported_works() { + type Migrations = (MockMigration<9>, MockMigration<10>, MockMigration<11>); + assert!(Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(11))); + assert!(!Migrations::is_upgrade_supported(StorageVersion::new(9), StorageVersion::new(11))); + assert!(!Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(12))); + } + + #[test] + fn steps_works() { + type Migrations = (MockMigration<2>, MockMigration<3>); + let version = StorageVersion::new(2); + let mut cursor = Migrations::new(version); + + let mut meter = WeightMeter::with_limit(Weight::from_all(1)); + let result = Migrations::steps(version, &cursor, &mut meter); + cursor = alloc::vec![1u8, 0].try_into().unwrap(); + assert_eq!(result, StepResult::InProgress { cursor: cursor.clone(), steps_done: 1 }); + assert_eq!(meter.consumed(), Weight::from_all(1)); + + let mut meter = WeightMeter::with_limit(Weight::from_all(1)); + assert_eq!( + Migrations::steps(version, &cursor, &mut meter), + StepResult::Completed { steps_done: 1 } + ); + } + + #[test] + fn no_migration_in_progress_works() { + type TestMigration = Migration; + + ExtBuilder::default().build().execute_with(|| { + assert_eq!(StorageVersion::get::>(), LATEST_MIGRATION_VERSION); + assert_eq!( + TestMigration::migrate(&mut WeightMeter::new()), + MigrateResult::NoMigrationInProgress + ) + }); + } + + #[test] + fn migration_works() { + type TestMigration = Migration; + + ExtBuilder::default() + .set_storage_version(LATEST_MIGRATION_VERSION - 2) + .build() + .execute_with(|| { + assert_eq!(StorageVersion::get::>(), LATEST_MIGRATION_VERSION - 2); + TestMigration::on_runtime_upgrade(); + for (version, status) in [ + (LATEST_MIGRATION_VERSION - 1, MigrateResult::InProgress { steps_done: 1 }), + (LATEST_MIGRATION_VERSION, MigrateResult::Completed), + ] { + assert_eq!(TestMigration::migrate(&mut WeightMeter::new()), status); + assert_eq!( + >::on_chain_storage_version(), + StorageVersion::new(version) + ); + } + + assert_eq!( + TestMigration::migrate(&mut WeightMeter::new()), + MigrateResult::NoMigrationInProgress + ); + assert_eq!(StorageVersion::get::>(), LATEST_MIGRATION_VERSION); + }); + } +} diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs new file mode 100644 index 000000000000..a4a1133b7104 --- /dev/null +++ b/substrate/frame/revive/src/primitives.rs @@ -0,0 +1,285 @@ +// 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 crate that hosts a common definitions that are relevant for the pallet-revive. + +use alloc::vec::Vec; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::weights::Weight; +use pallet_revive_uapi::ReturnFlags; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{Saturating, Zero}, + DispatchError, RuntimeDebug, +}; + +/// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and +/// `ContractsApi::instantiate`. +/// +/// It contains the execution result together with some auxiliary information. +/// +/// #Note +/// +/// It has been extended to include `events` at the end of the struct while not bumping the +/// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data +/// should be ignored to avoid any potential compatibility issues. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ContractResult { + /// How much weight was consumed during execution. + pub gas_consumed: Weight, + /// How much weight is required as gas limit in order to execute this call. + /// + /// This value should be used to determine the weight limit for on-chain execution. + /// + /// # Note + /// + /// This can only different from [`Self::gas_consumed`] when weight pre charging + /// is used. Currently, only `seal_call_runtime` makes use of pre charging. + /// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging + /// when a non-zero `gas_limit` argument is supplied. + pub gas_required: Weight, + /// How much balance was paid by the origin into the contract's deposit account in order to + /// pay for storage. + /// + /// The storage deposit is never actually charged from the origin in case of [`Self::result`] + /// is `Err`. This is because on error all storage changes are rolled back including the + /// payment of the deposit. + pub storage_deposit: StorageDeposit, + /// An optional debug message. This message is only filled when explicitly requested + /// by the code that calls into the contract. Otherwise it is empty. + /// + /// The contained bytes are valid UTF-8. This is not declared as `String` because + /// this type is not allowed within the runtime. + /// + /// Clients should not make any assumptions about the format of the buffer. + /// They should just display it as-is. It is **not** only a collection of log lines + /// provided by a contract but a formatted buffer with different sections. + /// + /// # Note + /// + /// The debug message is never generated during on-chain execution. It is reserved for + /// RPC calls. + pub debug_message: Vec, + /// The execution result of the wasm code. + pub result: R, + /// The events that were emitted during execution. It is an option as event collection is + /// optional. + pub events: Option>, +} + +/// Result type of a `bare_call` call as well as `ContractsApi::call`. +pub type ContractExecResult = + ContractResult, Balance, EventRecord>; + +/// Result type of a `bare_instantiate` call as well as `ContractsApi::instantiate`. +pub type ContractInstantiateResult = + ContractResult, DispatchError>, Balance, EventRecord>; + +/// Result type of a `bare_code_upload` call. +pub type CodeUploadResult = + Result, DispatchError>; + +/// Result type of a `get_storage` call. +pub type GetStorageResult = Result>, ContractAccessError>; + +/// The possible errors that can happen querying the storage of a contract. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)] +pub enum ContractAccessError { + /// The given address doesn't point to a contract. + DoesntExist, + /// Storage key cannot be decoded from the provided input data. + KeyDecodingFailed, + /// Storage is migrating. Try again later. + MigrationInProgress, +} + +/// Output of a contract call or instantiation which ran to completion. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ExecReturnValue { + /// Flags passed along by `seal_return`. Empty when `seal_return` was never called. + pub flags: ReturnFlags, + /// Buffer passed along by `seal_return`. Empty when `seal_return` was never called. + pub data: Vec, +} + +impl ExecReturnValue { + /// The contract did revert all storage changes. + pub fn did_revert(&self) -> bool { + self.flags.contains(ReturnFlags::REVERT) + } +} + +/// The result of a successful contract instantiation. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct InstantiateReturnValue { + /// The output of the called constructor. + pub result: ExecReturnValue, + /// The account id of the new contract. + pub account_id: AccountId, +} + +/// The result of successfully uploading a contract. +#[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)] +pub struct CodeUploadReturnValue { + /// The key under which the new code is stored. + pub code_hash: CodeHash, + /// The deposit that was reserved at the caller. Is zero when the code already existed. + pub deposit: Balance, +} + +/// Reference to an existing code hash or a new wasm module. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum Code { + /// A wasm module as raw bytes. + Upload(Vec), + /// The code hash of an on-chain wasm blob. + Existing(Hash), +} + +/// The amount of balance that was either charged or refunded in order to pay for storage. +#[derive( + Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo, +)] +pub enum StorageDeposit { + /// The transaction reduced storage consumption. + /// + /// This means that the specified amount of balance was transferred from the involved + /// deposit accounts to the origin. + Refund(Balance), + /// The transaction increased storage consumption. + /// + /// This means that the specified amount of balance was transferred from the origin + /// to the involved deposit accounts. + Charge(Balance), +} + +impl Default for StorageDeposit { + fn default() -> Self { + Self::Charge(Zero::zero()) + } +} + +impl StorageDeposit { + /// Returns how much balance is charged or `0` in case of a refund. + pub fn charge_or_zero(&self) -> Balance { + match self { + Self::Charge(amount) => *amount, + Self::Refund(_) => Zero::zero(), + } + } + + pub fn is_zero(&self) -> bool { + match self { + Self::Charge(amount) => amount.is_zero(), + Self::Refund(amount) => amount.is_zero(), + } + } +} + +impl StorageDeposit +where + Balance: Saturating + Ord + Copy, +{ + /// This is essentially a saturating signed add. + pub fn saturating_add(&self, rhs: &Self) -> Self { + use StorageDeposit::*; + match (self, rhs) { + (Charge(lhs), Charge(rhs)) => Charge(lhs.saturating_add(*rhs)), + (Refund(lhs), Refund(rhs)) => Refund(lhs.saturating_add(*rhs)), + (Charge(lhs), Refund(rhs)) => + if lhs >= rhs { + Charge(lhs.saturating_sub(*rhs)) + } else { + Refund(rhs.saturating_sub(*lhs)) + }, + (Refund(lhs), Charge(rhs)) => + if lhs > rhs { + Refund(lhs.saturating_sub(*rhs)) + } else { + Charge(rhs.saturating_sub(*lhs)) + }, + } + } + + /// This is essentially a saturating signed sub. + pub fn saturating_sub(&self, rhs: &Self) -> Self { + use StorageDeposit::*; + match (self, rhs) { + (Charge(lhs), Refund(rhs)) => Charge(lhs.saturating_add(*rhs)), + (Refund(lhs), Charge(rhs)) => Refund(lhs.saturating_add(*rhs)), + (Charge(lhs), Charge(rhs)) => + if lhs >= rhs { + Charge(lhs.saturating_sub(*rhs)) + } else { + Refund(rhs.saturating_sub(*lhs)) + }, + (Refund(lhs), Refund(rhs)) => + if lhs > rhs { + Refund(lhs.saturating_sub(*rhs)) + } else { + Charge(rhs.saturating_sub(*lhs)) + }, + } + } + + /// If the amount of deposit (this type) is constrained by a `limit` this calculates how + /// much balance (if any) is still available from this limit. + /// + /// # Note + /// + /// In case of a refund the return value can be larger than `limit`. + pub fn available(&self, limit: &Balance) -> Balance { + use StorageDeposit::*; + match self { + Charge(amount) => limit.saturating_sub(*amount), + Refund(amount) => limit.saturating_add(*amount), + } + } +} + +/// Determines whether events should be collected during execution. +#[derive( + Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo, +)] +pub enum CollectEvents { + /// Collect events. + /// + /// # Note + /// + /// Events should only be collected when called off-chain, as this would otherwise + /// collect all the Events emitted in the block so far and put them into the PoV. + /// + /// **Never** use this mode for on-chain execution. + UnsafeCollect, + /// Skip event collection. + Skip, +} + +/// Determines whether debug messages will be collected. +#[derive( + Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo, +)] +pub enum DebugInfo { + /// Collect debug messages. + /// # Note + /// + /// This should only ever be set to `UnsafeDebug` when executing as an RPC because + /// it adds allocations and could be abused to drive the runtime into an OOM panic. + UnsafeDebug, + /// Skip collection of debug messages. + Skip, +} diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs new file mode 100644 index 000000000000..87274ce407fa --- /dev/null +++ b/substrate/frame/revive/src/storage.rs @@ -0,0 +1,482 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains routines for accessing and altering a contract related state. + +pub mod meter; + +use crate::{ + exec::{AccountIdOf, Key}, + limits, + storage::meter::Diff, + weights::WeightInfo, + BalanceOf, CodeHash, CodeInfo, Config, ContractInfoOf, DeletionQueue, DeletionQueueCounter, + Error, TrieId, SENTINEL, +}; +use alloc::vec::Vec; +use codec::{Decode, Encode, MaxEncodedLen}; +use core::marker::PhantomData; +use frame_support::{ + storage::child::{self, ChildInfo}, + weights::{Weight, WeightMeter}, + CloneNoBound, DefaultNoBound, +}; +use scale_info::TypeInfo; +use sp_core::{ConstU32, Get}; +use sp_io::KillStorageResult; +use sp_runtime::{ + traits::{Hash, Saturating, Zero}, + BoundedBTreeMap, DispatchError, DispatchResult, RuntimeDebug, +}; + +type DelegateDependencyMap = + BoundedBTreeMap, BalanceOf, ConstU32<{ limits::DELEGATE_DEPENDENCIES }>>; + +/// Information for managing an account and its sub trie abstraction. +/// This is the required info to cache for an account. +#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(T))] +pub struct ContractInfo { + /// Unique ID for the subtree encoded as a bytes vector. + pub trie_id: TrieId, + /// The code associated with a given account. + pub code_hash: CodeHash, + /// How many bytes of storage are accumulated in this contract's child trie. + storage_bytes: u32, + /// How many items of storage are accumulated in this contract's child trie. + storage_items: u32, + /// This records to how much deposit the accumulated `storage_bytes` amount to. + pub storage_byte_deposit: BalanceOf, + /// This records to how much deposit the accumulated `storage_items` amount to. + storage_item_deposit: BalanceOf, + /// This records how much deposit is put down in order to pay for the contract itself. + /// + /// We need to store this information separately so it is not used when calculating any refunds + /// since the base deposit can only ever be refunded on contract termination. + storage_base_deposit: BalanceOf, + /// Map of code hashes and deposit balances. + /// + /// Tracks the code hash and deposit held for locking delegate dependencies. Dependencies added + /// to the map can not be removed from the chain state and can be safely used for delegate + /// calls. + delegate_dependencies: DelegateDependencyMap, +} + +impl ContractInfo { + /// Constructs a new contract info **without** writing it to storage. + /// + /// This returns an `Err` if an contract with the supplied `account` already exists + /// in storage. + pub fn new( + account: &AccountIdOf, + nonce: T::Nonce, + code_hash: CodeHash, + ) -> Result { + if >::contains_key(account) { + return Err(Error::::DuplicateContract.into()) + } + + let trie_id = { + let buf = ("bcontract_trie_v1", account, nonce).using_encoded(T::Hashing::hash); + buf.as_ref() + .to_vec() + .try_into() + .expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed") + }; + + let contract = Self { + trie_id, + code_hash, + storage_bytes: 0, + storage_items: 0, + storage_byte_deposit: Zero::zero(), + storage_item_deposit: Zero::zero(), + storage_base_deposit: Zero::zero(), + delegate_dependencies: Default::default(), + }; + + Ok(contract) + } + + /// Returns the number of locked delegate dependencies. + pub fn delegate_dependencies_count(&self) -> usize { + self.delegate_dependencies.len() + } + + /// Associated child trie unique id is built from the hash part of the trie id. + pub fn child_trie_info(&self) -> ChildInfo { + ChildInfo::new_default(self.trie_id.as_ref()) + } + + /// The deposit paying for the accumulated storage generated within the contract's child trie. + pub fn extra_deposit(&self) -> BalanceOf { + self.storage_byte_deposit.saturating_add(self.storage_item_deposit) + } + + /// Same as [`Self::extra_deposit`] but including the base deposit. + pub fn total_deposit(&self) -> BalanceOf { + self.extra_deposit().saturating_add(self.storage_base_deposit) + } + + /// Returns the storage base deposit of the contract. + pub fn storage_base_deposit(&self) -> BalanceOf { + self.storage_base_deposit + } + + /// Reads a storage kv pair of a contract. + /// + /// The read is performed from the `trie_id` only. The `address` is not necessary. If the + /// contract doesn't store under the given `key` `None` is returned. + pub fn read(&self, key: &Key) -> Option> { + child::get_raw(&self.child_trie_info(), key.hash().as_slice()) + } + + /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + pub fn size(&self, key: &Key) -> Option { + child::len(&self.child_trie_info(), key.hash().as_slice()) + } + + /// Update a storage entry into a contract's kv storage. + /// + /// If the `new_value` is `None` then the kv pair is removed. If `take` is true + /// a [`WriteOutcome::Taken`] is returned instead of a [`WriteOutcome::Overwritten`]. + /// + /// This function also records how much storage was created or removed if a `storage_meter` + /// is supplied. It should only be absent for testing or benchmarking code. + pub fn write( + &self, + key: &Key, + new_value: Option>, + storage_meter: Option<&mut meter::NestedMeter>, + take: bool, + ) -> Result { + let hashed_key = key.hash(); + self.write_raw(&hashed_key, new_value, storage_meter, take) + } + + /// Update a storage entry into a contract's kv storage. + /// Function used in benchmarks, which can simulate prefix collision in keys. + #[cfg(feature = "runtime-benchmarks")] + pub fn bench_write_raw( + &self, + key: &[u8], + new_value: Option>, + take: bool, + ) -> Result { + self.write_raw(key, new_value, None, take) + } + + fn write_raw( + &self, + key: &[u8], + new_value: Option>, + storage_meter: Option<&mut meter::NestedMeter>, + take: bool, + ) -> Result { + let child_trie_info = &self.child_trie_info(); + let (old_len, old_value) = if take { + let val = child::get_raw(child_trie_info, key); + (val.as_ref().map(|v| v.len() as u32), val) + } else { + (child::len(child_trie_info, key), None) + }; + + if let Some(storage_meter) = storage_meter { + let mut diff = meter::Diff::default(); + match (old_len, new_value.as_ref().map(|v| v.len() as u32)) { + (Some(old_len), Some(new_len)) => + if new_len > old_len { + diff.bytes_added = new_len - old_len; + } else { + diff.bytes_removed = old_len - new_len; + }, + (None, Some(new_len)) => { + diff.bytes_added = new_len; + diff.items_added = 1; + }, + (Some(old_len), None) => { + diff.bytes_removed = old_len; + diff.items_removed = 1; + }, + (None, None) => (), + } + storage_meter.charge(&diff); + } + + match &new_value { + Some(new_value) => child::put_raw(child_trie_info, key, new_value), + None => child::kill(child_trie_info, key), + } + + Ok(match (old_len, old_value) { + (None, _) => WriteOutcome::New, + (Some(old_len), None) => WriteOutcome::Overwritten(old_len), + (Some(_), Some(old_value)) => WriteOutcome::Taken(old_value), + }) + } + + /// Sets and returns the contract base deposit. + /// + /// The base deposit is updated when the `code_hash` of the contract changes, as it depends on + /// the deposit paid to upload the contract's code. + pub fn update_base_deposit(&mut self, code_info: &CodeInfo) -> BalanceOf { + let info_deposit = + Diff { bytes_added: self.encoded_size() as u32, items_added: 1, ..Default::default() } + .update_contract::(None) + .charge_or_zero(); + + // Instantiating the contract prevents its code to be deleted, therefore the base deposit + // includes a fraction (`T::CodeHashLockupDepositPercent`) of the original storage deposit + // to prevent abuse. + let upload_deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit()); + + let deposit = info_deposit.saturating_add(upload_deposit); + self.storage_base_deposit = deposit; + deposit + } + + /// Adds a new delegate dependency to the contract. + /// The `amount` is the amount of funds that will be reserved for the dependency. + /// + /// Returns an error if the maximum number of delegate_dependencies is reached or if + /// the delegate dependency already exists. + pub fn lock_delegate_dependency( + &mut self, + code_hash: CodeHash, + amount: BalanceOf, + ) -> DispatchResult { + self.delegate_dependencies + .try_insert(code_hash, amount) + .map_err(|_| Error::::MaxDelegateDependenciesReached)? + .map_or(Ok(()), |_| Err(Error::::DelegateDependencyAlreadyExists)) + .map_err(Into::into) + } + + /// Removes the delegate dependency from the contract and returns the deposit held for this + /// dependency. + /// + /// Returns an error if the entry doesn't exist. + pub fn unlock_delegate_dependency( + &mut self, + code_hash: &CodeHash, + ) -> Result, DispatchError> { + self.delegate_dependencies + .remove(code_hash) + .ok_or(Error::::DelegateDependencyNotFound.into()) + } + + /// Returns the delegate_dependencies of the contract. + pub fn delegate_dependencies(&self) -> &DelegateDependencyMap { + &self.delegate_dependencies + } + + /// Push a contract's trie to the deletion queue for lazy removal. + /// + /// You must make sure that the contract is also removed when queuing the trie for deletion. + pub fn queue_trie_for_deletion(&self) { + DeletionQueueManager::::load().insert(self.trie_id.clone()); + } + + /// Calculates the weight that is necessary to remove one key from the trie and how many + /// of those keys can be deleted from the deletion queue given the supplied weight limit. + pub fn deletion_budget(meter: &WeightMeter) -> (Weight, u32) { + let base_weight = T::WeightInfo::on_process_deletion_queue_batch(); + let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) - + T::WeightInfo::on_initialize_per_trie_key(0); + + // `weight_per_key` being zero makes no sense and would constitute a failure to + // benchmark properly. We opt for not removing any keys at all in this case. + let key_budget = meter + .limit() + .saturating_sub(base_weight) + .checked_div_per_component(&weight_per_key) + .unwrap_or(0) as u32; + + (weight_per_key, key_budget) + } + + /// Delete as many items from the deletion queue possible within the supplied weight limit. + pub fn process_deletion_queue_batch(meter: &mut WeightMeter) { + if meter.try_consume(T::WeightInfo::on_process_deletion_queue_batch()).is_err() { + return + }; + + let mut queue = >::load(); + if queue.is_empty() { + return; + } + + let (weight_per_key, budget) = Self::deletion_budget(&meter); + let mut remaining_key_budget = budget; + while remaining_key_budget > 0 { + let Some(entry) = queue.next() else { break }; + + #[allow(deprecated)] + let outcome = child::kill_storage( + &ChildInfo::new_default(&entry.trie_id), + Some(remaining_key_budget), + ); + + match outcome { + // This happens when our budget wasn't large enough to remove all keys. + KillStorageResult::SomeRemaining(keys_removed) => { + remaining_key_budget.saturating_reduce(keys_removed); + break + }, + KillStorageResult::AllRemoved(keys_removed) => { + entry.remove(); + // charge at least one key even if none were removed. + remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed.max(1)); + }, + }; + } + + meter.consume(weight_per_key.saturating_mul(u64::from(budget - remaining_key_budget))) + } + + /// Returns the code hash of the contract specified by `account` ID. + pub fn load_code_hash(account: &AccountIdOf) -> Option> { + >::get(account).map(|i| i.code_hash) + } +} + +/// Information about what happened to the pre-existing value when calling [`ContractInfo::write`]. +#[cfg_attr(any(test, feature = "runtime-benchmarks"), derive(Debug, PartialEq))] +pub enum WriteOutcome { + /// No value existed at the specified key. + New, + /// A value of the returned length was overwritten. + Overwritten(u32), + /// The returned value was taken out of storage before being overwritten. + /// + /// This is only returned when specifically requested because it causes additional work + /// depending on the size of the pre-existing value. When not requested [`Self::Overwritten`] + /// is returned instead. + Taken(Vec), +} + +impl WriteOutcome { + /// Extracts the size of the overwritten value or `0` if there + /// was no value in storage. + pub fn old_len(&self) -> u32 { + match self { + Self::New => 0, + Self::Overwritten(len) => *len, + Self::Taken(value) => value.len() as u32, + } + } + + /// Extracts the size of the overwritten value or `SENTINEL` if there + /// was no value in storage. + /// + /// # Note + /// + /// We cannot use `0` as sentinel value because there could be a zero sized + /// storage entry which is different from a non existing one. + pub fn old_len_with_sentinel(&self) -> u32 { + match self { + Self::New => SENTINEL, + Self::Overwritten(len) => *len, + Self::Taken(value) => value.len() as u32, + } + } +} + +/// Manage the removal of contracts storage that are marked for deletion. +/// +/// When a contract is deleted by calling `seal_terminate` it becomes inaccessible +/// immediately, but the deletion of the storage items it has accumulated is performed +/// later by pulling the contract from the queue in the `on_idle` hook. +#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)] +#[scale_info(skip_type_params(T))] +pub struct DeletionQueueManager { + /// Counter used as a key for inserting a new deleted contract in the queue. + /// The counter is incremented after each insertion. + insert_counter: u32, + /// The index used to read the next element to be deleted in the queue. + /// The counter is incremented after each deletion. + delete_counter: u32, + + _phantom: PhantomData, +} + +/// View on a contract that is marked for deletion. +struct DeletionQueueEntry<'a, T: Config> { + /// the trie id of the contract to delete. + trie_id: TrieId, + + /// A mutable reference on the queue so that the contract can be removed, and none can be added + /// or read in the meantime. + queue: &'a mut DeletionQueueManager, +} + +impl<'a, T: Config> DeletionQueueEntry<'a, T> { + /// Remove the contract from the deletion queue. + fn remove(self) { + >::remove(self.queue.delete_counter); + self.queue.delete_counter = self.queue.delete_counter.wrapping_add(1); + >::set(self.queue.clone()); + } +} + +impl DeletionQueueManager { + /// Load the `DeletionQueueCounter`, so we can perform read or write operations on the + /// DeletionQueue storage. + fn load() -> Self { + >::get() + } + + /// Returns `true` if the queue contains no elements. + fn is_empty(&self) -> bool { + self.insert_counter.wrapping_sub(self.delete_counter) == 0 + } + + /// Insert a contract in the deletion queue. + fn insert(&mut self, trie_id: TrieId) { + >::insert(self.insert_counter, trie_id); + self.insert_counter = self.insert_counter.wrapping_add(1); + >::set(self.clone()); + } + + /// Fetch the next contract to be deleted. + /// + /// Note: + /// we use the delete counter to get the next value to read from the queue and thus don't pay + /// the cost of an extra call to `sp_io::storage::next_key` to lookup the next entry in the map + fn next(&mut self) -> Option> { + if self.is_empty() { + return None + } + + let entry = >::get(self.delete_counter); + entry.map(|trie_id| DeletionQueueEntry { trie_id, queue: self }) + } +} + +#[cfg(test)] +#[cfg(feature = "riscv")] +impl DeletionQueueManager { + pub fn from_test_values(insert_counter: u32, delete_counter: u32) -> Self { + Self { insert_counter, delete_counter, _phantom: Default::default() } + } + pub fn as_test_tuple(&self) -> (u32, u32) { + (self.insert_counter, self.delete_counter) + } +} diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs new file mode 100644 index 000000000000..8735aa823421 --- /dev/null +++ b/substrate/frame/revive/src/storage/meter.rs @@ -0,0 +1,888 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains functions to meter the storage deposit. + +use crate::{ + storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, Event, HoldReason, + Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, +}; + +use alloc::vec::Vec; +use core::{fmt::Debug, marker::PhantomData}; +use frame_support::{ + traits::{ + fungible::{Mutate, MutateHold}, + tokens::{Fortitude, Fortitude::Polite, Precision, Preservation, Restriction}, + Get, + }, + DefaultNoBound, RuntimeDebugNoBound, +}; +use sp_runtime::{ + traits::{Saturating, Zero}, + DispatchError, FixedPointNumber, FixedU128, +}; + +/// Deposit that uses the native fungible's balance type. +pub type DepositOf = Deposit>; + +/// A production root storage meter that actually charges from its origin. +pub type Meter = RawMeter; + +/// A production nested storage meter that actually charges from its origin. +pub type NestedMeter = RawMeter; + +/// A production storage meter that actually charges from its origin. +/// +/// This can be used where we want to be generic over the state (Root vs. Nested). +pub type GenericMeter = RawMeter; + +/// A trait that allows to decouple the metering from the charging of balance. +/// +/// This mostly exists for testing so that the charging can be mocked. +pub trait Ext { + /// This checks whether `origin` is able to afford the storage deposit limit. + /// + /// It is necessary to do this check beforehand so that the charge won't fail later on. + /// + /// `origin`: The origin of the call stack from which is responsible for putting down a deposit. + /// `limit`: The limit with which the meter was constructed. + /// `min_leftover`: How much `free_balance` in addition to the existential deposit (ed) should + /// be left inside the `origin` account. + /// + /// Returns the limit that should be used by the meter. If origin can't afford the `limit` + /// it returns `Err`. + fn check_limit( + origin: &T::AccountId, + limit: BalanceOf, + min_leftover: BalanceOf, + ) -> Result, DispatchError>; + /// This is called to inform the implementer that some balance should be charged due to + /// some interaction of the `origin` with a `contract`. + /// + /// The balance transfer can either flow from `origin` to `contract` or the other way + /// around depending on whether `amount` constitutes a `Charge` or a `Refund`. + /// It should be used in combination with `check_limit` to check that no more balance than this + /// limit is ever charged. + fn charge( + origin: &T::AccountId, + contract: &T::AccountId, + amount: &DepositOf, + state: &ContractState, + ) -> Result<(), DispatchError>; +} + +/// This [`Ext`] is used for actual on-chain execution when balance needs to be charged. +/// +/// It uses [`frame_support::traits::fungible::Mutate`] in order to do accomplish the reserves. +pub enum ReservingExt {} + +/// Used to implement a type state pattern for the meter. +/// +/// It is sealed and cannot be implemented outside of this module. +pub trait State: private::Sealed {} + +/// State parameter that constitutes a meter that is in its root state. +#[derive(Default, Debug)] +pub struct Root; + +/// State parameter that constitutes a meter that is in its nested state. +/// Its value indicates whether the nested meter has its own limit. +#[derive(DefaultNoBound, RuntimeDebugNoBound)] +pub enum Nested { + #[default] + DerivedLimit, + OwnLimit, +} + +impl State for Root {} +impl State for Nested {} + +/// A type that allows the metering of consumed or freed storage of a single contract call stack. +#[derive(DefaultNoBound, RuntimeDebugNoBound)] +pub struct RawMeter { + /// The limit of how much balance this meter is allowed to consume. + limit: BalanceOf, + /// The amount of balance that was used in this meter and all of its already absorbed children. + total_deposit: DepositOf, + /// The amount of storage changes that were recorded in this meter alone. + own_contribution: Contribution, + /// List of charges that should be applied at the end of a contract stack execution. + /// + /// We only have one charge per contract hence the size of this vector is + /// limited by the maximum call depth. + charges: Vec>, + /// We store the nested state to determine if it has a special limit for sub-call. + nested: S, + /// Type parameter only used in impls. + _phantom: PhantomData, +} + +/// This type is used to describe a storage change when charging from the meter. +#[derive(Default, RuntimeDebugNoBound)] +pub struct Diff { + /// How many bytes were added to storage. + pub bytes_added: u32, + /// How many bytes were removed from storage. + pub bytes_removed: u32, + /// How many storage items were added to storage. + pub items_added: u32, + /// How many storage items were removed from storage. + pub items_removed: u32, +} + +impl Diff { + /// Calculate how much of a charge or refund results from applying the diff and store it + /// in the passed `info` if any. + /// + /// # Note + /// + /// In case `None` is passed for `info` only charges are calculated. This is because refunds + /// are calculated pro rata of the existing storage within a contract and hence need extract + /// this information from the passed `info`. + pub fn update_contract(&self, info: Option<&mut ContractInfo>) -> DepositOf { + let per_byte = T::DepositPerByte::get(); + let per_item = T::DepositPerItem::get(); + let bytes_added = self.bytes_added.saturating_sub(self.bytes_removed); + let items_added = self.items_added.saturating_sub(self.items_removed); + let mut bytes_deposit = Deposit::Charge(per_byte.saturating_mul((bytes_added).into())); + let mut items_deposit = Deposit::Charge(per_item.saturating_mul((items_added).into())); + + // Without any contract info we can only calculate diffs which add storage + let info = if let Some(info) = info { + info + } else { + debug_assert_eq!(self.bytes_removed, 0); + debug_assert_eq!(self.items_removed, 0); + return bytes_deposit.saturating_add(&items_deposit) + }; + + // Refunds are calculated pro rata based on the accumulated storage within the contract + let bytes_removed = self.bytes_removed.saturating_sub(self.bytes_added); + let items_removed = self.items_removed.saturating_sub(self.items_added); + let ratio = FixedU128::checked_from_rational(bytes_removed, info.storage_bytes) + .unwrap_or_default() + .min(FixedU128::from_u32(1)); + bytes_deposit = bytes_deposit + .saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_byte_deposit))); + let ratio = FixedU128::checked_from_rational(items_removed, info.storage_items) + .unwrap_or_default() + .min(FixedU128::from_u32(1)); + items_deposit = items_deposit + .saturating_add(&Deposit::Refund(ratio.saturating_mul_int(info.storage_item_deposit))); + + // We need to update the contract info structure with the new deposits + info.storage_bytes = + info.storage_bytes.saturating_add(bytes_added).saturating_sub(bytes_removed); + info.storage_items = + info.storage_items.saturating_add(items_added).saturating_sub(items_removed); + match &bytes_deposit { + Deposit::Charge(amount) => + info.storage_byte_deposit = info.storage_byte_deposit.saturating_add(*amount), + Deposit::Refund(amount) => + info.storage_byte_deposit = info.storage_byte_deposit.saturating_sub(*amount), + } + match &items_deposit { + Deposit::Charge(amount) => + info.storage_item_deposit = info.storage_item_deposit.saturating_add(*amount), + Deposit::Refund(amount) => + info.storage_item_deposit = info.storage_item_deposit.saturating_sub(*amount), + } + + bytes_deposit.saturating_add(&items_deposit) + } +} + +impl Diff { + fn saturating_add(&self, rhs: &Self) -> Self { + Self { + bytes_added: self.bytes_added.saturating_add(rhs.bytes_added), + bytes_removed: self.bytes_removed.saturating_add(rhs.bytes_removed), + items_added: self.items_added.saturating_add(rhs.items_added), + items_removed: self.items_removed.saturating_add(rhs.items_removed), + } + } +} + +/// The state of a contract. +/// +/// In case of termination the beneficiary is indicated. +#[derive(RuntimeDebugNoBound, Clone, PartialEq, Eq)] +pub enum ContractState { + Alive, + Terminated { beneficiary: AccountIdOf }, +} + +/// Records information to charge or refund a plain account. +/// +/// All the charges are deferred to the end of a whole call stack. Reason is that by doing +/// this we can do all the refunds before doing any charge. This way a plain account can use +/// more deposit than it has balance as along as it is covered by a refund. This +/// essentially makes the order of storage changes irrelevant with regard to the deposit system. +/// The only exception is when a special (tougher) deposit limit is specified for a cross-contract +/// call. In that case the limit is enforced once the call is returned, rolling it back if +/// exhausted. +#[derive(RuntimeDebugNoBound, Clone)] +struct Charge { + contract: T::AccountId, + amount: DepositOf, + state: ContractState, +} + +/// Records the storage changes of a storage meter. +#[derive(RuntimeDebugNoBound)] +enum Contribution { + /// The contract the meter belongs to is alive and accumulates changes using a [`Diff`]. + Alive(Diff), + /// The meter was checked against its limit using [`RawMeter::enforce_limit`] at the end of + /// its execution. In this process the [`Diff`] was converted into a [`Deposit`]. + Checked(DepositOf), + /// The contract was terminated. In this process the [`Diff`] was converted into a [`Deposit`] + /// in order to calculate the refund. Upon termination the `reducible_balance` in the + /// contract's account is transferred to the [`beneficiary`]. + Terminated { deposit: DepositOf, beneficiary: AccountIdOf }, +} + +impl Contribution { + /// See [`Diff::update_contract`]. + fn update_contract(&self, info: Option<&mut ContractInfo>) -> DepositOf { + match self { + Self::Alive(diff) => diff.update_contract::(info), + Self::Terminated { deposit, beneficiary: _ } | Self::Checked(deposit) => + deposit.clone(), + } + } +} + +impl Default for Contribution { + fn default() -> Self { + Self::Alive(Default::default()) + } +} + +/// Functions that apply to all states. +impl RawMeter +where + T: Config, + E: Ext, + S: State + Default + Debug, +{ + /// Create a new child that has its `limit`. + /// Passing `0` as the limit is interpreted as to take whatever is remaining from its parent. + /// + /// This is called whenever a new subcall is initiated in order to track the storage + /// usage for this sub call separately. This is necessary because we want to exchange balance + /// with the current contract we are interacting with. + pub fn nested(&self, limit: BalanceOf) -> RawMeter { + debug_assert!(matches!(self.contract_state(), ContractState::Alive)); + // If a special limit is specified higher than it is available, + // we want to enforce the lesser limit to the nested meter, to fail in the sub-call. + let limit = self.available().min(limit); + if limit.is_zero() { + RawMeter { limit: self.available(), ..Default::default() } + } else { + RawMeter { limit, nested: Nested::OwnLimit, ..Default::default() } + } + } + + /// Absorb a child that was spawned to handle a sub call. + /// + /// This should be called whenever a sub call comes to its end and it is **not** reverted. + /// This does the actual balance transfer from/to `origin` and `contract` based on the + /// overall storage consumption of the call. It also updates the supplied contract info. + /// + /// In case a contract reverted the child meter should just be dropped in order to revert + /// any changes it recorded. + /// + /// # Parameters + /// + /// - `absorbed`: The child storage meter that should be absorbed. + /// - `origin`: The origin that spawned the original root meter. + /// - `contract`: The contract's account that this sub call belongs to. + /// - `info`: The info of the contract in question. `None` if the contract was terminated. + pub fn absorb( + &mut self, + absorbed: RawMeter, + contract: &T::AccountId, + info: Option<&mut ContractInfo>, + ) { + let own_deposit = absorbed.own_contribution.update_contract(info); + self.total_deposit = self + .total_deposit + .saturating_add(&absorbed.total_deposit) + .saturating_add(&own_deposit); + self.charges.extend_from_slice(&absorbed.charges); + if !own_deposit.is_zero() { + self.charges.push(Charge { + contract: contract.clone(), + amount: own_deposit, + state: absorbed.contract_state(), + }); + } + } + + /// The amount of balance that is still available from the original `limit`. + fn available(&self) -> BalanceOf { + self.total_deposit.available(&self.limit) + } + + /// Returns the state of the currently executed contract. + fn contract_state(&self) -> ContractState { + match &self.own_contribution { + Contribution::Terminated { deposit: _, beneficiary } => + ContractState::Terminated { beneficiary: beneficiary.clone() }, + _ => ContractState::Alive, + } + } +} + +/// Functions that only apply to the root state. +impl RawMeter +where + T: Config, + E: Ext, +{ + /// Create new storage meter for the specified `origin` and `limit`. + /// + /// This tries to [`Ext::check_limit`] on `origin` and fails if this is not possible. + pub fn new( + origin: &Origin, + limit: BalanceOf, + min_leftover: BalanceOf, + ) -> Result { + // Check the limit only if the origin is not root. + return match origin { + Origin::Root => Ok(Self { limit, ..Default::default() }), + Origin::Signed(o) => { + let limit = E::check_limit(o, limit, min_leftover)?; + Ok(Self { limit, ..Default::default() }) + }, + } + } + + /// The total amount of deposit that should change hands as result of the execution + /// that this meter was passed into. This will also perform all the charges accumulated + /// in the whole contract stack. + /// + /// This drops the root meter in order to make sure it is only called when the whole + /// execution did finish. + pub fn try_into_deposit(self, origin: &Origin) -> Result, DispatchError> { + // Only refund or charge deposit if the origin is not root. + let origin = match origin { + Origin::Root => return Ok(Deposit::Charge(Zero::zero())), + Origin::Signed(o) => o, + }; + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) { + E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; + } + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) { + E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; + } + Ok(self.total_deposit) + } +} + +/// Functions that only apply to the nested state. +impl RawMeter +where + T: Config, + E: Ext, +{ + /// Charges `diff` from the meter. + pub fn charge(&mut self, diff: &Diff) { + match &mut self.own_contribution { + Contribution::Alive(own) => *own = own.saturating_add(diff), + _ => panic!("Charge is never called after termination; qed"), + }; + } + + /// Adds a deposit charge. + /// + /// Use this method instead of [`Self::charge`] when the charge is not the result of a storage + /// change. This is the case when a `delegate_dependency` is added or removed, or when the + /// `code_hash` is updated. [`Self::charge`] cannot be used here because we keep track of the + /// deposit charge separately from the storage charge. + pub fn charge_deposit(&mut self, contract: T::AccountId, amount: DepositOf) { + self.total_deposit = self.total_deposit.saturating_add(&amount); + self.charges.push(Charge { contract, amount, state: ContractState::Alive }); + } + + /// Charges from `origin` a storage deposit for contract instantiation. + /// + /// This immediately transfers the balance in order to create the account. + pub fn charge_instantiate( + &mut self, + origin: &T::AccountId, + contract: &T::AccountId, + contract_info: &mut ContractInfo, + code_info: &CodeInfo, + ) -> Result<(), DispatchError> { + debug_assert!(matches!(self.contract_state(), ContractState::Alive)); + + // We need to make sure that the contract's account exists. + let ed = Pallet::::min_balance(); + self.total_deposit = Deposit::Charge(ed); + T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?; + + // A consumer is added at account creation and removed it on termination, otherwise the + // runtime could remove the account. As long as a contract exists its account must exist. + // With the consumer, a correct runtime cannot remove the account. + System::::inc_consumers(contract)?; + + let deposit = contract_info.update_base_deposit(&code_info); + let deposit = Deposit::Charge(deposit); + + self.charge_deposit(contract.clone(), deposit); + Ok(()) + } + + /// Call to tell the meter that the currently executing contract was terminated. + /// + /// This will manipulate the meter so that all storage deposit accumulated in + /// `contract_info` will be refunded to the `origin` of the meter. And the free + /// (`reducible_balance`) will be sent to the `beneficiary`. + pub fn terminate(&mut self, info: &ContractInfo, beneficiary: T::AccountId) { + debug_assert!(matches!(self.contract_state(), ContractState::Alive)); + self.own_contribution = Contribution::Terminated { + deposit: Deposit::Refund(info.total_deposit()), + beneficiary, + }; + } + + /// [`Self::charge`] does not enforce the storage limit since we want to do this check as late + /// as possible to allow later refunds to offset earlier charges. + /// + /// # Note + /// + /// We normally need to call this **once** for every call stack and not for every cross contract + /// call. However, if a dedicated limit is specified for a sub-call, this needs to be called + /// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is + /// used. + pub fn enforce_limit( + &mut self, + info: Option<&mut ContractInfo>, + ) -> Result<(), DispatchError> { + let deposit = self.own_contribution.update_contract(info); + let total_deposit = self.total_deposit.saturating_add(&deposit); + // We don't want to override a `Terminated` with a `Checked`. + if matches!(self.contract_state(), ContractState::Alive) { + self.own_contribution = Contribution::Checked(deposit); + } + if let Deposit::Charge(amount) = total_deposit { + if amount > self.limit { + return Err(>::StorageDepositLimitExhausted.into()) + } + } + Ok(()) + } + + /// This is a wrapper around [`Self::enforce_limit`] to use on the exit from a sub-call to + /// enforce its special limit if needed. + pub fn enforce_subcall_limit( + &mut self, + info: Option<&mut ContractInfo>, + ) -> Result<(), DispatchError> { + match self.nested { + Nested::OwnLimit => self.enforce_limit(info), + Nested::DerivedLimit => Ok(()), + } + } +} + +impl Ext for ReservingExt { + fn check_limit( + origin: &T::AccountId, + limit: BalanceOf, + min_leftover: BalanceOf, + ) -> Result, DispatchError> { + let limit = T::Currency::reducible_balance(origin, Preservation::Preserve, Polite) + .saturating_sub(min_leftover) + .min(limit); + Ok(limit) + } + + fn charge( + origin: &T::AccountId, + contract: &T::AccountId, + amount: &DepositOf, + state: &ContractState, + ) -> Result<(), DispatchError> { + match amount { + Deposit::Charge(amount) | Deposit::Refund(amount) if amount.is_zero() => return Ok(()), + Deposit::Charge(amount) => { + // This could fail if the `origin` does not have enough liquidity. Ideally, though, + // this should have been checked before with `check_limit`. + T::Currency::transfer_and_hold( + &HoldReason::StorageDepositReserve.into(), + origin, + contract, + *amount, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, + )?; + + Pallet::::deposit_event(Event::StorageDepositTransferredAndHeld { + from: origin.clone(), + to: contract.clone(), + amount: *amount, + }); + }, + Deposit::Refund(amount) => { + let transferred = T::Currency::transfer_on_hold( + &HoldReason::StorageDepositReserve.into(), + contract, + origin, + *amount, + Precision::BestEffort, + Restriction::Free, + Fortitude::Polite, + )?; + + Pallet::::deposit_event(Event::StorageDepositTransferredAndReleased { + from: contract.clone(), + to: origin.clone(), + amount: transferred, + }); + + if transferred < *amount { + // This should never happen, if it does it means that there is a bug in the + // runtime logic. In the rare case this happens we try to refund as much as we + // can, thus the `Precision::BestEffort`. + log::error!( + target: LOG_TARGET, + "Failed to repatriate full storage deposit {:?} from contract {:?} to origin {:?}. Transferred {:?}.", + amount, contract, origin, transferred, + ); + } + }, + } + if let ContractState::::Terminated { beneficiary } = state { + System::::dec_consumers(&contract); + // Whatever is left in the contract is sent to the termination beneficiary. + T::Currency::transfer( + &contract, + &beneficiary, + T::Currency::reducible_balance(&contract, Preservation::Expendable, Polite), + Preservation::Expendable, + )?; + } + Ok(()) + } +} + +mod private { + pub trait Sealed {} + impl Sealed for super::Root {} + impl Sealed for super::Nested {} +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{exec::AccountIdOf, test_utils::*, tests::Test}; + use frame_support::parameter_types; + use pretty_assertions::assert_eq; + + type TestMeter = RawMeter; + + parameter_types! { + static TestExtTestValue: TestExt = Default::default(); + } + + #[derive(Debug, PartialEq, Eq, Clone)] + struct LimitCheck { + origin: AccountIdOf, + limit: BalanceOf, + min_leftover: BalanceOf, + } + + #[derive(Debug, PartialEq, Eq, Clone)] + struct Charge { + origin: AccountIdOf, + contract: AccountIdOf, + amount: DepositOf, + state: ContractState, + } + + #[derive(Default, Debug, PartialEq, Eq, Clone)] + pub struct TestExt { + limit_checks: Vec, + charges: Vec, + } + + impl TestExt { + fn clear(&mut self) { + self.limit_checks.clear(); + self.charges.clear(); + } + } + + impl Ext for TestExt { + fn check_limit( + origin: &AccountIdOf, + limit: BalanceOf, + min_leftover: BalanceOf, + ) -> Result, DispatchError> { + TestExtTestValue::mutate(|ext| { + ext.limit_checks + .push(LimitCheck { origin: origin.clone(), limit, min_leftover }) + }); + Ok(limit) + } + + fn charge( + origin: &AccountIdOf, + contract: &AccountIdOf, + amount: &DepositOf, + state: &ContractState, + ) -> Result<(), DispatchError> { + TestExtTestValue::mutate(|ext| { + ext.charges.push(Charge { + origin: origin.clone(), + contract: contract.clone(), + amount: amount.clone(), + state: state.clone(), + }) + }); + Ok(()) + } + } + + fn clear_ext() { + TestExtTestValue::mutate(|ext| ext.clear()) + } + + struct ChargingTestCase { + origin: Origin, + deposit: DepositOf, + expected: TestExt, + } + + #[derive(Default)] + struct StorageInfo { + bytes: u32, + items: u32, + bytes_deposit: BalanceOf, + items_deposit: BalanceOf, + } + + fn new_info(info: StorageInfo) -> ContractInfo { + ContractInfo:: { + trie_id: Default::default(), + code_hash: Default::default(), + storage_bytes: info.bytes, + storage_items: info.items, + storage_byte_deposit: info.bytes_deposit, + storage_item_deposit: info.items_deposit, + storage_base_deposit: Default::default(), + delegate_dependencies: Default::default(), + } + } + + #[test] + fn new_reserves_balance_works() { + clear_ext(); + + TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + + assert_eq!( + TestExtTestValue::get(), + TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], + ..Default::default() + } + ) + } + + #[test] + fn empty_charge_works() { + clear_ext(); + + let mut meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + + // an empty charge does not create a `Charge` entry + let mut nested0 = meter.nested(BalanceOf::::zero()); + nested0.charge(&Default::default()); + meter.absorb(nested0, &BOB, None); + + assert_eq!( + TestExtTestValue::get(), + TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], + ..Default::default() + } + ) + } + + #[test] + fn charging_works() { + let test_cases = vec![ + ChargingTestCase { + origin: Origin::::from_account_id(ALICE), + deposit: Deposit::Refund(28), + expected: TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }], + charges: vec![ + Charge { + origin: ALICE, + contract: CHARLIE, + amount: Deposit::Refund(10), + state: ContractState::Alive, + }, + Charge { + origin: ALICE, + contract: CHARLIE, + amount: Deposit::Refund(20), + state: ContractState::Alive, + }, + Charge { + origin: ALICE, + contract: BOB, + amount: Deposit::Charge(2), + state: ContractState::Alive, + }, + ], + }, + }, + ChargingTestCase { + origin: Origin::::Root, + deposit: Deposit::Charge(0), + expected: TestExt { limit_checks: vec![], charges: vec![] }, + }, + ]; + + for test_case in test_cases { + clear_ext(); + + let mut meter = TestMeter::new(&test_case.origin, 100, 0).unwrap(); + assert_eq!(meter.available(), 100); + + let mut nested0_info = new_info(StorageInfo { + bytes: 100, + items: 5, + bytes_deposit: 100, + items_deposit: 10, + }); + let mut nested0 = meter.nested(BalanceOf::::zero()); + nested0.charge(&Diff { + bytes_added: 108, + bytes_removed: 5, + items_added: 1, + items_removed: 2, + }); + nested0.charge(&Diff { bytes_removed: 99, ..Default::default() }); + + let mut nested1_info = new_info(StorageInfo { + bytes: 100, + items: 10, + bytes_deposit: 100, + items_deposit: 20, + }); + let mut nested1 = nested0.nested(BalanceOf::::zero()); + nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info)); + + let mut nested2_info = new_info(StorageInfo { + bytes: 100, + items: 7, + bytes_deposit: 100, + items_deposit: 20, + }); + let mut nested2 = nested0.nested(BalanceOf::::zero()); + nested2.charge(&Diff { items_removed: 7, ..Default::default() }); + nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info)); + + nested0.enforce_limit(Some(&mut nested0_info)).unwrap(); + meter.absorb(nested0, &BOB, Some(&mut nested0_info)); + + assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit); + + assert_eq!(nested0_info.extra_deposit(), 112); + assert_eq!(nested1_info.extra_deposit(), 110); + assert_eq!(nested2_info.extra_deposit(), 100); + + assert_eq!(TestExtTestValue::get(), test_case.expected) + } + } + + #[test] + fn termination_works() { + let test_cases = vec![ + ChargingTestCase { + origin: Origin::::from_account_id(ALICE), + deposit: Deposit::Refund(108), + expected: TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], + charges: vec![ + Charge { + origin: ALICE, + contract: CHARLIE, + amount: Deposit::Refund(120), + state: ContractState::Terminated { beneficiary: CHARLIE }, + }, + Charge { + origin: ALICE, + contract: BOB, + amount: Deposit::Charge(12), + state: ContractState::Alive, + }, + ], + }, + }, + ChargingTestCase { + origin: Origin::::Root, + deposit: Deposit::Charge(0), + expected: TestExt { limit_checks: vec![], charges: vec![] }, + }, + ]; + + for test_case in test_cases { + clear_ext(); + + let mut meter = TestMeter::new(&test_case.origin, 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + + let mut nested0 = meter.nested(BalanceOf::::zero()); + nested0.charge(&Diff { + bytes_added: 5, + bytes_removed: 1, + items_added: 3, + items_removed: 1, + }); + nested0.charge(&Diff { items_added: 2, ..Default::default() }); + + let mut nested1_info = new_info(StorageInfo { + bytes: 100, + items: 10, + bytes_deposit: 100, + items_deposit: 20, + }); + let mut nested1 = nested0.nested(BalanceOf::::zero()); + nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); + nested1.terminate(&nested1_info, CHARLIE); + nested0.enforce_limit(Some(&mut nested1_info)).unwrap(); + nested0.absorb(nested1, &CHARLIE, None); + + meter.absorb(nested0, &BOB, None); + assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit); + assert_eq!(TestExtTestValue::get(), test_case.expected) + } + } +} diff --git a/substrate/frame/revive/src/test_utils.rs b/substrate/frame/revive/src/test_utils.rs new file mode 100644 index 000000000000..2bfe754f86cd --- /dev/null +++ b/substrate/frame/revive/src/test_utils.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Shared utilities for testing contracts. +//! This is not part of the tests module because it is made public for other crates to use. + +#![cfg(feature = "std")] + +pub mod builder; + +use crate::{BalanceOf, Config}; +use frame_support::weights::Weight; +pub use sp_runtime::AccountId32; + +pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); +pub const BOB: AccountId32 = AccountId32::new([2u8; 32]); +pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]); +pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]); + +pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); + +pub fn deposit_limit() -> BalanceOf { + 10_000_000u32.into() +} diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs new file mode 100644 index 000000000000..bf8cbcd5a01f --- /dev/null +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -0,0 +1,218 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{deposit_limit, GAS_LIMIT}; +use crate::{ + AccountIdLookupOf, AccountIdOf, BalanceOf, Code, CodeHash, CollectEvents, Config, + ContractExecResult, ContractInstantiateResult, DebugInfo, EventRecordOf, ExecReturnValue, + InstantiateReturnValue, OriginFor, Pallet, Weight, +}; +use codec::{Encode, HasCompact}; +use core::fmt::Debug; +use frame_support::pallet_prelude::DispatchResultWithPostInfo; +use paste::paste; +use scale_info::TypeInfo; + +/// Helper macro to generate a builder for contract API calls. +macro_rules! builder { + // Entry point to generate a builder for the given method. + ( + $method:ident($($field:ident: $type:ty,)*) -> $result:ty; + $($extra:item)* + ) => { + paste!{ + builder!([< $method:camel Builder >], $method($($field: $type,)* ) -> $result; $($extra)*); + } + }; + // Generate the builder struct and its methods. + ( + $name:ident, + $method:ident($($field:ident: $type:ty,)*) -> $result:ty; + $($extra:item)* + ) => { + #[doc = concat!("A builder to construct a ", stringify!($method), " call")] + pub struct $name { + $($field: $type,)* + } + + #[allow(dead_code)] + impl $name + where + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + { + $( + #[doc = concat!("Set the ", stringify!($field))] + pub fn $field(mut self, value: $type) -> Self { + self.$field = value; + self + } + )* + + #[doc = concat!("Build the ", stringify!($method), " call")] + pub fn build(self) -> $result { + Pallet::::$method( + $(self.$field,)* + ) + } + + $($extra)* + } + } +} + +builder!( + instantiate_with_code( + origin: OriginFor, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + code: Vec, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo; + + /// Create an [`InstantiateWithCodeBuilder`] with default values. + pub fn instantiate_with_code(origin: OriginFor, code: Vec) -> Self { + Self { + origin, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: deposit_limit::(), + code, + data: vec![], + salt: vec![], + } + } +); + +builder!( + instantiate( + origin: OriginFor, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + code_hash: CodeHash, + data: Vec, + salt: Vec, + ) -> DispatchResultWithPostInfo; + + /// Create an [`InstantiateBuilder`] with default values. + pub fn instantiate(origin: OriginFor, code_hash: CodeHash) -> Self { + Self { + origin, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: deposit_limit::(), + code_hash, + data: vec![], + salt: vec![], + } + } +); + +builder!( + bare_instantiate( + origin: OriginFor, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + code: Code>, + data: Vec, + salt: Vec, + debug: DebugInfo, + collect_events: CollectEvents, + ) -> ContractInstantiateResult, BalanceOf, EventRecordOf>; + + /// Build the instantiate call and unwrap the result. + pub fn build_and_unwrap_result(self) -> InstantiateReturnValue> { + self.build().result.unwrap() + } + + /// Build the instantiate call and unwrap the account id. + pub fn build_and_unwrap_account_id(self) -> AccountIdOf { + self.build().result.unwrap().account_id + } + + pub fn bare_instantiate(origin: OriginFor, code: Code>) -> Self { + Self { + origin, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: deposit_limit::(), + code, + data: vec![], + salt: vec![], + debug: DebugInfo::UnsafeDebug, + collect_events: CollectEvents::Skip, + } + } +); + +builder!( + call( + origin: OriginFor, + dest: AccountIdLookupOf, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + data: Vec, + ) -> DispatchResultWithPostInfo; + + /// Create a [`CallBuilder`] with default values. + pub fn call(origin: OriginFor, dest: AccountIdLookupOf) -> Self { + CallBuilder { + origin, + dest, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: deposit_limit::(), + data: vec![], + } + } +); + +builder!( + bare_call( + origin: OriginFor, + dest: AccountIdOf, + value: BalanceOf, + gas_limit: Weight, + storage_deposit_limit: BalanceOf, + data: Vec, + debug: DebugInfo, + collect_events: CollectEvents, + ) -> ContractExecResult, EventRecordOf>; + + /// Build the call and unwrap the result. + pub fn build_and_unwrap_result(self) -> ExecReturnValue { + self.build().result.unwrap() + } + + /// Create a [`BareCallBuilder`] with default values. + pub fn bare_call(origin: OriginFor, dest: AccountIdOf) -> Self { + Self { + origin, + dest, + value: 0u32.into(), + gas_limit: GAS_LIMIT, + storage_deposit_limit: deposit_limit::(), + data: vec![], + debug: DebugInfo::UnsafeDebug, + collect_events: CollectEvents::Skip, + } + } +); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs new file mode 100644 index 000000000000..52ee7b310542 --- /dev/null +++ b/substrate/frame/revive/src/tests.rs @@ -0,0 +1,4166 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "riscv"), allow(dead_code, unused_imports, unused_macros))] + +mod pallet_dummy; +mod test_debug; + +use self::{ + test_debug::TestDebug, + test_utils::{ensure_stored, expected_deposit}, +}; +use crate::{ + self as pallet_revive, + chain_extension::{ + ChainExtension, Environment, Ext, RegisteredChainExtension, Result as ExtensionResult, + RetVal, ReturnFlags, + }, + exec::Key, + limits, + migration::codegen::LATEST_MIGRATION_VERSION, + primitives::CodeUploadReturnValue, + storage::DeletionQueueManager, + test_utils::*, + tests::test_utils::{get_contract, get_contract_checked}, + wasm::Memory, + weights::WeightInfo, + BalanceOf, Code, CodeHash, CodeInfoOf, CollectEvents, Config, ContractInfo, ContractInfoOf, + DebugInfo, DefaultAddressGenerator, DeletionQueueCounter, Error, HoldReason, + MigrationInProgress, Origin, Pallet, PristineCode, +}; +use assert_matches::assert_matches; +use codec::{Decode, Encode}; +use frame_support::{ + assert_err, assert_err_ignore_postinfo, assert_err_with_weight, assert_noop, assert_ok, + derive_impl, + dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo}, + pallet_prelude::EnsureOrigin, + parameter_types, + storage::child, + traits::{ + fungible::{BalancedHold, Inspect, Mutate, MutateHold}, + tokens::Preservation, + ConstU32, ConstU64, Contains, OnIdle, OnInitialize, StorageVersion, + }, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight, WeightMeter}, +}; +use frame_system::{EventRecord, Phase}; +use pallet_revive_fixtures::{bench::dummy_unique, compile_module}; +use pallet_revive_uapi::ReturnErrorCode as RuntimeReturnCode; +use sp_core::ByteArray; +use sp_io::hashing::blake2_256; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; +use sp_runtime::{ + testing::H256, + traits::{BlakeTwo256, Convert, Hash, IdentityLookup}, + AccountId32, BuildStorage, DispatchError, Perbill, TokenError, +}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Timestamp: pallet_timestamp, + Utility: pallet_utility, + Contracts: pallet_revive, + Proxy: pallet_proxy, + Dummy: pallet_dummy + } +); + +macro_rules! assert_return_code { + ( $x:expr , $y:expr $(,)? ) => {{ + assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); + }}; +} + +macro_rules! assert_refcount { + ( $code_hash:expr , $should:expr $(,)? ) => {{ + let is = crate::CodeInfoOf::::get($code_hash).map(|m| m.refcount()).unwrap(); + assert_eq!(is, $should); + }}; +} + +pub mod test_utils { + use super::{Contracts, DepositPerByte, DepositPerItem, Test}; + use crate::{ + exec::AccountIdOf, BalanceOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, + ContractInfoOf, PristineCode, + }; + use codec::{Encode, MaxEncodedLen}; + use frame_support::traits::fungible::{InspectHold, Mutate}; + + pub fn place_contract(address: &AccountIdOf, code_hash: CodeHash) { + set_balance(address, Contracts::min_balance() * 10); + >::insert(code_hash, CodeInfo::new(address.clone())); + let contract = >::new(&address, 0, code_hash).unwrap(); + >::insert(address, contract); + } + pub fn set_balance(who: &AccountIdOf, amount: u64) { + let _ = ::Currency::set_balance(who, amount); + } + pub fn get_balance(who: &AccountIdOf) -> u64 { + ::Currency::free_balance(who) + } + pub fn get_balance_on_hold( + reason: &::RuntimeHoldReason, + who: &AccountIdOf, + ) -> u64 { + ::Currency::balance_on_hold(reason.into(), who) + } + pub fn get_contract(addr: &AccountIdOf) -> ContractInfo { + get_contract_checked(addr).unwrap() + } + pub fn get_contract_checked(addr: &AccountIdOf) -> Option> { + ContractInfoOf::::get(addr) + } + pub fn get_code_deposit(code_hash: &CodeHash) -> BalanceOf { + crate::CodeInfoOf::::get(code_hash).unwrap().deposit() + } + pub fn contract_info_storage_deposit( + addr: &::AccountId, + ) -> BalanceOf { + let contract_info = self::get_contract(&addr); + let info_size = contract_info.encoded_size() as u64; + DepositPerByte::get() + .saturating_mul(info_size) + .saturating_add(DepositPerItem::get()) + } + pub fn expected_deposit(code_len: usize) -> u64 { + // For code_info, the deposit for max_encoded_len is taken. + let code_info_len = CodeInfo::::max_encoded_len() as u64; + // Calculate deposit to be reserved. + // We add 2 storage items: one for code, other for code_info + DepositPerByte::get().saturating_mul(code_len as u64 + code_info_len) + + DepositPerItem::get().saturating_mul(2) + } + pub fn ensure_stored(code_hash: CodeHash) -> usize { + // Assert that code_info is stored + assert!(CodeInfoOf::::contains_key(&code_hash)); + // Assert that contract code is stored, and get its size. + PristineCode::::try_get(&code_hash).unwrap().len() + } +} + +mod builder { + use super::Test; + use crate::{ + test_utils::{builder::*, AccountId32, ALICE}, + tests::RuntimeOrigin, + AccountIdLookupOf, Code, CodeHash, + }; + + pub fn bare_instantiate(code: Code>) -> BareInstantiateBuilder { + BareInstantiateBuilder::::bare_instantiate(RuntimeOrigin::signed(ALICE), code) + } + + pub fn bare_call(dest: AccountId32) -> BareCallBuilder { + BareCallBuilder::::bare_call(RuntimeOrigin::signed(ALICE), dest) + } + + pub fn instantiate_with_code(code: Vec) -> InstantiateWithCodeBuilder { + InstantiateWithCodeBuilder::::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + code, + ) + } + + pub fn instantiate(code_hash: CodeHash) -> InstantiateBuilder { + InstantiateBuilder::::instantiate(RuntimeOrigin::signed(ALICE), code_hash) + } + + pub fn call(dest: AccountIdLookupOf) -> CallBuilder { + CallBuilder::::call(RuntimeOrigin::signed(ALICE), dest) + } +} + +impl Test { + pub fn set_unstable_interface(unstable_interface: bool) { + UNSTABLE_INTERFACE.with(|v| *v.borrow_mut() = unstable_interface); + } +} + +parameter_types! { + static TestExtensionTestValue: TestExtension = Default::default(); +} + +#[derive(Clone)] +pub struct TestExtension { + enabled: bool, + last_seen_buffer: Vec, + last_seen_input_len: u32, +} + +#[derive(Default)] +pub struct RevertingExtension; + +#[derive(Default)] +pub struct DisabledExtension; + +#[derive(Default)] +pub struct TempStorageExtension { + storage: u32, +} + +impl TestExtension { + fn disable() { + TestExtensionTestValue::mutate(|e| e.enabled = false) + } + + fn last_seen_buffer() -> Vec { + TestExtensionTestValue::get().last_seen_buffer.clone() + } + + fn last_seen_input_len() -> u32 { + TestExtensionTestValue::get().last_seen_input_len + } +} + +impl Default for TestExtension { + fn default() -> Self { + Self { enabled: true, last_seen_buffer: vec![], last_seen_input_len: 0 } + } +} + +impl ChainExtension for TestExtension { + fn call(&mut self, mut env: Environment) -> ExtensionResult + where + E: Ext, + M: ?Sized + Memory, + { + let func_id = env.func_id(); + let id = env.ext_id() as u32 | func_id as u32; + match func_id { + 0 => { + let input = env.read(8)?; + env.write(&input, false, None)?; + TestExtensionTestValue::mutate(|e| e.last_seen_buffer = input); + Ok(RetVal::Converging(id)) + }, + 1 => { + TestExtensionTestValue::mutate(|e| e.last_seen_input_len = env.in_len()); + Ok(RetVal::Converging(id)) + }, + 2 => { + let mut enc = &env.read(9)?[4..8]; + let weight = Weight::from_parts( + u32::decode(&mut enc).map_err(|_| Error::::ContractTrapped)?.into(), + 0, + ); + env.charge_weight(weight)?; + Ok(RetVal::Converging(id)) + }, + 3 => Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![42, 99] }), + _ => { + panic!("Passed unknown id to test chain extension: {}", func_id); + }, + } + } + + fn enabled() -> bool { + TestExtensionTestValue::get().enabled + } +} + +impl RegisteredChainExtension for TestExtension { + const ID: u16 = 0; +} + +impl ChainExtension for RevertingExtension { + fn call(&mut self, _env: Environment) -> ExtensionResult + where + E: Ext, + M: ?Sized + Memory, + { + Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![0x4B, 0x1D] }) + } + + fn enabled() -> bool { + TestExtensionTestValue::get().enabled + } +} + +impl RegisteredChainExtension for RevertingExtension { + const ID: u16 = 1; +} + +impl ChainExtension for DisabledExtension { + fn call(&mut self, _env: Environment) -> ExtensionResult + where + E: Ext, + M: ?Sized + Memory, + { + panic!("Disabled chain extensions are never called") + } + + fn enabled() -> bool { + false + } +} + +impl RegisteredChainExtension for DisabledExtension { + const ID: u16 = 2; +} + +impl ChainExtension for TempStorageExtension { + fn call(&mut self, env: Environment) -> ExtensionResult + where + E: Ext, + M: ?Sized + Memory, + { + let func_id = env.func_id(); + match func_id { + 0 => self.storage = 42, + 1 => assert_eq!(self.storage, 42, "Storage is preserved inside the same call."), + 2 => { + assert_eq!(self.storage, 0, "Storage is different for different calls."); + self.storage = 99; + }, + 3 => assert_eq!(self.storage, 99, "Storage is preserved inside the same call."), + _ => { + panic!("Passed unknown id to test chain extension: {}", func_id); + }, + } + Ok(RetVal::Converging(0)) + } + + fn enabled() -> bool { + TestExtensionTestValue::get().enabled + } +} + +impl RegisteredChainExtension for TempStorageExtension { + const ID: u16 = 3; +} + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max( + Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + ); + pub static ExistentialDeposit: u64 = 1; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type ExistentialDeposit = ExistentialDeposit; + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +#[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)] +impl pallet_timestamp::Config for Test {} + +impl pallet_utility::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); +} + +impl pallet_proxy::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = (); + type ProxyDepositBase = ConstU64<1>; + type ProxyDepositFactor = ConstU64<1>; + type MaxProxies = ConstU32<32>; + type WeightInfo = (); + type MaxPending = ConstU32<32>; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = ConstU64<1>; + type AnnouncementDepositFactor = ConstU64<1>; +} + +impl pallet_dummy::Config for Test {} + +parameter_types! { + pub static DepositPerByte: BalanceOf = 1; + pub const DepositPerItem: BalanceOf = 2; + pub static CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); +} + +impl Convert> for Test { + fn convert(w: Weight) -> BalanceOf { + w.ref_time() + } +} + +/// A filter whose filter function can be swapped at runtime. +pub struct TestFilter; + +#[derive(Clone)] +pub struct Filters { + filter: fn(&RuntimeCall) -> bool, +} + +impl Default for Filters { + fn default() -> Self { + Filters { filter: (|_| true) } + } +} + +parameter_types! { + static CallFilter: Filters = Default::default(); +} + +impl TestFilter { + pub fn set_filter(filter: fn(&RuntimeCall) -> bool) { + CallFilter::mutate(|fltr| fltr.filter = filter); + } +} + +impl Contains for TestFilter { + fn contains(call: &RuntimeCall) -> bool { + (CallFilter::get().filter)(call) + } +} + +parameter_types! { + pub static UploadAccount: Option<::AccountId> = None; + pub static InstantiateAccount: Option<::AccountId> = None; +} + +pub struct EnsureAccount(core::marker::PhantomData<(T, A)>); +impl>>> + EnsureOrigin<::RuntimeOrigin> for EnsureAccount +where + ::AccountId: From, +{ + type Success = T::AccountId; + + fn try_origin(o: T::RuntimeOrigin) -> Result { + let who = as EnsureOrigin<_>>::try_origin(o.clone())?; + if matches!(A::get(), Some(a) if who != a) { + return Err(o); + } + + Ok(who) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Err(()) + } +} +parameter_types! { + pub static UnstableInterface: bool = true; +} + +#[derive_impl(crate::config_preludes::TestDefaultConfig)] +impl Config for Test { + type Time = Timestamp; + type Currency = Balances; + type CallFilter = TestFilter; + type ChainExtension = + (TestExtension, DisabledExtension, RevertingExtension, TempStorageExtension); + type DepositPerByte = DepositPerByte; + type DepositPerItem = DepositPerItem; + type AddressGenerator = DefaultAddressGenerator; + type UnsafeUnstableInterface = UnstableInterface; + type UploadOrigin = EnsureAccount; + type InstantiateOrigin = EnsureAccount; + type Migrations = crate::migration::codegen::BenchMigrations; + type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type Debug = TestDebug; +} + +pub struct ExtBuilder { + existential_deposit: u64, + storage_version: Option, + code_hashes: Vec>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + existential_deposit: ExistentialDeposit::get(), + storage_version: None, + code_hashes: vec![], + } + } +} + +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { + self.existential_deposit = existential_deposit; + self + } + pub fn with_code_hashes(mut self, code_hashes: Vec>) -> Self { + self.code_hashes = code_hashes; + self + } + pub fn set_associated_consts(&self) { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); + } + pub fn set_storage_version(mut self, version: u16) -> Self { + self.storage_version = Some(StorageVersion::new(version)); + self + } + pub fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + self.set_associated_consts(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { balances: vec![] } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); + ext.execute_with(|| { + use frame_support::traits::OnGenesis; + + Pallet::::on_genesis(); + if let Some(storage_version) = self.storage_version { + storage_version.put::>(); + } + System::set_block_number(1) + }); + ext.execute_with(|| { + for code_hash in self.code_hashes { + CodeInfoOf::::insert(code_hash, crate::CodeInfo::new(ALICE)); + } + }); + ext + } +} + +fn initialize_block(number: u64) { + System::reset_events(); + System::initialize(&number, &[0u8; 32].into(), &Default::default()); +} + +struct ExtensionInput<'a> { + extension_id: u16, + func_id: u16, + extra: &'a [u8], +} + +impl<'a> ExtensionInput<'a> { + fn to_vec(&self) -> Vec { + ((self.extension_id as u32) << 16 | (self.func_id as u32)) + .to_le_bytes() + .iter() + .chain(self.extra) + .cloned() + .collect() + } +} + +impl<'a> From> for Vec { + fn from(input: ExtensionInput) -> Vec { + input.to_vec() + } +} + +impl Default for Origin { + fn default() -> Self { + Self::Signed(ALICE) + } +} + +/// We can only run the tests if we have a riscv toolchain installed +#[cfg(feature = "riscv")] +mod run_tests { + use super::*; + use pretty_assertions::{assert_eq, assert_ne}; + + // Perform a call to a plain account. + // The actual transfer fails because we can only call contracts. + // Then we check that at least the base costs where charged (no runtime gas costs.) + #[test] + fn calling_plain_account_fails() { + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + let base_cost = <::WeightInfo as WeightInfo>::call(); + + assert_eq!( + builder::call(BOB).build(), + Err(DispatchErrorWithPostInfo { + error: Error::::ContractNotFound.into(), + post_info: PostDispatchInfo { + actual_weight: Some(base_cost), + pays_fee: Default::default(), + }, + }) + ); + }); + } + + #[test] + fn migration_on_idle_hooks_works() { + // Defines expectations of how many migration steps can be done given the weight limit. + let tests = [ + (Weight::zero(), LATEST_MIGRATION_VERSION - 2), + (::WeightInfo::migrate() + 1.into(), LATEST_MIGRATION_VERSION - 1), + (Weight::MAX, LATEST_MIGRATION_VERSION), + ]; + + for (weight, expected_version) in tests { + ExtBuilder::default() + .set_storage_version(LATEST_MIGRATION_VERSION - 2) + .build() + .execute_with(|| { + MigrationInProgress::::set(Some(Default::default())); + Contracts::on_idle(System::block_number(), weight); + assert_eq!(StorageVersion::get::>(), expected_version); + }); + } + } + + #[test] + fn migration_in_progress_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + MigrationInProgress::::set(Some(Default::default())); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + vec![], + deposit_limit::(), + ), + Error::::MigrationInProgress, + ); + assert_err!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + Error::::MigrationInProgress, + ); + assert_err!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB.clone(), code_hash), + Error::::MigrationInProgress, + ); + assert_err_ignore_postinfo!( + builder::call(BOB).build(), + Error::::MigrationInProgress + ); + assert_err_ignore_postinfo!( + builder::instantiate_with_code(wasm).value(100_000).build(), + Error::::MigrationInProgress, + ); + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).value(100_000).build(), + Error::::MigrationInProgress, + ); + }); + } + + #[test] + fn instantiate_and_call_and_deposit_event() { + let (wasm, code_hash) = compile_module::("event_and_return_on_deploy").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let value = 100; + + // We determine the storage deposit limit after uploading because it depends on ALICEs + // free balance which is changed by uploading a module. + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm, + deposit_limit::(), + )); + + // Drop previous events + initialize_block(2); + + // Check at the end to get hash on error easily + let addr = builder::bare_instantiate(Code::Existing(code_hash)) + .value(value) + .build_and_unwrap_account_id(); + assert!(ContractInfoOf::::contains_key(&addr)); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: value, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::ContractEmitted { + contract: addr.clone(), + data: vec![1, 2, 3, 4] + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE, + contract: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: addr.clone(), + amount: test_utils::contract_info_storage_deposit(&addr), + } + ), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn deposit_event_max_value_limit() { + let (wasm, _code_hash) = compile_module::("event_size").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_account_id(); + + // Call contract with allowed storage value. + assert_ok!(builder::call(addr.clone()) + .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer, + .data(limits::PAYLOAD_BYTES.encode()) + .build()); + + // Call contract with too large a storage value. + assert_err_ignore_postinfo!( + builder::call(addr).data((limits::PAYLOAD_BYTES + 1).encode()).build(), + Error::::ValueTooLarge, + ); + }); + } + + // Fail out of fuel (ref_time weight) in the engine. + #[test] + fn run_out_of_fuel_engine() { + let (wasm, _code_hash) = compile_module::("run_out_of_gas").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100 * min_balance) + .build_and_unwrap_account_id(); + + // Call the contract with a fixed gas limit. It must run out of gas because it just + // loops forever. + assert_err_ignore_postinfo!( + builder::call(addr) + .gas_limit(Weight::from_parts(10_000_000_000, u64::MAX)) + .build(), + Error::::OutOfGas, + ); + }); + } + + // Fail out of fuel (ref_time weight) in the host. + #[test] + fn run_out_of_fuel_host() { + let (code, _hash) = compile_module::("chain_extension").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let gas_limit = Weight::from_parts(u32::MAX as u64, GAS_LIMIT.proof_size()); + + // Use chain extension to charge more ref_time than it is available. + let result = builder::bare_call(addr.clone()) + .gas_limit(gas_limit) + .data( + ExtensionInput { extension_id: 0, func_id: 2, extra: &u32::MAX.encode() } + .into(), + ) + .build() + .result; + assert_err!(result, >::OutOfGas); + }); + } + + #[test] + fn gas_syncs_work() { + let (code, _code_hash) = compile_module::("caller_is_origin_n").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_account_id(); + + let result = builder::bare_call(addr.clone()).data(0u32.encode()).build(); + assert_ok!(result.result); + let engine_consumed_noop = result.gas_consumed.ref_time(); + + let result = builder::bare_call(addr.clone()).data(1u32.encode()).build(); + assert_ok!(result.result); + let gas_consumed_once = result.gas_consumed.ref_time(); + let host_consumed_once = + ::WeightInfo::seal_caller_is_origin().ref_time(); + let engine_consumed_once = + gas_consumed_once - host_consumed_once - engine_consumed_noop; + + let result = builder::bare_call(addr).data(2u32.encode()).build(); + assert_ok!(result.result); + let gas_consumed_twice = result.gas_consumed.ref_time(); + let host_consumed_twice = host_consumed_once * 2; + let engine_consumed_twice = + gas_consumed_twice - host_consumed_twice - engine_consumed_noop; + + // Second contract just repeats first contract's instructions twice. + // If runtime syncs gas with the engine properly, this should pass. + assert_eq!(engine_consumed_twice, engine_consumed_once * 2); + }); + } + + /// Check that contracts with the same account id have different trie ids. + /// Check the `Nonce` storage item for more information. + #[test] + fn instantiate_unique_trie_id() { + let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, deposit_limit::()) + .unwrap(); + + // Instantiate the contract and store its trie id for later comparison. + let addr = + builder::bare_instantiate(Code::Existing(code_hash)).build_and_unwrap_account_id(); + let trie_id = get_contract(&addr).trie_id; + + // Try to instantiate it again without termination should yield an error. + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).build(), + >::DuplicateContract, + ); + + // Terminate the contract. + assert_ok!(builder::call(addr.clone()).build()); + + // Re-Instantiate after termination. + assert_ok!(builder::instantiate(code_hash).build()); + + // Trie ids shouldn't match or we might have a collision + assert_ne!(trie_id, get_contract(&addr).trie_id); + }); + } + + #[test] + fn storage_work() { + let (code, _code_hash) = compile_module::("storage").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + builder::bare_call(addr).build_and_unwrap_result(); + }); + } + + #[test] + fn storage_max_value_limit() { + let (wasm, _code_hash) = compile_module::("storage_size").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_account_id(); + get_contract(&addr); + + // Call contract with allowed storage value. + assert_ok!(builder::call(addr.clone()) + .gas_limit(GAS_LIMIT.set_ref_time(GAS_LIMIT.ref_time() * 2)) // we are copying a huge buffer + .data(limits::PAYLOAD_BYTES.encode()) + .build()); + + // Call contract with too large a storage value. + assert_err_ignore_postinfo!( + builder::call(addr).data((limits::PAYLOAD_BYTES + 1).encode()).build(), + Error::::ValueTooLarge, + ); + }); + } + + #[test] + fn transient_storage_work() { + let (code, _code_hash) = compile_module::("transient_storage").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + builder::bare_call(addr).build_and_unwrap_result(); + }); + } + + #[test] + fn transient_storage_limit_in_call() { + let (wasm_caller, _code_hash_caller) = + compile_module::("create_transient_storage_and_call").unwrap(); + let (wasm_callee, _code_hash_callee) = + compile_module::("set_transient_storage").unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + // Call contracts with storage values within the limit. + // Caller and Callee contracts each set a transient storage value of size 100. + assert_ok!(builder::call(addr_caller.clone()) + .data((100u32, 100u32, &addr_callee).encode()) + .build(),); + + // Call a contract with a storage value that is too large. + // Limit exceeded in the caller contract. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .data((4u32 * 1024u32, 200u32, &addr_callee).encode()) + .build(), + >::OutOfTransientStorage, + ); + + // Call a contract with a storage value that is too large. + // Limit exceeded in the callee contract. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data((50u32, 4 * 1024u32, &addr_callee).encode()) + .build(), + >::ContractTrapped + ); + }); + } + + #[test] + fn deploy_and_call_other_contract() { + let (caller_wasm, _caller_code_hash) = compile_module::("caller_contract").unwrap(); + let (callee_wasm, callee_code_hash) = compile_module::("return_with_data").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + + // Create + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let caller_addr = builder::bare_instantiate(Code::Upload(caller_wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + callee_wasm, + deposit_limit::(), + ) + .unwrap(); + + let callee_addr = Contracts::contract_address( + &caller_addr, + &callee_code_hash, + &[0, 1, 34, 51, 68, 85, 102, 119], // hard coded in wasm + &[], + ); + + // Drop previous events + initialize_block(2); + + // Call BOB contract, which attempts to instantiate and call the callee contract and + // makes various assertions on the results from those calls. + assert_ok!(builder::call(caller_addr.clone()) + .data(callee_code_hash.as_ref().to_vec()) + .build()); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: callee_addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: callee_addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: callee_addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: caller_addr.clone(), + to: callee_addr.clone(), + amount: 32768 // hardcoded in wasm + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: caller_addr.clone(), + contract: callee_addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: caller_addr.clone(), + to: callee_addr.clone(), + amount: 32768, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(caller_addr.clone()), + contract: callee_addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: caller_addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: callee_addr.clone(), + amount: test_utils::contract_info_storage_deposit(&callee_addr), + } + ), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn delegate_call() { + let (caller_wasm, _caller_code_hash) = compile_module::("delegate_call").unwrap(); + let (callee_wasm, callee_code_hash) = compile_module::("delegate_call_lib").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let caller_addr = builder::bare_instantiate(Code::Upload(caller_wasm)) + .value(300_000) + .build_and_unwrap_account_id(); + // Only upload 'callee' code + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), callee_wasm, 100_000,)); + + assert_ok!(builder::call(caller_addr.clone()) + .value(1337) + .data(callee_code_hash.as_ref().to_vec()) + .build()); + }); + } + + #[test] + fn transfer_expendable_cannot_kill_account() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(1_000) + .build_and_unwrap_account_id(); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + let total_balance = ::Currency::total_balance(&addr); + + assert_eq!( + test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr), + test_utils::contract_info_storage_deposit(&addr) + ); + + // Some ot the total balance is held, so it can't be transferred. + assert_err!( + <::Currency as Mutate>::transfer( + &addr, + &ALICE, + total_balance, + Preservation::Expendable, + ), + TokenError::FundsUnavailable, + ); + + assert_eq!(::Currency::total_balance(&addr), total_balance); + }); + } + + #[test] + fn cannot_self_destruct_through_draining() { + let (wasm, _code_hash) = compile_module::("drain").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let value = 1_000; + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(value) + .build_and_unwrap_account_id(); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + // Call BOB which makes it send all funds to the zero address + // The contract code asserts that the transfer fails with the correct error code + assert_ok!(builder::call(addr.clone()).build()); + + // Make sure the account wasn't remove by sending all free balance away. + assert_eq!( + ::Currency::total_balance(&addr), + value + test_utils::contract_info_storage_deposit(&addr) + min_balance, + ); + }); + } + + #[test] + fn cannot_self_destruct_through_storage_refund_after_price_change() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!(get_contract(&addr).extra_deposit(), 0); + assert_eq!( + ::Currency::total_balance(&addr), + info_deposit + min_balance + ); + + // Create 100 bytes of storage with a price of per byte and a single storage item of + // price 2 + assert_ok!(builder::call(addr.clone()).data(100u32.to_le_bytes().to_vec()).build()); + assert_eq!(get_contract(&addr).total_deposit(), info_deposit + 102); + + // Increase the byte price and trigger a refund. This should not have any influence + // because the removal is pro rata and exactly those 100 bytes should have been + // removed. + DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 500); + assert_ok!(builder::call(addr.clone()).data(0u32.to_le_bytes().to_vec()).build()); + + // Make sure the account wasn't removed by the refund + assert_eq!( + ::Currency::total_balance(&addr), + get_contract(&addr).total_deposit() + min_balance, + ); + assert_eq!(get_contract(&addr).extra_deposit(), 2); + }); + } + + #[test] + fn cannot_self_destruct_while_live() { + let (wasm, _code_hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + + // Check that the BOB contract has been instantiated. + get_contract(&addr); + + // Call BOB with input data, forcing it make a recursive call to itself to + // self-destruct, resulting in a trap. + assert_err_ignore_postinfo!( + builder::call(addr.clone()).data(vec![0]).build(), + Error::::ContractTrapped, + ); + + // Check that BOB is still there. + get_contract(&addr); + }); + } + + #[test] + fn self_destruct_works() { + let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(1_000).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&DJANGO, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + + // Check that the BOB contract has been instantiated. + let _ = get_contract(&addr); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + + // Drop all previous events + initialize_block(2); + + // Call BOB without input data which triggers termination. + assert_matches!(builder::call(addr.clone()).build(), Ok(_)); + + // Check that code is still there but refcount dropped to zero. + assert_refcount!(&code_hash, 0); + + // Check that account is gone + assert!(get_contract_checked(&addr).is_none()); + assert_eq!(::Currency::total_balance(&addr), 0); + + // Check that the beneficiary (django) got remaining balance. + assert_eq!( + ::Currency::free_balance(DJANGO), + 1_000_000 + 100_000 + min_balance + ); + + // Check that the Alice is missing Django's benefit. Within ALICE's total balance + // there's also the code upload deposit held. + assert_eq!( + ::Currency::total_balance(&ALICE), + 1_000_000 - (100_000 + min_balance) + ); + + pretty_assertions::assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Terminated { + contract: addr.clone(), + beneficiary: DJANGO + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndReleased { + from: addr.clone(), + to: ALICE, + amount: info_deposit, + } + ), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::KilledAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: addr.clone(), + to: DJANGO, + amount: 100_000 + min_balance, + }), + topics: vec![], + }, + ], + ); + }); + } + + // This tests that one contract cannot prevent another from self-destructing by sending it + // additional funds after it has been drained. + #[test] + fn destroy_contract_and_transfer_funds() { + let (callee_wasm, callee_code_hash) = compile_module::("self_destruct").unwrap(); + let (caller_wasm, _caller_code_hash) = + compile_module::("destroy_and_transfer").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Create code hash for bob to instantiate + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + callee_wasm, + deposit_limit::(), + ) + .unwrap(); + + // This deploys the BOB contract, which in turn deploys the CHARLIE contract during + // construction. + let addr_bob = builder::bare_instantiate(Code::Upload(caller_wasm)) + .value(200_000) + .data(callee_code_hash.as_ref().to_vec()) + .build_and_unwrap_account_id(); + + // Check that the CHARLIE contract has been instantiated. + let addr_charlie = + Contracts::contract_address(&addr_bob, &callee_code_hash, &[], &[0x47, 0x11]); + get_contract(&addr_charlie); + + // Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct. + assert_ok!(builder::call(addr_bob).data(addr_charlie.encode()).build()); + + // Check that CHARLIE has moved on to the great beyond (ie. died). + assert!(get_contract_checked(&addr_charlie).is_none()); + }); + } + + #[test] + fn cannot_self_destruct_in_constructor() { + let (wasm, _) = compile_module::("self_destructing_constructor").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Fail to instantiate the BOB because the constructor calls seal_terminate. + assert_err_ignore_postinfo!( + builder::instantiate_with_code(wasm).value(100_000).build(), + Error::::TerminatedInConstructor, + ); + }); + } + + #[test] + fn crypto_hashes() { + let (wasm, _code_hash) = compile_module::("crypto_hashes").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the CRYPTO_HASHES contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + // Perform the call. + let input = b"_DEAD_BEEF"; + use sp_io::hashing::*; + // Wraps a hash function into a more dynamic form usable for testing. + macro_rules! dyn_hash_fn { + ($name:ident) => { + Box::new(|input| $name(input).as_ref().to_vec().into_boxed_slice()) + }; + } + // All hash functions and their associated output byte lengths. + let test_cases: &[(Box Box<[u8]>>, usize)] = &[ + (dyn_hash_fn!(sha2_256), 32), + (dyn_hash_fn!(keccak_256), 32), + (dyn_hash_fn!(blake2_256), 32), + (dyn_hash_fn!(blake2_128), 16), + ]; + // Test the given hash functions for the input: "_DEAD_BEEF" + for (n, (hash_fn, expected_size)) in test_cases.iter().enumerate() { + // We offset data in the contract tables by 1. + let mut params = vec![(n + 1) as u8]; + params.extend_from_slice(input); + let result = + builder::bare_call(addr.clone()).data(params).build_and_unwrap_result(); + assert!(!result.did_revert()); + let expected = hash_fn(input.as_ref()); + assert_eq!(&result.data[..*expected_size], &*expected); + } + }) + } + + #[test] + fn transfer_return_code() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + // Contract has only the minimal balance so any transfer will fail. + ::Currency::set_balance(&addr, min_balance); + let result = builder::bare_call(addr.clone()).build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + }); + } + + #[test] + fn call_return_code() { + let (caller_code, _caller_hash) = compile_module::("call_return_code").unwrap(); + let (callee_code, _callee_hash) = compile_module::("ok_trap_revert").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + + let addr_bob = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .data(vec![0]) + .build_and_unwrap_account_id(); + ::Currency::set_balance(&addr_bob, min_balance); + + // Contract calls into Django which is no valid contract + let result = builder::bare_call(addr_bob.clone()) + .data(AsRef::<[u8]>::as_ref(&DJANGO).to_vec()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::NotCallable); + + let addr_django = builder::bare_instantiate(Code::Upload(callee_code)) + .origin(RuntimeOrigin::signed(CHARLIE)) + .value(min_balance * 100) + .data(vec![0]) + .build_and_unwrap_account_id(); + ::Currency::set_balance(&addr_django, min_balance); + + // Contract has only the minimal balance so any transfer will fail. + let result = builder::bare_call(addr_bob.clone()) + .data( + AsRef::<[u8]>::as_ref(&addr_django) + .iter() + .chain(&0u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + + // Contract has enough balance but callee reverts because "1" is passed. + ::Currency::set_balance(&addr_bob, min_balance + 1000); + let result = builder::bare_call(addr_bob.clone()) + .data( + AsRef::<[u8]>::as_ref(&addr_django) + .iter() + .chain(&1u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeReverted); + + // Contract has enough balance but callee traps because "2" is passed. + let result = builder::bare_call(addr_bob) + .data( + AsRef::<[u8]>::as_ref(&addr_django) + .iter() + .chain(&2u32.to_le_bytes()) + .cloned() + .collect(), + ) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); + }); + } + + #[test] + fn instantiate_return_code() { + let (caller_code, _caller_hash) = + compile_module::("instantiate_return_code").unwrap(); + let (callee_code, callee_hash) = compile_module::("ok_trap_revert").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + let callee_hash = callee_hash.as_ref().to_vec(); + + assert_ok!(builder::instantiate_with_code(callee_code) + .value(min_balance * 100) + .build()); + + let addr = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + // Contract has only the minimal balance so any transfer will fail. + ::Currency::set_balance(&addr, min_balance); + let result = builder::bare_call(addr.clone()) + .data(callee_hash.clone()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::TransferFailed); + + // Contract has enough balance but the passed code hash is invalid + ::Currency::set_balance(&addr, min_balance + 10_000); + let result = + builder::bare_call(addr.clone()).data(vec![0; 33]).build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CodeNotFound); + + // Contract has enough balance but callee reverts because "1" is passed. + let result = builder::bare_call(addr.clone()) + .data(callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeReverted); + + // Contract has enough balance but callee traps because "2" is passed. + let result = builder::bare_call(addr) + .data(callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect()) + .build_and_unwrap_result(); + assert_return_code!(result, RuntimeReturnCode::CalleeTrapped); + }); + } + + #[test] + fn disabled_chain_extension_errors_on_call() { + let (code, _hash) = compile_module::("chain_extension").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + TestExtension::disable(); + assert_err_ignore_postinfo!( + builder::call(addr.clone()).data(vec![7u8; 8]).build(), + Error::::NoChainExtension, + ); + }); + } + + #[test] + fn chain_extension_works() { + let (code, _hash) = compile_module::("chain_extension").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + // 0 = read input buffer and pass it through as output + let input: Vec = + ExtensionInput { extension_id: 0, func_id: 0, extra: &[99] }.into(); + let result = builder::bare_call(addr.clone()).data(input.clone()).build(); + assert_eq!(TestExtension::last_seen_buffer(), input); + assert_eq!(result.result.unwrap().data, input); + + // 1 = treat inputs as integer primitives and store the supplied integers + builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 0, func_id: 1, extra: &[] }.into()) + .build_and_unwrap_result(); + assert_eq!(TestExtension::last_seen_input_len(), 4); + + // 2 = charge some extra weight (amount supplied in the fifth byte) + let result = builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &0u32.encode() }.into()) + .build(); + assert_ok!(result.result); + let gas_consumed = result.gas_consumed; + let result = builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &42u32.encode() }.into()) + .build(); + assert_ok!(result.result); + assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 42); + let result = builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 0, func_id: 2, extra: &95u32.encode() }.into()) + .build(); + assert_ok!(result.result); + assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 95); + + // 3 = diverging chain extension call that sets flags to 0x1 and returns a fixed buffer + let result = builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 0, func_id: 3, extra: &[] }.into()) + .build_and_unwrap_result(); + assert_eq!(result.flags, ReturnFlags::REVERT); + assert_eq!(result.data, vec![42, 99]); + + // diverging to second chain extension that sets flags to 0x1 and returns a fixed buffer + // We set the MSB part to 1 (instead of 0) which routes the request into the second + // extension + let result = builder::bare_call(addr.clone()) + .data(ExtensionInput { extension_id: 1, func_id: 0, extra: &[] }.into()) + .build_and_unwrap_result(); + assert_eq!(result.flags, ReturnFlags::REVERT); + assert_eq!(result.data, vec![0x4B, 0x1D]); + + // Diverging to third chain extension that is disabled + // We set the MSB part to 2 (instead of 0) which routes the request into the third + // extension + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .data(ExtensionInput { extension_id: 2, func_id: 0, extra: &[] }.into()) + .build(), + Error::::NoChainExtension, + ); + }); + } + + #[test] + fn chain_extension_temp_storage_works() { + let (code, _hash) = compile_module::("chain_extension_temp_storage").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + // Call func 0 and func 1 back to back. + let stop_recursion = 0u8; + let mut input: Vec = + ExtensionInput { extension_id: 3, func_id: 0, extra: &[] }.into(); + input.extend_from_slice( + ExtensionInput { extension_id: 3, func_id: 1, extra: &[stop_recursion] } + .to_vec() + .as_ref(), + ); + + assert_ok!(builder::bare_call(addr.clone()).data(input.clone()).build().result); + }) + } + + #[test] + fn lazy_removal_works() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract + assert_ok!(builder::call(addr.clone()).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + // But value should be still there as the lazy removal did not run, yet. + assert_matches!(child::get(trie, &[99]), Some(42)); + + // Run the lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // Value should be gone now + assert_matches!(child::get::(trie, &[99]), None); + }); + } + + #[test] + fn lazy_batch_removal_works() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let mut tries: Vec = vec![]; + + for i in 0..3u8 { + let addr = builder::bare_instantiate(Code::Upload(code.clone())) + .value(min_balance * 100) + .salt(vec![i]) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract. Contract info should be gone, but value should be still + // there as the lazy removal did not run, yet. + assert_ok!(builder::call(addr.clone()).build()); + + assert!(!>::contains_key(&addr)); + assert_matches!(child::get(trie, &[99]), Some(42)); + + tries.push(trie.clone()) + } + + // Run single lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // The single lazy removal should have removed all queued tries + for trie in tries.iter() { + assert_matches!(child::get::(trie, &[99]), None); + } + }); + } + + #[test] + fn lazy_removal_partial_remove_works() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + + // We create a contract with some extra keys above the weight limit + let extra_keys = 7u32; + let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); + let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); + let vals: Vec<_> = (0..max_keys + extra_keys) + .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) + .collect(); + + let mut ext = ExtBuilder::default().existential_deposit(50).build(); + + let trie = ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + + // Put value into the contracts child trie + for val in &vals { + info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); + } + >::insert(&addr, info.clone()); + + // Terminate the contract + assert_ok!(builder::call(addr.clone()).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + let trie = info.child_trie_info(); + + // But value should be still there as the lazy removal did not run, yet. + for val in &vals { + assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); + } + + trie.clone() + }); + + // The lazy removal limit only applies to the backend but not to the overlay. + // This commits all keys from the overlay to the backend. + ext.commit_all().unwrap(); + + ext.execute_with(|| { + // Run the lazy removal + ContractInfo::::process_deletion_queue_batch(&mut meter); + + // Weight should be exhausted because we could not even delete all keys + assert!(!meter.can_consume(weight_per_key)); + + let mut num_deleted = 0u32; + let mut num_remaining = 0u32; + + for val in &vals { + match child::get::(&trie, &blake2_256(&val.0)) { + None => num_deleted += 1, + Some(x) if x == val.1 => num_remaining += 1, + Some(_) => panic!("Unexpected value in contract storage"), + } + } + + // All but one key is removed + assert_eq!(num_deleted + num_remaining, vals.len() as u32); + assert_eq!(num_deleted, max_keys); + assert_eq!(num_remaining, extra_keys); + }); + } + + #[test] + fn lazy_removal_does_no_run_on_low_remaining_weight() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract + assert_ok!(builder::call(addr.clone()).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + // But value should be still there as the lazy removal did not run, yet. + assert_matches!(child::get(trie, &[99]), Some(42)); + + // Assign a remaining weight which is too low for a successful deletion of the contract + let low_remaining_weight = + <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); + + // Run the lazy removal + Contracts::on_idle(System::block_number(), low_remaining_weight); + + // Value should still be there, since remaining weight was too low for removal + assert_matches!(child::get::(trie, &[99]), Some(42)); + + // Run the lazy removal while deletion_queue is not full + Contracts::on_initialize(System::block_number()); + + // Value should still be there, since deletion_queue was not full + assert_matches!(child::get::(trie, &[99]), Some(42)); + + // Run on_idle with max remaining weight, this should remove the value + Contracts::on_idle(System::block_number(), Weight::MAX); + + // Value should be gone + assert_matches!(child::get::(trie, &[99]), None); + }); + } + + #[test] + fn lazy_removal_does_not_use_all_weight() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + + let mut meter = WeightMeter::with_limit(Weight::from_parts(5_000_000_000, 100 * 1024)); + let mut ext = ExtBuilder::default().existential_deposit(50).build(); + + let (trie, vals, weight_per_key) = ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(&meter); + assert!(max_keys > 0); + + // We create a contract with one less storage item than we can remove within the limit + let vals: Vec<_> = (0..max_keys - 1) + .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) + .collect(); + + // Put value into the contracts child trie + for val in &vals { + info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); + } + >::insert(&addr, info.clone()); + + // Terminate the contract + assert_ok!(builder::call(addr.clone()).build()); + + // Contract info should be gone + assert!(!>::contains_key(&addr)); + + let trie = info.child_trie_info(); + + // But value should be still there as the lazy removal did not run, yet. + for val in &vals { + assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); + } + + (trie, vals, weight_per_key) + }); + + // The lazy removal limit only applies to the backend but not to the overlay. + // This commits all keys from the overlay to the backend. + ext.commit_all().unwrap(); + + ext.execute_with(|| { + // Run the lazy removal + ContractInfo::::process_deletion_queue_batch(&mut meter); + let base_weight = + <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); + assert_eq!(meter.consumed(), weight_per_key.mul(vals.len() as _) + base_weight); + + // All the keys are removed + for val in vals { + assert_eq!(child::get::(&trie, &blake2_256(&val.0)), None); + } + }); + } + + #[test] + fn deletion_queue_ring_buffer_overflow() { + let (code, _hash) = compile_module::("self_destruct").unwrap(); + let mut ext = ExtBuilder::default().existential_deposit(50).build(); + + // setup the deletion queue with custom counters + ext.execute_with(|| { + let queue = DeletionQueueManager::from_test_values(u32::MAX - 1, u32::MAX - 1); + >::set(queue); + }); + + // commit the changes to the storage + ext.commit_all().unwrap(); + + ext.execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let mut tries: Vec = vec![]; + + // add 3 contracts to the deletion queue + for i in 0..3u8 { + let addr = builder::bare_instantiate(Code::Upload(code.clone())) + .value(min_balance * 100) + .salt(vec![i]) + .build_and_unwrap_account_id(); + + let info = get_contract(&addr); + let trie = &info.child_trie_info(); + + // Put value into the contracts child trie + child::put(trie, &[99], &42); + + // Terminate the contract. Contract info should be gone, but value should be still + // there as the lazy removal did not run, yet. + assert_ok!(builder::call(addr.clone()).build()); + + assert!(!>::contains_key(&addr)); + assert_matches!(child::get(trie, &[99]), Some(42)); + + tries.push(trie.clone()) + } + + // Run single lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // The single lazy removal should have removed all queued tries + for trie in tries.iter() { + assert_matches!(child::get::(trie, &[99]), None); + } + + // insert and delete counter values should go from u32::MAX - 1 to 1 + assert_eq!(>::get().as_test_tuple(), (1, 1)); + }) + } + #[test] + fn refcounter() { + let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Create two contracts with the same code and check that they do in fact share it. + let addr0 = builder::bare_instantiate(Code::Upload(wasm.clone())) + .value(min_balance * 100) + .salt(vec![0]) + .build_and_unwrap_account_id(); + let addr1 = builder::bare_instantiate(Code::Upload(wasm.clone())) + .value(min_balance * 100) + .salt(vec![1]) + .build_and_unwrap_account_id(); + assert_refcount!(code_hash, 2); + + // Sharing should also work with the usual instantiate call + let addr2 = builder::bare_instantiate(Code::Existing(code_hash)) + .value(min_balance * 100) + .salt(vec![2]) + .build_and_unwrap_account_id(); + assert_refcount!(code_hash, 3); + + // Terminating one contract should decrement the refcount + assert_ok!(builder::call(addr0).build()); + assert_refcount!(code_hash, 2); + + // remove another one + assert_ok!(builder::call(addr1).build()); + assert_refcount!(code_hash, 1); + + // Pristine code should still be there + PristineCode::::get(code_hash).unwrap(); + + // remove the last contract + assert_ok!(builder::call(addr2).build()); + assert_refcount!(code_hash, 0); + + // refcount is `0` but code should still exists because it needs to be removed manually + assert!(crate::PristineCode::::contains_key(&code_hash)); + }); + } + + #[test] + fn debug_message_works() { + let (wasm, _code_hash) = compile_module::("debug_message_works").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_account_id(); + let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); + + assert_matches!(result.result, Ok(_)); + assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!"); + }); + } + + #[test] + fn debug_message_logging_disabled() { + let (wasm, _code_hash) = compile_module::("debug_message_logging_disabled").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_account_id(); + // the dispatchables always run without debugging + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr, + 0, + GAS_LIMIT, + deposit_limit::(), + vec![] + )); + }); + } + + #[test] + fn debug_message_invalid_utf8() { + let (wasm, _code_hash) = compile_module::("debug_message_invalid_utf8").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(30_000) + .build_and_unwrap_account_id(); + let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); + assert_ok!(result.result); + assert!(result.debug_message.is_empty()); + }); + } + + #[test] + fn gas_estimation_for_subcalls() { + let (caller_code, _caller_hash) = compile_module::("call_with_limit").unwrap(); + let (call_runtime_code, _caller_hash) = compile_module::("call_runtime").unwrap(); + let (dummy_code, _callee_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 2_000 * min_balance); + + let addr_caller = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let addr_dummy = builder::bare_instantiate(Code::Upload(dummy_code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let addr_call_runtime = builder::bare_instantiate(Code::Upload(call_runtime_code)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + // Run the test for all of those weight limits for the subcall + let weights = [ + Weight::zero(), + GAS_LIMIT, + GAS_LIMIT * 2, + GAS_LIMIT / 5, + Weight::from_parts(0, GAS_LIMIT.proof_size()), + Weight::from_parts(GAS_LIMIT.ref_time(), 0), + ]; + + // This call is passed to the sub call in order to create a large `required_weight` + let runtime_call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge { + pre_charge: Weight::from_parts(10_000_000_000, 512 * 1024), + actual_weight: Weight::from_parts(1, 1), + }) + .encode(); + + // Encodes which contract should be sub called with which input + let sub_calls: [(&[u8], Vec<_>, bool); 2] = [ + (addr_dummy.as_ref(), vec![], false), + (addr_call_runtime.as_ref(), runtime_call, true), + ]; + + for weight in weights { + for (sub_addr, sub_input, out_of_gas_in_subcall) in &sub_calls { + let input: Vec = sub_addr + .iter() + .cloned() + .chain(weight.ref_time().to_le_bytes()) + .chain(weight.proof_size().to_le_bytes()) + .chain(sub_input.clone()) + .collect(); + + // Call in order to determine the gas that is required for this call + let result_orig = + builder::bare_call(addr_caller.clone()).data(input.clone()).build(); + assert_ok!(&result_orig.result); + + // If the out of gas happens in the subcall the caller contract + // will just trap. Otherwise we would need to forward an error + // code to signal that the sub contract ran out of gas. + let error: DispatchError = if *out_of_gas_in_subcall { + assert!(result_orig.gas_required.all_gt(result_orig.gas_consumed)); + >::ContractTrapped.into() + } else { + assert_eq!(result_orig.gas_required, result_orig.gas_consumed); + >::OutOfGas.into() + }; + + // Make the same call using the estimated gas. Should succeed. + let result = builder::bare_call(addr_caller.clone()) + .gas_limit(result_orig.gas_required) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .data(input.clone()) + .build(); + assert_ok!(&result.result); + + // Check that it fails with too little ref_time + let result = builder::bare_call(addr_caller.clone()) + .gas_limit(result_orig.gas_required.sub_ref_time(1)) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .data(input.clone()) + .build(); + assert_err!(result.result, error); + + // Check that it fails with too little proof_size + let result = builder::bare_call(addr_caller.clone()) + .gas_limit(result_orig.gas_required.sub_proof_size(1)) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .data(input.clone()) + .build(); + assert_err!(result.result, error); + } + } + }); + } + + #[test] + fn gas_estimation_call_runtime() { + let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + + let addr_caller = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .salt(vec![0]) + .build_and_unwrap_account_id(); + + // Call something trivial with a huge gas limit so that we can observe the effects + // of pre-charging. This should create a difference between consumed and required. + let call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge { + pre_charge: Weight::from_parts(10_000_000, 1_000), + actual_weight: Weight::from_parts(100, 100), + }); + let result = builder::bare_call(addr_caller.clone()).data(call.encode()).build(); + // contract encodes the result of the dispatch runtime + let outcome = u32::decode(&mut result.result.unwrap().data.as_ref()).unwrap(); + assert_eq!(outcome, 0); + assert!(result.gas_required.all_gt(result.gas_consumed)); + + // Make the same call using the required gas. Should succeed. + assert_ok!( + builder::bare_call(addr_caller) + .gas_limit(result.gas_required) + .data(call.encode()) + .build() + .result + ); + }); + } + + #[test] + fn call_runtime_reentrancy_guarded() { + let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); + let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&CHARLIE, 1000 * min_balance); + + let addr_caller = builder::bare_instantiate(Code::Upload(caller_code)) + .value(min_balance * 100) + .salt(vec![0]) + .build_and_unwrap_account_id(); + + let addr_callee = builder::bare_instantiate(Code::Upload(callee_code)) + .value(min_balance * 100) + .salt(vec![1]) + .build_and_unwrap_account_id(); + + // Call pallet_revive call() dispatchable + let call = RuntimeCall::Contracts(crate::Call::call { + dest: addr_callee, + value: 0, + gas_limit: GAS_LIMIT / 3, + storage_deposit_limit: deposit_limit::(), + data: vec![], + }); + + // Call runtime to re-enter back to contracts engine by + // calling dummy contract + let result = builder::bare_call(addr_caller.clone()) + .data(call.encode()) + .build_and_unwrap_result(); + // Call to runtime should fail because of the re-entrancy guard + assert_return_code!(result, RuntimeReturnCode::CallRuntimeFailed); + }); + } + + #[test] + fn ecdsa_recover() { + let (wasm, _code_hash) = compile_module::("ecdsa_recover").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the ecdsa_recover contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + + #[rustfmt::skip] + let signature: [u8; 65] = [ + 161, 234, 203, 74, 147, 96, 51, 212, 5, 174, 231, 9, 142, 48, 137, 201, + 162, 118, 192, 67, 239, 16, 71, 216, 125, 86, 167, 139, 70, 7, 86, 241, + 33, 87, 154, 251, 81, 29, 160, 4, 176, 239, 88, 211, 244, 232, 232, 52, + 211, 234, 100, 115, 230, 47, 80, 44, 152, 166, 62, 50, 8, 13, 86, 175, + 28, + ]; + #[rustfmt::skip] + let message_hash: [u8; 32] = [ + 162, 28, 244, 179, 96, 76, 244, 178, 188, 83, 230, 248, 143, 106, 77, 117, + 239, 95, 244, 171, 65, 95, 62, 153, 174, 166, 182, 28, 130, 73, 196, 208 + ]; + #[rustfmt::skip] + const EXPECTED_COMPRESSED_PUBLIC_KEY: [u8; 33] = [ + 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, + 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, + 152, + ]; + let mut params = vec![]; + params.extend_from_slice(&signature); + params.extend_from_slice(&message_hash); + assert!(params.len() == 65 + 32); + let result = builder::bare_call(addr.clone()).data(params).build_and_unwrap_result(); + assert!(!result.did_revert()); + assert_eq!(result.data, EXPECTED_COMPRESSED_PUBLIC_KEY); + }) + } + + #[test] + fn bare_instantiate_returns_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let result = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .collect_events(CollectEvents::UnsafeCollect) + .build(); + + let events = result.events.unwrap(); + assert!(!events.is_empty()); + assert_eq!(events, System::events()); + }); + } + + #[test] + fn bare_instantiate_does_not_return_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let result = + builder::bare_instantiate(Code::Upload(wasm)).value(min_balance * 100).build(); + + let events = result.events; + assert!(!System::events().is_empty()); + assert!(events.is_none()); + }); + } + + #[test] + fn bare_call_returns_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let result = builder::bare_call(addr.clone()) + .collect_events(CollectEvents::UnsafeCollect) + .build(); + + let events = result.events.unwrap(); + assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); + assert!(!events.is_empty()); + assert_eq!(events, System::events()); + }); + } + + #[test] + fn bare_call_does_not_return_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = Contracts::min_balance(); + let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(min_balance * 100) + .build_and_unwrap_account_id(); + + let result = builder::bare_call(addr.clone()).build(); + + let events = result.events; + assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); + assert!(!System::events().is_empty()); + assert!(events.is_none()); + }); + } + + #[test] + fn sr25519_verify() { + let (wasm, _code_hash) = compile_module::("sr25519_verify").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the sr25519_verify contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(100_000) + .build_and_unwrap_account_id(); + + let call_with = |message: &[u8; 11]| { + // Alice's signature for "hello world" + #[rustfmt::skip] + let signature: [u8; 64] = [ + 184, 49, 74, 238, 78, 165, 102, 252, 22, 92, 156, 176, 124, 118, 168, 116, 247, + 99, 0, 94, 2, 45, 9, 170, 73, 222, 182, 74, 60, 32, 75, 64, 98, 174, 69, 55, 83, + 85, 180, 98, 208, 75, 231, 57, 205, 62, 4, 105, 26, 136, 172, 17, 123, 99, 90, 255, + 228, 54, 115, 63, 30, 207, 205, 131, + ]; + + // Alice's public key + #[rustfmt::skip] + let public_key: [u8; 32] = [ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, + 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, + ]; + + let mut params = vec![]; + params.extend_from_slice(&signature); + params.extend_from_slice(&public_key); + params.extend_from_slice(message); + + builder::bare_call(addr.clone()).data(params).build_and_unwrap_result() + }; + + // verification should succeed for "hello world" + assert_return_code!(call_with(&b"hello world"), RuntimeReturnCode::Success); + + // verification should fail for other messages + assert_return_code!(call_with(&b"hello worlD"), RuntimeReturnCode::Sr25519VerifyFailed); + }); + } + + #[test] + fn failed_deposit_charge_should_roll_back_call() { + let (wasm_caller, _) = compile_module::("call_runtime_and_call").unwrap(); + let (wasm_callee, _) = compile_module::("store_call").unwrap(); + const ED: u64 = 200; + + let execute = || { + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate both contracts. + let addr_caller = builder::bare_instantiate(Code::Upload(wasm_caller.clone())) + .build_and_unwrap_account_id(); + let addr_callee = builder::bare_instantiate(Code::Upload(wasm_callee.clone())) + .build_and_unwrap_account_id(); + + // Give caller proxy access to Alice. + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + (), + 0 + )); + + // Create a Proxy call that will attempt to transfer away Alice's balance. + let transfer_call = + Box::new(RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + dest: CHARLIE, + value: pallet_balances::Pallet::::free_balance(&ALICE) - 2 * ED, + })); + + // Wrap the transfer call in a proxy call. + let transfer_proxy_call = RuntimeCall::Proxy(pallet_proxy::Call::proxy { + real: ALICE, + force_proxy_type: Some(()), + call: transfer_call, + }); + + let data = ( + (ED - DepositPerItem::get()) as u32, // storage length + addr_callee, + transfer_proxy_call, + ); + + builder::call(addr_caller).data(data.encode()).build() + }) + }; + + // With a low enough deposit per byte, the call should succeed. + let result = execute().unwrap(); + + // Bump the deposit per byte to a high value to trigger a FundsUnavailable error. + DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 20); + assert_err_with_weight!(execute(), TokenError::FundsUnavailable, result.actual_weight); + } + + #[test] + fn upload_code_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert!(!PristineCode::::contains_key(&code_hash)); + + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE + }), + topics: vec![], + },] + ); + }); + } + + #[test] + fn upload_code_limit_too_low() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + let deposit_expected = expected_deposit(wasm.len()); + let deposit_insufficient = deposit_expected.saturating_sub(1); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, deposit_insufficient,), + >::StorageDepositLimitExhausted, + ); + + assert_eq!(System::events(), vec![]); + }); + } + + #[test] + fn upload_code_not_enough_balance() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + let deposit_expected = expected_deposit(wasm.len()); + let deposit_insufficient = deposit_expected.saturating_sub(1); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, deposit_insufficient); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,), + >::StorageDepositNotEnoughFunds, + ); + + assert_eq!(System::events(), vec![]); + }); + } + + #[test] + fn remove_code_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeRemoved { + code_hash, + deposit_released: deposit_expected, + remover: ALICE + }), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn remove_code_wrong_origin() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), + sp_runtime::traits::BadOrigin, + ); + + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE + }), + topics: vec![], + },] + ); + }); + } + + #[test] + fn remove_code_in_use() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + assert_ok!(builder::instantiate_with_code(wasm).build()); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + >::CodeInUse, + ); + + assert_eq!(System::events(), vec![]); + }); + } + + #[test] + fn remove_code_not_found() { + let (_wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Drop previous events + initialize_block(2); + + assert_noop!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + >::CodeNotFound, + ); + + assert_eq!(System::events(), vec![]); + }); + } + + #[test] + fn instantiate_with_zero_balance_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Drop previous events + initialize_block(2); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + + // Make sure the account exists even though no free balance was send + assert_eq!(::Currency::free_balance(&addr), min_balance); + assert_eq!( + ::Currency::total_balance(&addr), + min_balance + test_utils::contract_info_storage_deposit(&addr) + ); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE, + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: addr.clone(), + amount: test_utils::contract_info_storage_deposit(&addr), + } + ), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn instantiate_with_below_existential_deposit_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + let value = 50; + + // Drop previous events + initialize_block(2); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(value) + .build_and_unwrap_account_id(); + + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + // Make sure the account exists even though not enough free balance was send + assert_eq!(::Currency::free_balance(&addr), min_balance + value); + assert_eq!( + ::Currency::total_balance(&addr), + min_balance + value + test_utils::contract_info_storage_deposit(&addr) + ); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { + code_hash, + deposit_held: deposit_expected, + uploader: ALICE + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: 50, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Instantiated { + deployer: ALICE, + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: addr.clone(), + amount: test_utils::contract_info_storage_deposit(&addr), + } + ), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn storage_deposit_works() { + let (wasm, _code_hash) = compile_module::("multi_store").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let mut deposit = test_utils::contract_info_storage_deposit(&addr); + + // Drop previous events + initialize_block(2); + + // Create storage + assert_ok!(builder::call(addr.clone()).value(42).data((50u32, 20u32).encode()).build()); + // 4 is for creating 2 storage items + let charged0 = 4 + 50 + 20; + deposit += charged0; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + // Add more storage (but also remove some) + assert_ok!(builder::call(addr.clone()).data((100u32, 10u32).encode()).build()); + let charged1 = 50 - 10; + deposit += charged1; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + // Remove more storage (but also add some) + assert_ok!(builder::call(addr.clone()).data((10u32, 20u32).encode()).build()); + // -1 for numeric instability + let refunded0 = 90 - 10 - 1; + deposit -= refunded0; + assert_eq!(get_contract(&addr).total_deposit(), deposit); + + assert_eq!( + System::events(), + vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: 42, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: addr.clone(), + amount: charged0, + } + ), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndHeld { + from: ALICE, + to: addr.clone(), + amount: charged1, + } + ), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts( + pallet_revive::Event::StorageDepositTransferredAndReleased { + from: addr.clone(), + to: ALICE, + amount: refunded0, + } + ), + topics: vec![], + }, + ] + ); + }); + } + + #[test] + fn storage_deposit_callee_works() { + let (wasm_caller, _code_hash_caller) = compile_module::("call").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + assert_ok!(builder::call(addr_caller).data((100u32, &addr_callee).encode()).build()); + + let callee = get_contract(&addr_callee); + let deposit = DepositPerByte::get() * 100 + DepositPerItem::get() * 1; + + assert_eq!(test_utils::get_balance(&addr_callee), min_balance); + assert_eq!( + callee.total_deposit(), + deposit + test_utils::contract_info_storage_deposit(&addr_callee) + ); + }); + } + + #[test] + fn set_code_extrinsic() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + let (new_wasm, new_code_hash) = compile_module::("crypto_hashes").unwrap(); + + assert_ne!(code_hash, new_code_hash); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_wasm, + deposit_limit::(), + )); + + // Drop previous events + initialize_block(2); + + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + + // only root can execute this extrinsic + assert_noop!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), addr.clone(), new_code_hash), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![]); + + // contract must exist + assert_noop!( + Contracts::set_code(RuntimeOrigin::root(), BOB, new_code_hash), + >::ContractNotFound, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![]); + + // new code hash must exist + assert_noop!( + Contracts::set_code(RuntimeOrigin::root(), addr.clone(), Default::default()), + >::CodeNotFound, + ); + assert_eq!(get_contract(&addr).code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![]); + + // successful call + assert_ok!(Contracts::set_code(RuntimeOrigin::root(), addr.clone(), new_code_hash)); + assert_eq!(get_contract(&addr).code_hash, new_code_hash); + assert_refcount!(&code_hash, 0); + assert_refcount!(&new_code_hash, 1); + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(pallet_revive::Event::ContractCodeUpdated { + contract: addr.clone(), + new_code_hash, + old_code_hash: code_hash, + }), + topics: vec![], + },] + ); + }); + } + + #[test] + fn slash_cannot_kill_account() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let value = 700; + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + let addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(value) + .build_and_unwrap_account_id(); + + // Drop previous events + initialize_block(2); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + + assert_eq!( + test_utils::get_balance_on_hold(&HoldReason::StorageDepositReserve.into(), &addr), + info_deposit + ); + + assert_eq!( + ::Currency::total_balance(&addr), + info_deposit + value + min_balance + ); + + // Try to destroy the account of the contract by slashing the total balance. + // The account does not get destroyed because slashing only affects the balance held + // under certain `reason`. Slashing can for example happen if the contract takes part + // in staking. + let _ = ::Currency::slash( + &HoldReason::StorageDepositReserve.into(), + &addr, + ::Currency::total_balance(&addr), + ); + + // Slashing only removed the balance held. + assert_eq!(::Currency::total_balance(&addr), value + min_balance); + }); + } + + #[test] + fn contract_reverted() { + let (wasm, code_hash) = compile_module::("return_with_data").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let flags = ReturnFlags::REVERT; + let buffer = [4u8, 8, 15, 16, 23, 42]; + let input = (flags.bits(), buffer).encode(); + + // We just upload the code for later use + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + deposit_limit::(), + )); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).data(input.clone()).build(), + >::ContractReverted, + ); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::instantiate_with_code(wasm).data(input.clone()).build(), + >::ContractReverted, + ); + + // Calling directly: revert leads to success but the flags indicate the error + // This is just a different way of transporting the error that allows the read out + // the `data` which is only there on success. Obviously, the contract isn't + // instantiated. + let result = builder::bare_instantiate(Code::Existing(code_hash)) + .data(input.clone()) + .build_and_unwrap_result(); + assert_eq!(result.result.flags, flags); + assert_eq!(result.result.data, buffer); + assert!(!>::contains_key(result.account_id)); + + // Pass empty flags and therefore successfully instantiate the contract for later use. + let addr = builder::bare_instantiate(Code::Existing(code_hash)) + .data(ReturnFlags::empty().bits().encode()) + .build_and_unwrap_account_id(); + + // Calling extrinsic: revert leads to an error + assert_err_ignore_postinfo!( + builder::call(addr.clone()).data(input.clone()).build(), + >::ContractReverted, + ); + + // Calling directly: revert leads to success but the flags indicate the error + let result = builder::bare_call(addr.clone()).data(input).build_and_unwrap_result(); + assert_eq!(result.flags, flags); + assert_eq!(result.data, buffer); + }); + } + + #[test] + fn set_code_hash() { + let (wasm, code_hash) = compile_module::("set_code_hash").unwrap(); + let (new_wasm, new_code_hash) = + compile_module::("new_set_code_hash_contract").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the 'caller' + let contract_addr = builder::bare_instantiate(Code::Upload(wasm)) + .value(300_000) + .build_and_unwrap_account_id(); + // upload new code + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + new_wasm.clone(), + deposit_limit::(), + )); + + System::reset_events(); + + // First call sets new code_hash and returns 1 + let result = builder::bare_call(contract_addr.clone()) + .data(new_code_hash.as_ref().to_vec()) + .debug(DebugInfo::UnsafeDebug) + .build_and_unwrap_result(); + assert_return_code!(result, 1); + + // Second calls new contract code that returns 2 + let result = builder::bare_call(contract_addr.clone()) + .debug(DebugInfo::UnsafeDebug) + .build_and_unwrap_result(); + assert_return_code!(result, 2); + + // Checking for the last event only + assert_eq!( + &System::events(), + &[ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::ContractCodeUpdated { + contract: contract_addr.clone(), + new_code_hash, + old_code_hash: code_hash, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: contract_addr.clone(), + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::Called { + caller: Origin::from_account_id(ALICE), + contract: contract_addr.clone(), + }), + topics: vec![], + }, + ], + ); + }); + } + + #[test] + fn storage_deposit_limit_is_enforced() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let min_balance = Contracts::min_balance(); + + // Setting insufficient storage_deposit should fail. + assert_err!( + builder::bare_instantiate(Code::Upload(wasm.clone())) + // expected deposit is 2 * ed + 3 for the call + .storage_deposit_limit((2 * min_balance + 3 - 1).into()) + .build() + .result, + >::StorageDepositLimitExhausted, + ); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + // Check that the BOB contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&addr), + info_deposit + min_balance + ); + + // Create 1 byte of storage with a price of per byte, + // setting insufficient deposit limit, as it requires 3 Balance: + // 2 for the item added + 1 for the new storage item. + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .storage_deposit_limit(2) + .data(1u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Create 1 byte of storage, should cost 3 Balance: + // 2 for the item added + 1 for the new storage item. + // Should pass as it fallbacks to DefaultDepositLimit. + assert_ok!(builder::call(addr.clone()) + .storage_deposit_limit(3) + .data(1u32.to_le_bytes().to_vec()) + .build()); + + // Use 4 more bytes of the storage for the same item, which requires 4 Balance. + // Should fail as DefaultDepositLimit is 3 and hence isn't enough. + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .storage_deposit_limit(3) + .data(5u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + }); + } + + #[test] + fn deposit_limit_in_nested_calls() { + let (wasm_caller, _code_hash_caller) = + compile_module::("create_storage_and_call").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + // Create 100 bytes of storage with a price of per byte + // This is 100 Balance + 2 Balance for the item + assert_ok!(builder::call(addr_callee.clone()) + .storage_deposit_limit(102) + .data(100u32.to_le_bytes().to_vec()) + .build()); + + // We do not remove any storage but add a storage item of 12 bytes in the caller + // contract. This would cost 12 + 2 = 14 Balance. + // The nested call doesn't get a special limit, which is set by passing 0 to it. + // This should fail as the specified parent's limit is less than the cost: 13 < + // 14. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .storage_deposit_limit(13) + .data((100u32, &addr_callee, 0u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Now we specify the parent's limit high enough to cover the caller's storage + // additions. However, we use a single byte more in the callee, hence the storage + // deposit should be 15 Balance. + // The nested call doesn't get a special limit, which is set by passing 0 to it. + // This should fail as the specified parent's limit is less than the cost: 14 + // < 15. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .storage_deposit_limit(14) + .data((101u32, &addr_callee, 0u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Now we specify the parent's limit high enough to cover both the caller's and callee's + // storage additions. However, we set a special deposit limit of 1 Balance for the + // nested call. This should fail as callee adds up 2 bytes to the storage, meaning + // that the nested call should have a deposit limit of at least 2 Balance. The + // sub-call should be rolled back, which is covered by the next test case. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .storage_deposit_limit(16) + .data((102u32, &addr_callee, 1u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Refund in the callee contract but not enough to cover the 14 Balance required by the + // caller. Note that if previous sub-call wouldn't roll back, this call would pass + // making the test case fail. We don't set a special limit for the nested call here. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .storage_deposit_limit(0) + .data((87u32, &addr_callee, 0u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + let _ = ::Currency::set_balance(&ALICE, 511); + + // Require more than the sender's balance. + // We don't set a special limit for the nested call. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .data((512u32, &addr_callee, 1u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + + // Same as above but allow for the additional deposit of 1 Balance in parent. + // We set the special deposit limit of 1 Balance for the nested call, which isn't + // enforced as callee frees up storage. This should pass. + assert_ok!(builder::call(addr_caller.clone()) + .storage_deposit_limit(1) + .data((87u32, &addr_callee, 1u64).encode()) + .build()); + }); + } + + #[test] + fn deposit_limit_in_nested_instantiate() { + let (wasm_caller, _code_hash_caller) = + compile_module::("create_storage_and_instantiate").unwrap(); + let (wasm_callee, code_hash_callee) = compile_module::("store_deploy").unwrap(); + const ED: u64 = 5; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, 1_000_000); + // Create caller contract + let addr_caller = builder::bare_instantiate(Code::Upload(wasm_caller)) + .value(10_000u64) // this balance is later passed to the deployed contract + .build_and_unwrap_account_id(); + // Deploy a contract to get its occupied storage size + let addr = builder::bare_instantiate(Code::Upload(wasm_callee)) + .data(vec![0, 0, 0, 0]) + .build_and_unwrap_account_id(); + + let callee_info_len = ContractInfoOf::::get(&addr).unwrap().encoded_size() as u64; + + // We don't set a special deposit limit for the nested instantiation. + // + // The deposit limit set for the parent is insufficient for the instantiation, which + // requires: + // - callee_info_len + 2 for storing the new contract info, + // - ED for deployed contract account, + // - 2 for the storage item of 0 bytes being created in the callee constructor + // or (callee_info_len + 2 + ED + 2) Balance in total. + // + // Provided the limit is set to be 1 Balance less, + // this call should fail on the return from the caller contract. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 1) + .data((0u32, &code_hash_callee, 0u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + // The charges made on instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Now we give enough limit for the instantiation itself, but require for 1 more storage + // byte in the constructor. Hence +1 Balance to the limit is needed. This should fail on + // the return from constructor. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 2) + .data((1u32, &code_hash_callee, 0u64).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Now we set enough limit in parent call, but an insufficient limit for child + // instantiate. This should fail during the charging for the instantiation in + // `RawMeter::charge_instantiate()` + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 2) + .data((0u32, &code_hash_callee, callee_info_len + 2 + ED + 1).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Same as above but requires for single added storage + // item of 1 byte to be covered by the limit, which implies 3 more Balance. + // Now we set enough limit for the parent call, but insufficient limit for child + // instantiate. This should fail right after the constructor execution. + assert_err_ignore_postinfo!( + builder::call(addr_caller.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 3) // enough parent limit + .data((1u32, &code_hash_callee, callee_info_len + 2 + ED + 2).encode()) + .build(), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Set enough deposit limit for the child instantiate. This should succeed. + let result = builder::bare_call(addr_caller.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(callee_info_len + 2 + ED + 4) + .data((1u32, &code_hash_callee, callee_info_len + 2 + ED + 3).encode()) + .build(); + + let returned = result.result.unwrap(); + // All balance of the caller except ED has been transferred to the callee. + // No deposit has been taken from it. + assert_eq!(::Currency::free_balance(&addr_caller), ED); + // Get address of the deployed contract. + let addr_callee = AccountId32::from_slice(&returned.data[0..32]).unwrap(); + // 10_000 should be sent to callee from the caller contract, plus ED to be sent from the + // origin. + assert_eq!(::Currency::free_balance(&addr_callee), 10_000 + ED); + // The origin should be charged with: + // - callee instantiation deposit = (callee_info_len + 2) + // - callee account ED + // - for writing an item of 1 byte to storage = 3 Balance + assert_eq!( + ::Currency::free_balance(&BOB), + 1_000_000 - (callee_info_len + 2 + ED + 3) + ); + // Check that deposit due to be charged still includes these 3 Balance + assert_eq!(result.storage_deposit.charge_or_zero(), (callee_info_len + 2 + ED + 3)) + }); + } + + #[test] + fn deposit_limit_honors_liquidity_restrictions() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let bobs_balance = 1_000; + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, bobs_balance); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&addr), + info_deposit + min_balance + ); + + // check that the hold is honored + ::Currency::hold( + &HoldReason::CodeUploadDepositReserve.into(), + &BOB, + bobs_balance - min_balance, + ) + .unwrap(); + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(10_000) + .data(100u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + assert_eq!(::Currency::free_balance(&BOB), min_balance); + }); + } + + #[test] + fn deposit_limit_honors_existential_deposit() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, 300); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + + // Check that the contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&addr), + min_balance + info_deposit + ); + + // check that the deposit can't bring the account below the existential deposit + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(10_000) + .data(100u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + assert_eq!(::Currency::free_balance(&BOB), 300); + }); + } + + #[test] + fn deposit_limit_honors_min_leftover() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&BOB, 1_000); + let min_balance = Contracts::min_balance(); + + // Instantiate the BOB contract. + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + let info_deposit = test_utils::contract_info_storage_deposit(&addr); + + // Check that the contract has been instantiated and has the minimum balance and the + // storage deposit + assert_eq!(get_contract(&addr).total_deposit(), info_deposit); + assert_eq!( + ::Currency::total_balance(&addr), + info_deposit + min_balance + ); + + // check that the minimum leftover (value send) is considered + // given the minimum deposit of 200 sending 750 will only leave + // 50 for the storage deposit. Which is not enough to store the 50 bytes + // as we also need 2 bytes for the item + assert_err_ignore_postinfo!( + builder::call(addr.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .value(750) + .storage_deposit_limit(10_000) + .data(50u32.to_le_bytes().to_vec()) + .build(), + >::StorageDepositLimitExhausted, + ); + assert_eq!(::Currency::free_balance(&BOB), 1_000); + }); + } + + #[test] + fn locking_delegate_dependency_works() { + // set hash lock up deposit to 30%, to test deposit calculation. + CODE_HASH_LOCKUP_DEPOSIT_PERCENT.with(|c| *c.borrow_mut() = Perbill::from_percent(30)); + + let (wasm_caller, self_code_hash) = + compile_module::("locking_delegate_dependency").unwrap(); + let callee_codes: Vec<_> = + (0..limits::DELEGATE_DEPENDENCIES + 1).map(|idx| dummy_unique(idx)).collect(); + let callee_hashes: Vec<_> = callee_codes + .iter() + .map(|c| ::Hashing::hash(c)) + .collect(); + + // Define inputs with various actions to test locking / unlocking delegate_dependencies. + // See the contract for more details. + let noop_input = (0u32, callee_hashes[0]); + let lock_delegate_dependency_input = (1u32, callee_hashes[0]); + let unlock_delegate_dependency_input = (2u32, callee_hashes[0]); + let terminate_input = (3u32, callee_hashes[0]); + + // Instantiate the caller contract with the given input. + let instantiate = |input: &(u32, H256)| { + builder::bare_instantiate(Code::Upload(wasm_caller.clone())) + .data(input.encode()) + .build() + }; + + // Call contract with the given input. + let call = |addr_caller: &AccountId32, input: &(u32, H256)| { + builder::bare_call(addr_caller.clone()).data(input.encode()).build() + }; + const ED: u64 = 2000; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + + // Instantiate with lock_delegate_dependency should fail since the code is not yet on + // chain. + assert_err!( + instantiate(&lock_delegate_dependency_input).result, + Error::::CodeNotFound + ); + + // Upload all the delegated codes (they all have the same size) + let mut deposit = Default::default(); + for code in callee_codes.iter() { + let CodeUploadReturnValue { deposit: deposit_per_code, .. } = + Contracts::bare_upload_code( + RuntimeOrigin::signed(ALICE), + code.clone(), + deposit_limit::(), + ) + .unwrap(); + deposit = deposit_per_code; + } + + // Instantiate should now work. + let addr_caller = + instantiate(&lock_delegate_dependency_input).result.unwrap().account_id; + + // There should be a dependency and a deposit. + let contract = test_utils::get_contract(&addr_caller); + + let dependency_deposit = &CodeHashLockupDepositPercent::get().mul_ceil(deposit); + assert_eq!( + contract.delegate_dependencies().get(&callee_hashes[0]), + Some(dependency_deposit) + ); + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &addr_caller + ), + dependency_deposit + contract.storage_base_deposit() + ); + + // Removing the code should fail, since we have added a dependency. + assert_err!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), callee_hashes[0]), + >::CodeInUse + ); + + // Locking an already existing dependency should fail. + assert_err!( + call(&addr_caller, &lock_delegate_dependency_input).result, + Error::::DelegateDependencyAlreadyExists + ); + + // Locking self should fail. + assert_err!( + call(&addr_caller, &(1u32, self_code_hash)).result, + Error::::CannotAddSelfAsDelegateDependency + ); + + // Locking more than the maximum allowed delegate_dependencies should fail. + for hash in &callee_hashes[1..callee_hashes.len() - 1] { + call(&addr_caller, &(1u32, *hash)).result.unwrap(); + } + assert_err!( + call(&addr_caller, &(1u32, *callee_hashes.last().unwrap())).result, + Error::::MaxDelegateDependenciesReached + ); + + // Unlocking all dependency should work. + for hash in &callee_hashes[..callee_hashes.len() - 1] { + call(&addr_caller, &(2u32, *hash)).result.unwrap(); + } + + // Dependency should be removed, and deposit should be returned. + let contract = test_utils::get_contract(&addr_caller); + assert!(contract.delegate_dependencies().is_empty()); + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &addr_caller + ), + contract.storage_base_deposit() + ); + + // Removing a nonexistent dependency should fail. + assert_err!( + call(&addr_caller, &unlock_delegate_dependency_input).result, + Error::::DelegateDependencyNotFound + ); + + // Locking a dependency with a storage limit too low should fail. + assert_err!( + builder::bare_call(addr_caller.clone()) + .storage_deposit_limit(dependency_deposit - 1) + .data(lock_delegate_dependency_input.encode()) + .build() + .result, + Error::::StorageDepositLimitExhausted + ); + + // Since we unlocked the dependency we should now be able to remove the code. + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), callee_hashes[0])); + + // Calling should fail since the delegated contract is not on chain anymore. + assert_err!(call(&addr_caller, &noop_input).result, Error::::ContractTrapped); + + // Add the dependency back. + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + callee_codes[0].clone(), + deposit_limit::(), + ) + .unwrap(); + call(&addr_caller, &lock_delegate_dependency_input).result.unwrap(); + + // Call terminate should work, and return the deposit. + let balance_before = test_utils::get_balance(&ALICE); + assert_ok!(call(&addr_caller, &terminate_input).result); + assert_eq!( + test_utils::get_balance(&ALICE), + ED + balance_before + contract.storage_base_deposit() + dependency_deposit + ); + + // Terminate should also remove the dependency, so we can remove the code. + assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), callee_hashes[0])); + }); + } + + #[test] + fn native_dependency_deposit_works() { + let (wasm, code_hash) = compile_module::("set_code_hash").unwrap(); + let (dummy_wasm, dummy_code_hash) = compile_module::("dummy").unwrap(); + + // Set hash lock up deposit to 30%, to test deposit calculation. + CODE_HASH_LOCKUP_DEPOSIT_PERCENT.with(|c| *c.borrow_mut() = Perbill::from_percent(30)); + + // Test with both existing and uploaded code + for code in [Code::Upload(wasm.clone()), Code::Existing(code_hash)] { + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let lockup_deposit_percent = CodeHashLockupDepositPercent::get(); + + // Upload the dummy contract, + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + dummy_wasm.clone(), + deposit_limit::(), + ) + .unwrap(); + + // Upload `set_code_hash` contracts if using Code::Existing. + let add_upload_deposit = match code { + Code::Existing(_) => { + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + deposit_limit::(), + ) + .unwrap(); + false + }, + Code::Upload(_) => true, + }; + + // Instantiate the set_code_hash contract. + let res = builder::bare_instantiate(code).build(); + + let addr = res.result.unwrap().account_id; + let base_deposit = test_utils::contract_info_storage_deposit(&addr); + let upload_deposit = test_utils::get_code_deposit(&code_hash); + let extra_deposit = add_upload_deposit.then(|| upload_deposit).unwrap_or_default(); + + // Check initial storage_deposit + // The base deposit should be: contract_info_storage_deposit + 30% * deposit + let deposit = + extra_deposit + base_deposit + lockup_deposit_percent.mul_ceil(upload_deposit); + + assert_eq!( + res.storage_deposit.charge_or_zero(), + deposit + Contracts::min_balance() + ); + + // call set_code_hash + builder::bare_call(addr.clone()) + .data(dummy_code_hash.encode()) + .build_and_unwrap_result(); + + // Check updated storage_deposit + let code_deposit = test_utils::get_code_deposit(&dummy_code_hash); + let deposit = base_deposit + lockup_deposit_percent.mul_ceil(code_deposit); + assert_eq!(test_utils::get_contract(&addr).storage_base_deposit(), deposit); + + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &addr + ), + deposit + ); + }); + } + } + + #[test] + fn root_cannot_upload_code() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::upload_code(RuntimeOrigin::root(), wasm, deposit_limit::()), + DispatchError::BadOrigin, + ); + }); + } + + #[test] + fn root_cannot_remove_code() { + let (_, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::remove_code(RuntimeOrigin::root(), code_hash), + DispatchError::BadOrigin, + ); + }); + } + + #[test] + fn signed_cannot_set_code() { + let (_, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB, code_hash), + DispatchError::BadOrigin, + ); + }); + } + + #[test] + fn none_cannot_call_code() { + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::call(BOB).origin(RuntimeOrigin::none()).build(), + DispatchError::BadOrigin, + ); + }); + } + + #[test] + fn root_can_call() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_account_id(); + + // Call the contract. + assert_ok!(builder::call(addr.clone()).origin(RuntimeOrigin::root()).build()); + }); + } + + #[test] + fn root_cannot_instantiate_with_code() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::instantiate_with_code(wasm).origin(RuntimeOrigin::root()).build(), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn root_cannot_instantiate() { + let (_, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).origin(RuntimeOrigin::root()).build(), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn only_upload_origin_can_upload() { + let (wasm, _) = compile_module::("dummy").unwrap(); + UploadAccount::set(Some(ALICE)); + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let _ = Balances::set_balance(&BOB, 1_000_000); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::root(), + wasm.clone(), + deposit_limit::(), + ), + DispatchError::BadOrigin + ); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(BOB), + wasm.clone(), + deposit_limit::(), + ), + DispatchError::BadOrigin + ); + + // Only alice is allowed to upload contract code. + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + deposit_limit::(), + )); + }); + } + + #[test] + fn only_instantiation_origin_can_instantiate() { + let (code, code_hash) = compile_module::("dummy").unwrap(); + InstantiateAccount::set(Some(ALICE)); + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + let _ = Balances::set_balance(&BOB, 1_000_000); + + assert_err_ignore_postinfo!( + builder::instantiate_with_code(code.clone()) + .origin(RuntimeOrigin::root()) + .build(), + DispatchError::BadOrigin + ); + + assert_err_ignore_postinfo!( + builder::instantiate_with_code(code.clone()) + .origin(RuntimeOrigin::signed(BOB)) + .build(), + DispatchError::BadOrigin + ); + + // Only Alice can instantiate + assert_ok!(builder::instantiate_with_code(code).build()); + + // Bob cannot instantiate with either `instantiate_with_code` or `instantiate`. + assert_err_ignore_postinfo!( + builder::instantiate(code_hash).origin(RuntimeOrigin::signed(BOB)).build(), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn balance_api_returns_free_balance() { + let (wasm, _code_hash) = compile_module::("balance").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Instantiate the BOB contract without any extra balance. + let addr = builder::bare_instantiate(Code::Upload(wasm.to_vec())) + .build_and_unwrap_account_id(); + + let value = 0; + // Call BOB which makes it call the balance runtime API. + // The contract code asserts that the returned balance is 0. + assert_ok!(builder::call(addr.clone()).value(value).build()); + + let value = 1; + // Calling with value will trap the contract. + assert_err_ignore_postinfo!( + builder::call(addr.clone()).value(value).build(), + >::ContractTrapped + ); + }); + } + + #[test] + fn gas_consumed_is_linear_for_nested_calls() { + let (code, _code_hash) = compile_module::("recurse").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_account_id(); + + let [gas_0, gas_1, gas_2, gas_max] = { + [0u32, 1u32, 2u32, limits::CALL_STACK_DEPTH] + .iter() + .map(|i| { + let result = builder::bare_call(addr.clone()).data(i.encode()).build(); + assert_ok!(result.result); + result.gas_consumed + }) + .collect::>() + .try_into() + .unwrap() + }; + + let gas_per_recursion = gas_2.checked_sub(&gas_1).unwrap(); + assert_eq!(gas_max, gas_0 + gas_per_recursion * limits::CALL_STACK_DEPTH as u64); + }); + } + + #[test] + fn read_only_call_cannot_store() { + let (wasm_caller, _code_hash_caller) = compile_module::("read_only_call").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + // Read-only call fails when modifying storage. + assert_err_ignore_postinfo!( + builder::call(addr_caller).data((&addr_callee, 100u32).encode()).build(), + >::ContractTrapped + ); + }); + } + + #[test] + fn read_only_call_cannot_transfer() { + let (wasm_caller, _code_hash_caller) = + compile_module::("call_with_flags_and_value").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + // Read-only call fails when a non-zero value is set. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data( + (addr_callee, pallet_revive_uapi::CallFlags::READ_ONLY.bits(), 100u64) + .encode() + ) + .build(), + >::StateChangeDenied + ); + }); + } + + #[test] + fn read_only_subsequent_call_cannot_store() { + let (wasm_read_only_caller, _code_hash_caller) = + compile_module::("read_only_call").unwrap(); + let (wasm_caller, _code_hash_caller) = + compile_module::("call_with_flags_and_value").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create contracts: Constructors do nothing. + let addr_caller = builder::bare_instantiate(Code::Upload(wasm_read_only_caller)) + .build_and_unwrap_account_id(); + let addr_subsequent_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + // Subsequent call input. + let input = (&addr_callee, pallet_revive_uapi::CallFlags::empty().bits(), 0u64, 100u32); + + // Read-only call fails when modifying storage. + assert_err_ignore_postinfo!( + builder::call(addr_caller) + .data((&addr_subsequent_caller, input).encode()) + .build(), + >::ContractTrapped + ); + }); + } + + #[test] + fn read_only_call_works() { + let (wasm_caller, _code_hash_caller) = compile_module::("read_only_call").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = + builder::bare_instantiate(Code::Upload(wasm_caller)).build_and_unwrap_account_id(); + let addr_callee = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_account_id(); + + assert_ok!(builder::call(addr_caller.clone()).data(addr_callee.encode()).build()); + }); + } +} diff --git a/substrate/frame/revive/src/tests/pallet_dummy.rs b/substrate/frame/revive/src/tests/pallet_dummy.rs new file mode 100644 index 000000000000..2af8475d17ed --- /dev/null +++ b/substrate/frame/revive/src/tests/pallet_dummy.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use pallet::*; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::{ + dispatch::{Pays, PostDispatchInfo}, + ensure, + pallet_prelude::DispatchResultWithPostInfo, + weights::Weight, + }; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + /// Dummy function that overcharges the predispatch weight, allowing us to test the correct + /// values of [`ContractResult::gas_consumed`] and [`ContractResult::gas_required`] in + /// tests. + #[pallet::call_index(1)] + #[pallet::weight(*pre_charge)] + pub fn overestimate_pre_charge( + origin: OriginFor, + pre_charge: Weight, + actual_weight: Weight, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + ensure!(pre_charge.any_gt(actual_weight), "pre_charge must be > actual_weight"); + Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes }) + } + } +} diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs new file mode 100644 index 000000000000..166a0a8606a6 --- /dev/null +++ b/substrate/frame/revive/src/tests/test_debug.rs @@ -0,0 +1,243 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate::{ + debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing}, + primitives::ExecReturnValue, + test_utils::*, + AccountIdOf, +}; +use frame_support::traits::Currency; +use std::cell::RefCell; + +#[derive(Clone, PartialEq, Eq, Debug)] +struct DebugFrame { + contract_account: AccountId32, + call: ExportedFunction, + input: Vec, + result: Option>, +} + +thread_local! { + static DEBUG_EXECUTION_TRACE: RefCell> = RefCell::new(Vec::new()); + static INTERCEPTED_ADDRESS: RefCell> = RefCell::new(None); +} + +pub struct TestDebug; +pub struct TestCallSpan { + contract_account: AccountId32, + call: ExportedFunction, + input: Vec, +} + +impl Tracing for TestDebug { + type CallSpan = TestCallSpan; + + fn new_call_span( + contract_account: &AccountIdOf, + entry_point: ExportedFunction, + input_data: &[u8], + ) -> TestCallSpan { + DEBUG_EXECUTION_TRACE.with(|d| { + d.borrow_mut().push(DebugFrame { + contract_account: contract_account.clone(), + call: entry_point, + input: input_data.to_vec(), + result: None, + }) + }); + TestCallSpan { + contract_account: contract_account.clone(), + call: entry_point, + input: input_data.to_vec(), + } + } +} + +impl CallInterceptor for TestDebug { + fn intercept_call( + contract_address: &::AccountId, + _entry_point: ExportedFunction, + _input_data: &[u8], + ) -> Option { + INTERCEPTED_ADDRESS.with(|i| { + if i.borrow().as_ref() == Some(contract_address) { + Some(Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![] })) + } else { + None + } + }) + } +} + +impl CallSpan for TestCallSpan { + fn after_call(self, output: &ExecReturnValue) { + DEBUG_EXECUTION_TRACE.with(|d| { + d.borrow_mut().push(DebugFrame { + contract_account: self.contract_account, + call: self.call, + input: self.input, + result: Some(output.data.clone()), + }) + }); + } +} + +/// We can only run the tests if we have a riscv toolchain installed +#[cfg(feature = "riscv")] +mod run_tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn debugging_works() { + let (wasm_caller, _) = compile_module::("call").unwrap(); + let (wasm_callee, _) = compile_module::("store_call").unwrap(); + + fn current_stack() -> Vec { + DEBUG_EXECUTION_TRACE.with(|stack| stack.borrow().clone()) + } + + fn deploy(wasm: Vec) -> AccountId32 { + Contracts::bare_instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + deposit_limit::(), + Code::Upload(wasm), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id + } + + fn constructor_frame(contract_account: &AccountId32, after: bool) -> DebugFrame { + DebugFrame { + contract_account: contract_account.clone(), + call: ExportedFunction::Constructor, + input: vec![], + result: if after { Some(vec![]) } else { None }, + } + } + + fn call_frame(contract_account: &AccountId32, args: Vec, after: bool) -> DebugFrame { + DebugFrame { + contract_account: contract_account.clone(), + call: ExportedFunction::Call, + input: args, + result: if after { Some(vec![]) } else { None }, + } + } + + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_eq!(current_stack(), vec![]); + + let addr_caller = deploy(wasm_caller); + let addr_callee = deploy(wasm_callee); + + assert_eq!( + current_stack(), + vec![ + constructor_frame(&addr_caller, false), + constructor_frame(&addr_caller, true), + constructor_frame(&addr_callee, false), + constructor_frame(&addr_callee, true), + ] + ); + + let main_args = (100u32, &addr_callee.clone()).encode(); + let inner_args = (100u32).encode(); + + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + 0, + GAS_LIMIT, + deposit_limit::(), + main_args.clone() + )); + + let stack_top = current_stack()[4..].to_vec(); + assert_eq!( + stack_top, + vec![ + call_frame(&addr_caller, main_args.clone(), false), + call_frame(&addr_callee, inner_args.clone(), false), + call_frame(&addr_callee, inner_args, true), + call_frame(&addr_caller, main_args, true), + ] + ); + }); + } + + #[test] + fn call_interception_works() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + let account_id = Contracts::bare_instantiate( + RuntimeOrigin::signed(ALICE), + 0, + GAS_LIMIT, + deposit_limit::(), + Code::Upload(wasm), + vec![], + // some salt to ensure that the address of this contract is unique among all tests + vec![0x41, 0x41, 0x41, 0x41], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + // no interception yet + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + account_id.clone(), + 0, + GAS_LIMIT, + deposit_limit::(), + vec![], + )); + + // intercept calls to this contract + INTERCEPTED_ADDRESS.with(|i| *i.borrow_mut() = Some(account_id.clone())); + + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + account_id, + 0, + GAS_LIMIT, + deposit_limit::(), + vec![], + ), + >::ContractReverted, + ); + }); + } +} diff --git a/substrate/frame/revive/src/transient_storage.rs b/substrate/frame/revive/src/transient_storage.rs new file mode 100644 index 000000000000..298e0296fe69 --- /dev/null +++ b/substrate/frame/revive/src/transient_storage.rs @@ -0,0 +1,691 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module contains routines for accessing and altering a contract transient storage. + +use crate::{ + exec::{AccountIdOf, Key}, + storage::WriteOutcome, + Config, Error, +}; +use codec::Encode; +use core::marker::PhantomData; +use frame_support::DefaultNoBound; +use sp_runtime::{DispatchError, DispatchResult, Saturating}; +use sp_std::{collections::btree_map::BTreeMap, mem, vec::Vec}; + +/// Meter entry tracks transaction allocations. +#[derive(Default, Debug)] +pub struct MeterEntry { + /// Allocations made in the current transaction. + pub amount: u32, + /// Allocations limit in the current transaction. + pub limit: u32, +} + +impl MeterEntry { + /// Create a new entry. + fn new(limit: u32) -> Self { + Self { limit, amount: Default::default() } + } + + /// Check if the allocated amount exceeds the limit. + fn exceeds_limit(&self, amount: u32) -> bool { + self.amount.saturating_add(amount) > self.limit + } + + /// Absorb the allocation amount of the nested entry into the current entry. + fn absorb(&mut self, rhs: Self) { + self.amount.saturating_accrue(rhs.amount) + } +} + +// The storage meter enforces a limit for each transaction, +// which is calculated as free_storage * (1 - 1/16) for each subsequent frame. +#[derive(DefaultNoBound)] +pub struct StorageMeter { + nested_meters: Vec, + root_meter: MeterEntry, + _phantom: PhantomData, +} + +impl StorageMeter { + const STORAGE_FRACTION_DENOMINATOR: u32 = 16; + /// Create a new storage allocation meter. + fn new(memory_limit: u32) -> Self { + Self { root_meter: MeterEntry::new(memory_limit), ..Default::default() } + } + + /// Charge the allocated amount of transaction storage from the meter. + fn charge(&mut self, amount: u32) -> DispatchResult { + let meter = self.current_mut(); + if meter.exceeds_limit(amount) { + return Err(Error::::OutOfTransientStorage.into()); + } + meter.amount.saturating_accrue(amount); + Ok(()) + } + + /// Revert a transaction meter. + fn revert(&mut self) { + self.nested_meters.pop().expect( + "A call to revert a meter must be preceded by a corresponding call to start a meter; + the code within this crate makes sure that this is always the case; qed", + ); + } + + /// Start a transaction meter. + fn start(&mut self) { + let meter = self.current(); + let mut transaction_limit = meter.limit.saturating_sub(meter.amount); + if !self.nested_meters.is_empty() { + // Allow use of (1 - 1/STORAGE_FRACTION_DENOMINATOR) of free storage for subsequent + // calls. + transaction_limit.saturating_reduce( + transaction_limit.saturating_div(Self::STORAGE_FRACTION_DENOMINATOR), + ); + } + + self.nested_meters.push(MeterEntry::new(transaction_limit)); + } + + /// Commit a transaction meter. + fn commit(&mut self) { + let transaction_meter = self.nested_meters.pop().expect( + "A call to commit a meter must be preceded by a corresponding call to start a meter; + the code within this crate makes sure that this is always the case; qed", + ); + self.current_mut().absorb(transaction_meter) + } + + /// The total allocated amount of memory. + #[cfg(test)] + fn total_amount(&self) -> u32 { + self.nested_meters + .iter() + .fold(self.root_meter.amount, |acc, e| acc.saturating_add(e.amount)) + } + + /// A mutable reference to the current meter entry. + pub fn current_mut(&mut self) -> &mut MeterEntry { + self.nested_meters.last_mut().unwrap_or(&mut self.root_meter) + } + + /// A reference to the current meter entry. + pub fn current(&self) -> &MeterEntry { + self.nested_meters.last().unwrap_or(&self.root_meter) + } +} + +/// An entry representing a journal change. +struct JournalEntry { + key: Vec, + prev_value: Option>, +} + +impl JournalEntry { + /// Create a new change. + fn new(key: Vec, prev_value: Option>) -> Self { + Self { key, prev_value } + } + + /// Revert the change. + fn revert(self, storage: &mut Storage) { + storage.write(&self.key, self.prev_value); + } +} + +/// A journal containing transient storage modifications. +struct Journal(Vec); + +impl Journal { + /// Create a new journal. + fn new() -> Self { + Self(Default::default()) + } + + /// Add a change to the journal. + fn push(&mut self, entry: JournalEntry) { + self.0.push(entry); + } + + /// Length of the journal. + fn len(&self) -> usize { + self.0.len() + } + + /// Roll back all journal changes until the chackpoint + fn rollback(&mut self, storage: &mut Storage, checkpoint: usize) { + self.0.drain(checkpoint..).rev().for_each(|entry| entry.revert(storage)); + } +} + +/// Storage for maintaining the current transaction state. +#[derive(Default)] +struct Storage(BTreeMap, Vec>); + +impl Storage { + /// Read the storage entry. + fn read(&self, key: &Vec) -> Option> { + self.0.get(key).cloned() + } + + /// Write the storage entry. + fn write(&mut self, key: &Vec, value: Option>) -> Option> { + if let Some(value) = value { + // Insert storage entry. + self.0.insert(key.clone(), value) + } else { + // Remove storage entry. + self.0.remove(key) + } + } +} + +/// Transient storage behaves almost identically to regular storage but is discarded after each +/// transaction. It consists of a `BTreeMap` for the current state, a journal of all changes, and a +/// list of checkpoints. On entry to the `start_transaction` function, a marker (checkpoint) is +/// added to the list. New values are written to the current state, and the previous value is +/// recorded in the journal (`write`). When the `commit_transaction` function is called, the marker +/// to the journal index (checkpoint) of when that call was entered is discarded. +/// On `rollback_transaction`, all entries are reverted up to the last checkpoint. +pub struct TransientStorage { + // The storage and journal size is limited by the storage meter. + storage: Storage, + journal: Journal, + // The size of the StorageMeter is limited by the stack depth. + meter: StorageMeter, + // The size of the checkpoints is limited by the stack depth. + checkpoints: Vec, +} + +impl TransientStorage { + /// Create new transient storage with the supplied memory limit. + pub fn new(memory_limit: u32) -> Self { + TransientStorage { + storage: Default::default(), + journal: Journal::new(), + checkpoints: Default::default(), + meter: StorageMeter::new(memory_limit), + } + } + + /// Read the storage value. If the entry does not exist, `None` is returned. + pub fn read(&self, account: &AccountIdOf, key: &Key) -> Option> { + self.storage.read(&Self::storage_key(&account.encode(), &key.hash())) + } + + /// Write a value to storage. + /// + /// If the `value` is `None`, then the entry is removed. If `take` is true, + /// a [`WriteOutcome::Taken`] is returned instead of a [`WriteOutcome::Overwritten`]. + /// If the entry did not exist, [`WriteOutcome::New`] is returned. + pub fn write( + &mut self, + account: &AccountIdOf, + key: &Key, + value: Option>, + take: bool, + ) -> Result { + let key = Self::storage_key(&account.encode(), &key.hash()); + let prev_value = self.storage.read(&key); + // Skip if the same value is being set. + if prev_value != value { + // Calculate the allocation size. + if let Some(value) = &value { + // Charge the key, value and journal entry. + // If a new value is written, a new journal entry is created. The previous value is + // moved to the journal along with its key, and the new value is written to + // storage. + let key_len = key.capacity(); + let mut amount = value + .capacity() + .saturating_add(key_len) + .saturating_add(mem::size_of::()); + if prev_value.is_none() { + // Charge a new storage entry. + // If there was no previous value, a new entry is added to storage (BTreeMap) + // containing a Vec for the key and a Vec for the value. The value was already + // included in the amount. + amount.saturating_accrue(key_len.saturating_add(mem::size_of::>())); + } + self.meter.charge(amount as _)?; + } + self.storage.write(&key, value); + // Update the journal. + self.journal.push(JournalEntry::new(key, prev_value.clone())); + } + + Ok(match (take, prev_value) { + (_, None) => WriteOutcome::New, + (false, Some(prev_value)) => WriteOutcome::Overwritten(prev_value.len() as _), + (true, Some(prev_value)) => WriteOutcome::Taken(prev_value), + }) + } + + /// Start a new nested transaction. + /// + /// This allows to either commit or roll back all changes that are made after this call. + /// For every transaction there must be a matching call to either `rollback_transaction` + /// or `commit_transaction`. + pub fn start_transaction(&mut self) { + self.meter.start(); + self.checkpoints.push(self.journal.len()); + } + + /// Rollback the last transaction started by `start_transaction`. + /// + /// Any changes made during that transaction are discarded. + /// + /// # Panics + /// + /// Will panic if there is no open transaction. + pub fn rollback_transaction(&mut self) { + let checkpoint = self + .checkpoints + .pop() + .expect( + "A call to rollback_transaction must be preceded by a corresponding call to start_transaction; + the code within this crate makes sure that this is always the case; qed" + ); + self.meter.revert(); + self.journal.rollback(&mut self.storage, checkpoint); + } + + /// Commit the last transaction started by `start_transaction`. + /// + /// Any changes made during that transaction are committed. + /// + /// # Panics + /// + /// Will panic if there is no open transaction. + pub fn commit_transaction(&mut self) { + self.checkpoints + .pop() + .expect( + "A call to commit_transaction must be preceded by a corresponding call to start_transaction; + the code within this crate makes sure that this is always the case; qed" + ); + self.meter.commit(); + } + + /// The storage allocation meter used for transaction metering. + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub fn meter(&mut self) -> &mut StorageMeter { + return &mut self.meter + } + + fn storage_key(account: &[u8], key: &[u8]) -> Vec { + let mut storage_key = Vec::with_capacity(account.len() + key.len()); + storage_key.extend_from_slice(&account); + storage_key.extend_from_slice(&key); + storage_key + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{test_utils::*, tests::Test, Error}; + use core::u32::MAX; + + // Calculate the allocation size for the given entry. + fn allocation_size(account: &AccountIdOf, key: &Key, value: Option>) -> u32 { + let mut storage: TransientStorage = TransientStorage::::new(MAX); + storage + .write(account, key, value, false) + .expect("Could not write to transient storage."); + storage.meter().current().amount + } + + #[test] + fn read_write_works() { + let mut storage: TransientStorage = TransientStorage::::new(2048); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![2]), true), + Ok(WriteOutcome::New) + ); + assert_eq!( + storage.write(&BOB, &Key::Fix([3; 32]), Some(vec![3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1])); + assert_eq!(storage.read(&ALICE, &Key::Fix([2; 32])), Some(vec![2])); + assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), Some(vec![3])); + // Overwrite values. + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![4, 5]), false), + Ok(WriteOutcome::Overwritten(1)) + ); + assert_eq!( + storage.write(&BOB, &Key::Fix([3; 32]), Some(vec![6, 7]), true), + Ok(WriteOutcome::Taken(vec![3])) + ); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1])); + assert_eq!(storage.read(&ALICE, &Key::Fix([2; 32])), Some(vec![4, 5])); + assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), Some(vec![6, 7])); + + // Check for an empty value. + assert_eq!( + storage.write(&BOB, &Key::Fix([3; 32]), Some(vec![]), true), + Ok(WriteOutcome::Taken(vec![6, 7])) + ); + assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), Some(vec![])); + + assert_eq!( + storage.write(&BOB, &Key::Fix([3; 32]), None, true), + Ok(WriteOutcome::Taken(vec![])) + ); + assert_eq!(storage.read(&BOB, &Key::Fix([3; 32])), None); + } + + #[test] + fn read_write_with_var_sized_keys_works() { + let mut storage = TransientStorage::::new(2048); + assert_eq!( + storage.write( + &ALICE, + &Key::try_from_var([1; 64].to_vec()).unwrap(), + Some(vec![1]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + storage.write( + &BOB, + &Key::try_from_var([2; 64].to_vec()).unwrap(), + Some(vec![2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + storage.read(&ALICE, &Key::try_from_var([1; 64].to_vec()).unwrap()), + Some(vec![1]) + ); + assert_eq!( + storage.read(&BOB, &Key::try_from_var([2; 64].to_vec()).unwrap()), + Some(vec![2, 3]) + ); + // Overwrite values. + assert_eq!( + storage.write( + &ALICE, + &Key::try_from_var([1; 64].to_vec()).unwrap(), + Some(vec![4, 5]), + false + ), + Ok(WriteOutcome::Overwritten(1)) + ); + assert_eq!( + storage.read(&ALICE, &Key::try_from_var([1; 64].to_vec()).unwrap()), + Some(vec![4, 5]) + ); + } + + #[test] + fn rollback_transaction_works() { + let mut storage = TransientStorage::::new(1024); + + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.rollback_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), None) + } + + #[test] + fn commit_transaction_works() { + let mut storage = TransientStorage::::new(1024); + + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.commit_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1])) + } + + #[test] + fn overwrite_and_commmit_transaction_works() { + let mut storage = TransientStorage::::new(1024); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1, 2]), false), + Ok(WriteOutcome::Overwritten(1)) + ); + storage.commit_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1, 2])) + } + + #[test] + fn rollback_in_nested_transaction_works() { + let mut storage = TransientStorage::::new(1024); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.rollback_transaction(); + storage.commit_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1])); + assert_eq!(storage.read(&BOB, &Key::Fix([1; 32])), None) + } + + #[test] + fn commit_in_nested_transaction_works() { + let mut storage = TransientStorage::::new(1024); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![2]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&CHARLIE, &Key::Fix([1; 32]), Some(vec![3]), false), + Ok(WriteOutcome::New) + ); + storage.commit_transaction(); + storage.commit_transaction(); + storage.commit_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), Some(vec![1])); + assert_eq!(storage.read(&BOB, &Key::Fix([1; 32])), Some(vec![2])); + assert_eq!(storage.read(&CHARLIE, &Key::Fix([1; 32])), Some(vec![3])); + } + + #[test] + fn rollback_all_transactions_works() { + let mut storage = TransientStorage::::new(1024); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![2]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&CHARLIE, &Key::Fix([1; 32]), Some(vec![3]), false), + Ok(WriteOutcome::New) + ); + storage.commit_transaction(); + storage.commit_transaction(); + storage.rollback_transaction(); + assert_eq!(storage.read(&ALICE, &Key::Fix([1; 32])), None); + assert_eq!(storage.read(&BOB, &Key::Fix([1; 32])), None); + assert_eq!(storage.read(&CHARLIE, &Key::Fix([1; 32])), None); + } + + #[test] + fn metering_transactions_works() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size * 2); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + let limit = storage.meter().current().limit; + storage.commit_transaction(); + + storage.start_transaction(); + assert_eq!(storage.meter().current().limit, limit - size); + assert_eq!(storage.meter().current().limit - storage.meter().current().amount, size); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(storage.meter().current().amount, size); + storage.commit_transaction(); + assert_eq!(storage.meter().total_amount(), size * 2); + } + + #[test] + fn metering_nested_transactions_works() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size * 3); + + storage.start_transaction(); + let limit = storage.meter().current().limit; + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!(storage.meter().total_amount(), size); + assert!(storage.meter().current().limit < limit - size); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.commit_transaction(); + assert_eq!(storage.meter().current().limit, limit); + assert_eq!(storage.meter().total_amount(), storage.meter().current().amount); + storage.commit_transaction(); + } + + #[test] + fn metering_transaction_fails() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size - 1); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Err(Error::::OutOfTransientStorage.into()) + ); + assert_eq!(storage.meter.current().amount, 0); + storage.commit_transaction(); + assert_eq!(storage.meter.total_amount(), 0); + } + + #[test] + fn metering_nested_transactions_fails() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size * 2); + + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false), + Err(Error::::OutOfTransientStorage.into()) + ); + storage.commit_transaction(); + storage.commit_transaction(); + assert_eq!(storage.meter.total_amount(), size); + } + + #[test] + fn metering_nested_transaction_with_rollback_works() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size * 2); + + storage.start_transaction(); + let limit = storage.meter.current().limit; + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.rollback_transaction(); + + assert_eq!(storage.meter.total_amount(), 0); + assert_eq!(storage.meter.current().limit, limit); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + let amount = storage.meter().current().amount; + assert_eq!(storage.meter().total_amount(), amount); + storage.commit_transaction(); + } + + #[test] + fn metering_with_rollback_works() { + let size = allocation_size(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096])); + let mut storage = TransientStorage::::new(size * 5); + + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + let amount = storage.meter.total_amount(); + storage.start_transaction(); + assert_eq!( + storage.write(&ALICE, &Key::Fix([2; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.start_transaction(); + assert_eq!( + storage.write(&BOB, &Key::Fix([1; 32]), Some(vec![1u8; 4096]), false), + Ok(WriteOutcome::New) + ); + storage.commit_transaction(); + storage.rollback_transaction(); + assert_eq!(storage.meter.total_amount(), amount); + storage.commit_transaction(); + } +} diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs new file mode 100644 index 000000000000..784993ca793d --- /dev/null +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -0,0 +1,350 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This module provides a means for executing contracts +//! represented in wasm. + +mod runtime; + +#[cfg(doc)] +pub use crate::wasm::runtime::SyscallDoc; + +#[cfg(test)] +pub use runtime::HIGHEST_API_VERSION; + +#[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] +pub use crate::wasm::runtime::{ReturnData, TrapReason}; + +pub use crate::wasm::runtime::{ApiVersion, Memory, Runtime, RuntimeCosts}; + +use crate::{ + exec::{ExecResult, Executable, ExportedFunction, Ext}, + gas::{GasMeter, Token}, + storage::meter::Diff, + weights::WeightInfo, + AccountIdOf, BadOrigin, BalanceOf, CodeHash, CodeInfoOf, CodeVec, Config, Error, Event, + ExecError, HoldReason, Pallet, PristineCode, Weight, API_VERSION, LOG_TARGET, +}; +use alloc::vec::Vec; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + dispatch::DispatchResult, + ensure, + traits::{fungible::MutateHold, tokens::Precision::BestEffort}, +}; +use sp_core::Get; +use sp_runtime::{traits::Hash, DispatchError}; + +/// Validated Wasm module ready for execution. +/// This data structure is immutable once created and stored. +#[derive(Encode, Decode, scale_info::TypeInfo)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T))] +pub struct WasmBlob { + code: CodeVec, + // This isn't needed for contract execution and is not stored alongside it. + #[codec(skip)] + code_info: CodeInfo, + // This is for not calculating the hash every time we need it. + #[codec(skip)] + code_hash: CodeHash, +} + +/// Contract code related data, such as: +/// +/// - owner of the contract, i.e. account uploaded its code, +/// - storage deposit amount, +/// - reference count, +/// +/// It is stored in a separate storage entry to avoid loading the code when not necessary. +#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T))] +pub struct CodeInfo { + /// The account that has uploaded the contract code and hence is allowed to remove it. + owner: AccountIdOf, + /// The amount of balance that was deposited by the owner in order to store it on-chain. + #[codec(compact)] + deposit: BalanceOf, + /// The number of instantiated contracts that use this as their code. + #[codec(compact)] + refcount: u64, + /// Length of the code in bytes. + code_len: u32, + /// The API version that this contract operates under. + /// + /// This determines which host functions are available to the contract. This + /// prevents that new host functions become available to already deployed contracts. + api_version: u16, + /// The behaviour version that this contract operates under. + /// + /// Whenever any observeable change (with the exception of weights) are made we need + /// to make sure that already deployed contracts will not be affected. We do this by + /// exposing the old behaviour depending on the set behaviour version of the contract. + /// + /// As of right now this is a reserved field that is always set to 0. + behaviour_version: u16, +} + +impl ExportedFunction { + /// The wasm export name for the function. + fn identifier(&self) -> &str { + match self { + Self::Constructor => "deploy", + Self::Call => "call", + } + } +} + +/// Cost of code loading from storage. +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Clone, Copy)] +struct CodeLoadToken(u32); + +impl Token for CodeLoadToken { + fn weight(&self) -> Weight { + T::WeightInfo::call_with_code_per_byte(self.0) + .saturating_sub(T::WeightInfo::call_with_code_per_byte(0)) + } +} + +impl WasmBlob { + /// We only check for size and nothing else when the code is uploaded. + pub fn from_code( + code: Vec, + owner: AccountIdOf, + ) -> Result { + let code: CodeVec = + code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?; + let code_len = code.len() as u32; + let bytes_added = code_len.saturating_add(>::max_encoded_len() as u32); + let deposit = Diff { bytes_added, items_added: 2, ..Default::default() } + .update_contract::(None) + .charge_or_zero(); + let code_info = CodeInfo { + owner, + deposit, + refcount: 0, + code_len, + api_version: API_VERSION, + behaviour_version: Default::default(), + }; + let code_hash = T::Hashing::hash(&code); + Ok(WasmBlob { code, code_info, code_hash }) + } + + /// Remove the code from storage and refund the deposit to its owner. + /// + /// Applies all necessary checks before removing the code. + pub fn remove(origin: &T::AccountId, code_hash: CodeHash) -> DispatchResult { + >::try_mutate_exists(&code_hash, |existing| { + if let Some(code_info) = existing { + ensure!(code_info.refcount == 0, >::CodeInUse); + ensure!(&code_info.owner == origin, BadOrigin); + let _ = T::Currency::release( + &HoldReason::CodeUploadDepositReserve.into(), + &code_info.owner, + code_info.deposit, + BestEffort, + ); + let deposit_released = code_info.deposit; + let remover = code_info.owner.clone(); + + *existing = None; + >::remove(&code_hash); + >::deposit_event(Event::CodeRemoved { + code_hash, + deposit_released, + remover, + }); + Ok(()) + } else { + Err(>::CodeNotFound.into()) + } + }) + } + + /// Puts the module blob into storage, and returns the deposit collected for the storage. + pub fn store_code(&mut self) -> Result, Error> { + let code_hash = *self.code_hash(); + >::mutate(code_hash, |stored_code_info| { + match stored_code_info { + // Contract code is already stored in storage. Nothing to be done here. + Some(_) => Ok(Default::default()), + // Upload a new contract code. + // We need to store the code and its code_info, and collect the deposit. + // This `None` case happens only with freshly uploaded modules. This means that + // the `owner` is always the origin of the current transaction. + None => { + let deposit = self.code_info.deposit; + T::Currency::hold( + &HoldReason::CodeUploadDepositReserve.into(), + &self.code_info.owner, + deposit, + ) + .map_err(|_| >::StorageDepositNotEnoughFunds)?; + + self.code_info.refcount = 0; + >::insert(code_hash, &self.code); + *stored_code_info = Some(self.code_info.clone()); + >::deposit_event(Event::CodeStored { + code_hash, + deposit_held: deposit, + uploader: self.code_info.owner.clone(), + }); + Ok(deposit) + }, + } + }) + } +} + +impl CodeInfo { + #[cfg(test)] + pub fn new(owner: T::AccountId) -> Self { + CodeInfo { + owner, + deposit: Default::default(), + refcount: 0, + code_len: 0, + api_version: API_VERSION, + behaviour_version: Default::default(), + } + } + + /// Returns reference count of the module. + pub fn refcount(&self) -> u64 { + self.refcount + } + + /// Return mutable reference to the refcount of the module. + pub fn refcount_mut(&mut self) -> &mut u64 { + &mut self.refcount + } + + /// Returns the deposit of the module. + pub fn deposit(&self) -> BalanceOf { + self.deposit + } +} + +pub struct PreparedCall<'a, E: Ext> { + module: polkavm::Module, + instance: polkavm::RawInstance, + runtime: Runtime<'a, E, polkavm::RawInstance>, + api_version: ApiVersion, +} + +impl<'a, E: Ext> PreparedCall<'a, E> { + pub fn call(mut self) -> ExecResult { + let exec_result = loop { + let interrupt = self.instance.run(); + if let Some(exec_result) = self.runtime.handle_interrupt( + interrupt, + &self.module, + &mut self.instance, + self.api_version, + ) { + break exec_result + } + }; + let _ = self.runtime.ext().gas_meter_mut().sync_from_executor(self.instance.gas())?; + exec_result + } +} + +impl WasmBlob { + pub fn prepare_call>( + self, + mut runtime: Runtime, + entry_point: ExportedFunction, + api_version: ApiVersion, + ) -> Result, ExecError> { + let code = self.code.as_slice(); + + let mut config = polkavm::Config::default(); + config.set_backend(Some(polkavm::BackendKind::Interpreter)); + let engine = + polkavm::Engine::new(&config).expect("interpreter is available on all plattforms; qed"); + + let mut module_config = polkavm::ModuleConfig::new(); + module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync)); + let module = polkavm::Module::new(&engine, &module_config, code.into()).map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to create polkavm module: {err:?}"); + Error::::CodeRejected + })?; + + let entry_program_counter = module + .exports() + .find(|export| export.symbol().as_bytes() == entry_point.identifier().as_bytes()) + .ok_or_else(|| >::CodeRejected)? + .program_counter(); + + let gas_limit_polkavm: polkavm::Gas = runtime.ext().gas_meter_mut().engine_fuel_left()?; + + let mut instance = module.instantiate().map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to instantiate polkavm module: {err:?}"); + Error::::CodeRejected + })?; + + // Increment before execution so that the constructor sees the correct refcount + if let ExportedFunction::Constructor = entry_point { + E::increment_refcount(self.code_hash)?; + } + + instance.set_gas(gas_limit_polkavm); + instance.prepare_call_untyped(entry_program_counter, &[]); + + Ok(PreparedCall { module, instance, runtime, api_version }) + } +} + +impl Executable for WasmBlob { + fn from_storage( + code_hash: CodeHash, + gas_meter: &mut GasMeter, + ) -> Result { + let code_info = >::get(code_hash).ok_or(Error::::CodeNotFound)?; + gas_meter.charge(CodeLoadToken(code_info.code_len))?; + let code = >::get(code_hash).ok_or(Error::::CodeNotFound)?; + Ok(Self { code, code_info, code_hash }) + } + + fn execute>( + self, + ext: &mut E, + function: ExportedFunction, + input_data: Vec, + ) -> ExecResult { + let api_version = if ::UnsafeUnstableInterface::get() { + ApiVersion::UnsafeNewest + } else { + ApiVersion::Versioned(self.code_info.api_version) + }; + let prepared_call = + self.prepare_call(Runtime::new(ext, input_data), function, api_version)?; + prepared_call.call() + } + + fn code_info(&self) -> &CodeInfo { + &self.code_info + } + + fn code_hash(&self) -> &CodeHash { + &self.code_hash + } +} diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs new file mode 100644 index 000000000000..de910e79e73e --- /dev/null +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -0,0 +1,1936 @@ +// 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. + +//! Environment definition of the wasm smart-contract runtime. + +use crate::{ + exec::{ExecError, ExecResult, Ext, Key, TopicOf}, + gas::{ChargedAmount, Token}, + limits, + primitives::ExecReturnValue, + weights::WeightInfo, + BalanceOf, CodeHash, Config, Error, LOG_TARGET, SENTINEL, +}; +use alloc::{boxed::Box, vec, vec::Vec}; +use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; +use core::{fmt, marker::PhantomData}; +use frame_support::{ + dispatch::DispatchInfo, ensure, pallet_prelude::DispatchResultWithPostInfo, parameter_types, + traits::Get, weights::Weight, +}; +use pallet_revive_proc_macro::define_env; +use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags, StorageFlags}; +use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256}; +use sp_runtime::{traits::Zero, DispatchError, RuntimeDebug}; + +type CallOf = ::RuntimeCall; + +/// The maximum nesting depth a contract can use when encoding types. +const MAX_DECODE_NESTING: u32 = 256; + +#[derive(Clone, Copy)] +pub enum ApiVersion { + /// Expose all APIs even unversioned ones. Only used for testing and benchmarking. + UnsafeNewest, + /// Only expose API's up to and including the specified version. + Versioned(u16), +} + +/// Abstraction over the memory access within syscalls. +/// +/// The reason for this abstraction is that we run syscalls on the host machine when +/// benchmarking them. In that case we have direct access to the contract's memory. However, when +/// running within PolkaVM we need to resort to copying as we can't map the contracts memory into +/// the host (as of now). +pub trait Memory { + /// Read designated chunk from the sandbox memory into the supplied buffer. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - requested buffer is not within the bounds of the sandbox memory. + fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError>; + + /// Write the given buffer to the designated location in the sandbox memory. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - designated area is not within the bounds of the sandbox memory. + fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError>; + + /// Read designated chunk from the sandbox memory. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - requested buffer is not within the bounds of the sandbox memory. + fn read(&self, ptr: u32, len: u32) -> Result, DispatchError> { + let mut buf = vec![0u8; len as usize]; + self.read_into_buf(ptr, buf.as_mut_slice())?; + Ok(buf) + } + + /// Read designated chunk from the sandbox memory and attempt to decode into the specified type. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - requested buffer is not within the bounds of the sandbox memory. + /// - the buffer contents cannot be decoded as the required type. + /// + /// # Note + /// + /// There must be an extra benchmark for determining the influence of `len` with + /// regard to the overall weight. + fn read_as_unbounded(&self, ptr: u32, len: u32) -> Result { + let buf = self.read(ptr, len)?; + let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut buf.as_ref()) + .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; + Ok(decoded) + } + + /// Reads and decodes a type with a size fixed at compile time from contract memory. + /// + /// # Only use on fixed size types + /// + /// Don't use this for types where the encoded size is not fixed but merely bounded. Otherwise + /// this implementation will out of bound access the buffer declared by the guest. Some examples + /// of those bounded but not fixed types: Enums with data, `BoundedVec` or any compact encoded + /// integer. + /// + /// # Note + /// + /// The weight of reading a fixed value is included in the overall weight of any + /// contract callable function. + fn read_as(&self, ptr: u32) -> Result { + let buf = self.read(ptr, D::max_encoded_len() as u32)?; + let decoded = D::decode_with_depth_limit(MAX_DECODE_NESTING, &mut buf.as_ref()) + .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; + Ok(decoded) + } +} + +/// Allows syscalls access to the PolkaVM instance they are executing in. +/// +/// In case a contract is executing within PolkaVM its `memory` argument will also implement +/// this trait. The benchmarking implementation of syscalls will only require `Memory` +/// to be implemented. +pub trait PolkaVmInstance: Memory { + fn gas(&self) -> polkavm::Gas; + fn set_gas(&mut self, gas: polkavm::Gas); + fn read_input_regs(&self) -> (u32, u32, u32, u32, u32, u32); + fn write_output(&mut self, output: u32); +} + +// Memory implementation used in benchmarking where guest memory is mapped into the host. +// +// Please note that we could optimize the `read_as_*` functions by decoding directly from +// memory without a copy. However, we don't do that because as it would change the behaviour +// of those functions: A `read_as` with a `len` larger than the actual type can succeed +// in the streaming implementation while it could fail with a segfault in the copy implementation. +#[cfg(feature = "runtime-benchmarks")] +impl Memory for [u8] { + fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError> { + let ptr = ptr as usize; + let bound_checked = + self.get(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; + buf.copy_from_slice(bound_checked); + Ok(()) + } + + fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { + let ptr = ptr as usize; + let bound_checked = + self.get_mut(ptr..ptr + buf.len()).ok_or_else(|| Error::::OutOfBounds)?; + bound_checked.copy_from_slice(buf); + Ok(()) + } +} + +impl Memory for polkavm::RawInstance { + fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError> { + self.read_memory_into(ptr, buf) + .map(|_| ()) + .map_err(|_| Error::::OutOfBounds.into()) + } + + fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { + self.write_memory(ptr, buf).map_err(|_| Error::::OutOfBounds.into()) + } +} + +impl PolkaVmInstance for polkavm::RawInstance { + fn gas(&self) -> polkavm::Gas { + self.gas() + } + + fn set_gas(&mut self, gas: polkavm::Gas) { + self.set_gas(gas) + } + + fn read_input_regs(&self) -> (u32, u32, u32, u32, u32, u32) { + ( + self.reg(polkavm::Reg::A0), + self.reg(polkavm::Reg::A1), + self.reg(polkavm::Reg::A2), + self.reg(polkavm::Reg::A3), + self.reg(polkavm::Reg::A4), + self.reg(polkavm::Reg::A5), + ) + } + + fn write_output(&mut self, output: u32) { + self.set_reg(polkavm::Reg::A0, output); + } +} + +parameter_types! { + /// Getter types used by [`crate::SyscallDoc:call_runtime`] + const CallRuntimeFailed: ReturnErrorCode = ReturnErrorCode::CallRuntimeFailed; + /// Getter types used by [`crate::SyscallDoc::xcm_execute`] + const XcmExecutionFailed: ReturnErrorCode = ReturnErrorCode::XcmExecutionFailed; +} + +impl From for ReturnErrorCode { + fn from(from: ExecReturnValue) -> Self { + if from.flags.contains(ReturnFlags::REVERT) { + Self::CalleeReverted + } else { + Self::Success + } + } +} + +/// The data passed through when a contract uses `seal_return`. +#[derive(RuntimeDebug)] +pub struct ReturnData { + /// The flags as passed through by the contract. They are still unchecked and + /// will later be parsed into a `ReturnFlags` bitflags struct. + flags: u32, + /// The output buffer passed by the contract as return data. + data: Vec, +} + +/// Enumerates all possible reasons why a trap was generated. +/// +/// This is either used to supply the caller with more information about why an error +/// occurred (the SupervisorError variant). +/// The other case is where the trap does not constitute an error but rather was invoked +/// as a quick way to terminate the application (all other variants). +#[derive(RuntimeDebug)] +pub enum TrapReason { + /// The supervisor trapped the contract because of an error condition occurred during + /// execution in privileged code. + SupervisorError(DispatchError), + /// Signals that trap was generated in response to call `seal_return` host function. + Return(ReturnData), + /// Signals that a trap was generated in response to a successful call to the + /// `seal_terminate` host function. + Termination, +} + +impl> From for TrapReason { + fn from(from: T) -> Self { + Self::SupervisorError(from.into()) + } +} + +impl fmt::Display for TrapReason { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + Ok(()) + } +} + +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Copy, Clone)] +pub enum RuntimeCosts { + /// Base Weight of calling a host function. + HostFn, + /// Weight charged for copying data from the sandbox. + CopyFromContract(u32), + /// Weight charged for copying data to the sandbox. + CopyToContract(u32), + /// Weight of calling `seal_caller`. + Caller, + /// Weight of calling `seal_is_contract`. + IsContract, + /// Weight of calling `seal_code_hash`. + CodeHash, + /// Weight of calling `seal_own_code_hash`. + OwnCodeHash, + /// Weight of calling `seal_caller_is_origin`. + CallerIsOrigin, + /// Weight of calling `caller_is_root`. + CallerIsRoot, + /// Weight of calling `seal_address`. + Address, + /// Weight of calling `seal_gas_left`. + GasLeft, + /// Weight of calling `seal_balance`. + Balance, + /// Weight of calling `seal_value_transferred`. + ValueTransferred, + /// Weight of calling `seal_minimum_balance`. + MinimumBalance, + /// Weight of calling `seal_block_number`. + BlockNumber, + /// Weight of calling `seal_now`. + Now, + /// Weight of calling `seal_weight_to_fee`. + WeightToFee, + /// Weight of calling `seal_terminate`, passing the number of locked dependencies. + Terminate(u32), + /// Weight of calling `seal_deposit_event` with the given number of topics and event size. + DepositEvent { num_topic: u32, len: u32 }, + /// Weight of calling `seal_debug_message` per byte of passed message. + DebugMessage(u32), + /// Weight of calling `seal_set_storage` for the given storage item sizes. + SetStorage { old_bytes: u32, new_bytes: u32 }, + /// Weight of calling `seal_clear_storage` per cleared byte. + ClearStorage(u32), + /// Weight of calling `seal_contains_storage` per byte of the checked item. + ContainsStorage(u32), + /// Weight of calling `seal_get_storage` with the specified size in storage. + GetStorage(u32), + /// Weight of calling `seal_take_storage` for the given size. + TakeStorage(u32), + /// Weight of calling `seal_set_transient_storage` for the given storage item sizes. + SetTransientStorage { old_bytes: u32, new_bytes: u32 }, + /// Weight of calling `seal_clear_transient_storage` per cleared byte. + ClearTransientStorage(u32), + /// Weight of calling `seal_contains_transient_storage` per byte of the checked item. + ContainsTransientStorage(u32), + /// Weight of calling `seal_get_transient_storage` with the specified size in storage. + GetTransientStorage(u32), + /// Weight of calling `seal_take_transient_storage` for the given size. + TakeTransientStorage(u32), + /// Weight of calling `seal_transfer`. + Transfer, + /// Base weight of calling `seal_call`. + CallBase, + /// Weight of calling `seal_delegate_call` for the given input size. + DelegateCallBase, + /// Weight of the transfer performed during a call. + CallTransferSurcharge, + /// Weight per byte that is cloned by supplying the `CLONE_INPUT` flag. + CallInputCloned(u32), + /// Weight of calling `seal_instantiate` for the given input length and salt. + Instantiate { input_data_len: u32, salt_len: u32 }, + /// Weight of calling `seal_hash_sha_256` for the given input size. + HashSha256(u32), + /// Weight of calling `seal_hash_keccak_256` for the given input size. + HashKeccak256(u32), + /// Weight of calling `seal_hash_blake2_256` for the given input size. + HashBlake256(u32), + /// Weight of calling `seal_hash_blake2_128` for the given input size. + HashBlake128(u32), + /// Weight of calling `seal_ecdsa_recover`. + EcdsaRecovery, + /// Weight of calling `seal_sr25519_verify` for the given input size. + Sr25519Verify(u32), + /// Weight charged by a chain extension through `seal_call_chain_extension`. + ChainExtension(Weight), + /// Weight charged for calling into the runtime. + CallRuntime(Weight), + /// Weight charged for calling xcm_execute. + CallXcmExecute(Weight), + /// Weight of calling `seal_set_code_hash` + SetCodeHash, + /// Weight of calling `ecdsa_to_eth_address` + EcdsaToEthAddress, + /// Weight of calling `lock_delegate_dependency` + LockDelegateDependency, + /// Weight of calling `unlock_delegate_dependency` + UnlockDelegateDependency, +} + +/// For functions that modify storage, benchmarks are performed with one item in the +/// storage. To account for the worst-case scenario, the weight of the overhead of +/// writing to or reading from full storage is included. For transient storage writes, +/// the rollback weight is added to reflect the worst-case scenario for this operation. +macro_rules! cost_storage { + (write_transient, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::rollback_transient_storage()) + .saturating_add(T::WeightInfo::set_transient_storage_full() + .saturating_sub(T::WeightInfo::set_transient_storage_empty())) + }; + + (read_transient, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::get_transient_storage_full() + .saturating_sub(T::WeightInfo::get_transient_storage_empty())) + }; + + (write, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::set_storage_full() + .saturating_sub(T::WeightInfo::set_storage_empty())) + }; + + (read, $name:ident $(, $arg:expr )*) => { + T::WeightInfo::$name($( $arg ),*) + .saturating_add(T::WeightInfo::get_storage_full() + .saturating_sub(T::WeightInfo::get_storage_empty())) + }; +} + +macro_rules! cost_args { + // cost_args!(name, a, b, c) -> T::WeightInfo::name(a, b, c).saturating_sub(T::WeightInfo::name(0, 0, 0)) + ($name:ident, $( $arg: expr ),+) => { + (T::WeightInfo::$name($( $arg ),+).saturating_sub(cost_args!(@call_zero $name, $( $arg ),+))) + }; + // Transform T::WeightInfo::name(a, b, c) into T::WeightInfo::name(0, 0, 0) + (@call_zero $name:ident, $( $arg:expr ),*) => { + T::WeightInfo::$name($( cost_args!(@replace_token $arg) ),*) + }; + // Replace the token with 0. + (@replace_token $_in:tt) => { 0 }; +} + +impl Token for RuntimeCosts { + fn influence_lowest_gas_limit(&self) -> bool { + match self { + &Self::CallXcmExecute(_) => false, + _ => true, + } + } + + fn weight(&self) -> Weight { + use self::RuntimeCosts::*; + match *self { + HostFn => cost_args!(noop_host_fn, 1), + CopyToContract(len) => T::WeightInfo::seal_input(len), + CopyFromContract(len) => T::WeightInfo::seal_return(len), + Caller => T::WeightInfo::seal_caller(), + IsContract => T::WeightInfo::seal_is_contract(), + CodeHash => T::WeightInfo::seal_code_hash(), + OwnCodeHash => T::WeightInfo::seal_own_code_hash(), + CallerIsOrigin => T::WeightInfo::seal_caller_is_origin(), + CallerIsRoot => T::WeightInfo::seal_caller_is_root(), + Address => T::WeightInfo::seal_address(), + GasLeft => T::WeightInfo::seal_gas_left(), + Balance => T::WeightInfo::seal_balance(), + ValueTransferred => T::WeightInfo::seal_value_transferred(), + MinimumBalance => T::WeightInfo::seal_minimum_balance(), + BlockNumber => T::WeightInfo::seal_block_number(), + Now => T::WeightInfo::seal_now(), + WeightToFee => T::WeightInfo::seal_weight_to_fee(), + Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies), + DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), + DebugMessage(len) => T::WeightInfo::seal_debug_message(len), + SetStorage { new_bytes, old_bytes } => + cost_storage!(write, seal_set_storage, new_bytes, old_bytes), + ClearStorage(len) => cost_storage!(write, seal_clear_storage, len), + ContainsStorage(len) => cost_storage!(read, seal_contains_storage, len), + GetStorage(len) => cost_storage!(read, seal_get_storage, len), + TakeStorage(len) => cost_storage!(write, seal_take_storage, len), + SetTransientStorage { new_bytes, old_bytes } => + cost_storage!(write_transient, seal_set_transient_storage, new_bytes, old_bytes), + ClearTransientStorage(len) => + cost_storage!(write_transient, seal_clear_transient_storage, len), + ContainsTransientStorage(len) => + cost_storage!(read_transient, seal_contains_transient_storage, len), + GetTransientStorage(len) => + cost_storage!(read_transient, seal_get_transient_storage, len), + TakeTransientStorage(len) => + cost_storage!(write_transient, seal_take_transient_storage, len), + Transfer => T::WeightInfo::seal_transfer(), + CallBase => T::WeightInfo::seal_call(0, 0), + DelegateCallBase => T::WeightInfo::seal_delegate_call(), + CallTransferSurcharge => cost_args!(seal_call, 1, 0), + CallInputCloned(len) => cost_args!(seal_call, 0, len), + Instantiate { input_data_len, salt_len } => + T::WeightInfo::seal_instantiate(input_data_len, salt_len), + HashSha256(len) => T::WeightInfo::seal_hash_sha2_256(len), + HashKeccak256(len) => T::WeightInfo::seal_hash_keccak_256(len), + HashBlake256(len) => T::WeightInfo::seal_hash_blake2_256(len), + HashBlake128(len) => T::WeightInfo::seal_hash_blake2_128(len), + EcdsaRecovery => T::WeightInfo::seal_ecdsa_recover(), + Sr25519Verify(len) => T::WeightInfo::seal_sr25519_verify(len), + ChainExtension(weight) | CallRuntime(weight) | CallXcmExecute(weight) => weight, + SetCodeHash => T::WeightInfo::seal_set_code_hash(), + EcdsaToEthAddress => T::WeightInfo::seal_ecdsa_to_eth_address(), + LockDelegateDependency => T::WeightInfo::lock_delegate_dependency(), + UnlockDelegateDependency => T::WeightInfo::unlock_delegate_dependency(), + } + } +} + +/// Same as [`Runtime::charge_gas`]. +/// +/// We need this access as a macro because sometimes hiding the lifetimes behind +/// a function won't work out. +macro_rules! charge_gas { + ($runtime:expr, $costs:expr) => {{ + $runtime.ext.gas_meter_mut().charge($costs) + }}; +} + +/// The kind of call that should be performed. +enum CallType { + /// Execute another instantiated contract + Call { callee_ptr: u32, value_ptr: u32, deposit_ptr: u32, weight: Weight }, + /// Execute deployed code in the context (storage, account ID, value) of the caller contract + DelegateCall { code_hash_ptr: u32 }, +} + +impl CallType { + fn cost(&self) -> RuntimeCosts { + match self { + CallType::Call { .. } => RuntimeCosts::CallBase, + CallType::DelegateCall { .. } => RuntimeCosts::DelegateCallBase, + } + } +} + +/// This is only appropriate when writing out data of constant size that does not depend on user +/// input. In this case the costs for this copy was already charged as part of the token at +/// the beginning of the API entry point. +fn already_charged(_: u32) -> Option { + None +} + +/// Can only be used for one call. +pub struct Runtime<'a, E: Ext, M: ?Sized> { + ext: &'a mut E, + input_data: Option>, + chain_extension: Option::ChainExtension>>, + _phantom_data: PhantomData, +} + +impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { + pub fn handle_interrupt( + &mut self, + interrupt: Result, + module: &polkavm::Module, + instance: &mut M, + api_version: ApiVersion, + ) -> Option { + use polkavm::InterruptKind::*; + + match interrupt { + Err(error) => { + // in contrast to the other returns this "should" not happen: log level error + log::error!(target: LOG_TARGET, "polkavm execution error: {error}"); + Some(Err(Error::::ExecutionFailed.into())) + }, + Ok(Finished) => + Some(Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })), + Ok(Trap) => Some(Err(Error::::ContractTrapped.into())), + Ok(Segfault(_)) => Some(Err(Error::::ExecutionFailed.into())), + Ok(NotEnoughGas) => Some(Err(Error::::OutOfGas.into())), + Ok(Step) => None, + Ok(Ecalli(idx)) => { + let Some(syscall_symbol) = module.imports().get(idx) else { + return Some(Err(>::InvalidSyscall.into())) + }; + match self.handle_ecall(instance, syscall_symbol.as_bytes(), api_version) { + Ok(None) => None, + Ok(Some(return_value)) => { + instance.write_output(return_value); + None + }, + Err(TrapReason::Return(ReturnData { flags, data })) => + match ReturnFlags::from_bits(flags) { + None => Some(Err(Error::::InvalidCallFlags.into())), + Some(flags) => Some(Ok(ExecReturnValue { flags, data })), + }, + Err(TrapReason::Termination) => + Some(Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })), + Err(TrapReason::SupervisorError(error)) => Some(Err(error.into())), + } + }, + } + } +} + +impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { + pub fn new(ext: &'a mut E, input_data: Vec) -> Self { + Self { + ext, + input_data: Some(input_data), + chain_extension: Some(Box::new(Default::default())), + _phantom_data: Default::default(), + } + } + + /// Get a mutable reference to the inner `Ext`. + /// + /// This is mainly for the chain extension to have access to the environment the + /// contract is executing in. + pub fn ext(&mut self) -> &mut E { + self.ext + } + + /// Charge the gas meter with the specified token. + /// + /// Returns `Err(HostError)` if there is not enough gas. + pub fn charge_gas(&mut self, costs: RuntimeCosts) -> Result { + charge_gas!(self, costs) + } + + /// Adjust a previously charged amount down to its actual amount. + /// + /// This is when a maximum a priori amount was charged and then should be partially + /// refunded to match the actual amount. + pub fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) { + self.ext.gas_meter_mut().adjust_gas(charged, actual_costs); + } + + /// Charge, Run and adjust gas, for executing the given dispatchable. + fn call_dispatchable>( + &mut self, + dispatch_info: DispatchInfo, + runtime_cost: impl Fn(Weight) -> RuntimeCosts, + run: impl FnOnce(&mut Self) -> DispatchResultWithPostInfo, + ) -> Result { + use frame_support::dispatch::extract_actual_weight; + let charged = self.charge_gas(runtime_cost(dispatch_info.weight))?; + let result = run(self); + let actual_weight = extract_actual_weight(&result, &dispatch_info); + self.adjust_gas(charged, runtime_cost(actual_weight)); + match result { + Ok(_) => Ok(ReturnErrorCode::Success), + Err(e) => { + if self.ext.debug_buffer_enabled() { + self.ext.append_debug_buffer("call failed with: "); + self.ext.append_debug_buffer(e.into()); + }; + Ok(ErrorReturnCode::get()) + }, + } + } + + /// Write the given buffer and its length to the designated locations in sandbox memory and + /// charge gas according to the token returned by `create_token`. + // + /// `out_ptr` is the location in sandbox memory where `buf` should be written to. + /// `out_len_ptr` is an in-out location in sandbox memory. It is read to determine the + /// length of the buffer located at `out_ptr`. If that buffer is large enough the actual + /// `buf.len()` is written to this location. + /// + /// If `out_ptr` is set to the sentinel value of `SENTINEL` and `allow_skip` is true the + /// operation is skipped and `Ok` is returned. This is supposed to help callers to make copying + /// output optional. For example to skip copying back the output buffer of an `seal_call` + /// when the caller is not interested in the result. + /// + /// `create_token` can optionally instruct this function to charge the gas meter with the token + /// it returns. `create_token` receives the variable amount of bytes that are about to be copied + /// by this function. + /// + /// In addition to the error conditions of `Memory::write` this functions returns + /// `Err` if the size of the buffer located at `out_ptr` is too small to fit `buf`. + pub fn write_sandbox_output( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + buf: &[u8], + allow_skip: bool, + create_token: impl FnOnce(u32) -> Option, + ) -> Result<(), DispatchError> { + if allow_skip && out_ptr == SENTINEL { + return Ok(()) + } + + let buf_len = buf.len() as u32; + let len: u32 = memory.read_as(out_len_ptr)?; + + if len < buf_len { + return Err(Error::::OutputBufferTooSmall.into()) + } + + if let Some(costs) = create_token(buf_len) { + self.charge_gas(costs)?; + } + + memory.write(out_ptr, buf)?; + memory.write(out_len_ptr, &buf_len.encode()) + } + + /// Computes the given hash function on the supplied input. + /// + /// Reads from the sandboxed input buffer into an intermediate buffer. + /// Returns the result directly to the output buffer of the sandboxed memory. + /// + /// It is the callers responsibility to provide an output buffer that + /// is large enough to hold the expected amount of bytes returned by the + /// chosen hash function. + /// + /// # Note + /// + /// The `input` and `output` buffers may overlap. + fn compute_hash_on_intermediate_buffer( + &self, + memory: &mut M, + hash_fn: F, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), DispatchError> + where + F: FnOnce(&[u8]) -> R, + R: AsRef<[u8]>, + { + // Copy input into supervisor memory. + let input = memory.read(input_ptr, input_len)?; + // Compute the hash on the input buffer using the given hash function. + let hash = hash_fn(&input); + // Write the resulting hash back into the sandboxed output buffer. + memory.write(output_ptr, hash.as_ref())?; + Ok(()) + } + + /// Fallible conversion of `DispatchError` to `ReturnErrorCode`. + fn err_into_return_code(from: DispatchError) -> Result { + use ReturnErrorCode::*; + + let transfer_failed = Error::::TransferFailed.into(); + let no_code = Error::::CodeNotFound.into(); + let not_found = Error::::ContractNotFound.into(); + + match from { + x if x == transfer_failed => Ok(TransferFailed), + x if x == no_code => Ok(CodeNotFound), + x if x == not_found => Ok(NotCallable), + err => Err(err), + } + } + + /// Fallible conversion of a `ExecResult` to `ReturnErrorCode`. + fn exec_into_return_code(from: ExecResult) -> Result { + use crate::exec::ErrorOrigin::Callee; + + let ExecError { error, origin } = match from { + Ok(retval) => return Ok(retval.into()), + Err(err) => err, + }; + + match (error, origin) { + (_, Callee) => Ok(ReturnErrorCode::CalleeTrapped), + (err, _) => Self::err_into_return_code(err), + } + } + fn decode_key(&self, memory: &M, key_ptr: u32, key_len: u32) -> Result { + let res = match key_len { + SENTINEL => { + let mut buffer = [0u8; 32]; + memory.read_into_buf(key_ptr, buffer.as_mut())?; + Ok(Key::from_fixed(buffer)) + }, + len => { + ensure!(len <= limits::STORAGE_KEY_BYTES, Error::::DecodingFailed); + let key = memory.read(key_ptr, len)?; + Key::try_from_var(key) + }, + }; + + res.map_err(|_| Error::::DecodingFailed.into()) + } + + fn is_transient(flags: u32) -> Result { + StorageFlags::from_bits(flags) + .ok_or_else(|| >::InvalidStorageFlags.into()) + .map(|flags| flags.contains(StorageFlags::TRANSIENT)) + } + + fn set_storage( + &mut self, + memory: &M, + flags: u32, + key_ptr: u32, + key_len: u32, + value_ptr: u32, + value_len: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |new_bytes: u32, old_bytes: u32| { + if transient { + RuntimeCosts::SetTransientStorage { new_bytes, old_bytes } + } else { + RuntimeCosts::SetStorage { new_bytes, old_bytes } + } + }; + let max_size = self.ext.max_value_size(); + let charged = self.charge_gas(costs(value_len, self.ext.max_value_size()))?; + if value_len > max_size { + return Err(Error::::ValueTooLarge.into()) + } + let key = self.decode_key(memory, key_ptr, key_len)?; + let value = Some(memory.read(value_ptr, value_len)?); + let write_outcome = if transient { + self.ext.set_transient_storage(&key, value, false)? + } else { + self.ext.set_storage(&key, value, false)? + }; + self.adjust_gas(charged, costs(value_len, write_outcome.old_len())); + Ok(write_outcome.old_len_with_sentinel()) + } + + fn clear_storage( + &mut self, + memory: &M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::ClearTransientStorage(len) + } else { + RuntimeCosts::ClearStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.set_transient_storage(&key, None, false)? + } else { + self.ext.set_storage(&key, None, false)? + }; + self.adjust_gas(charged, costs(outcome.old_len())); + Ok(outcome.old_len_with_sentinel()) + } + + fn get_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::GetTransientStorage(len) + } else { + RuntimeCosts::GetStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.get_transient_storage(&key) + } else { + self.ext.get_storage(&key) + }; + if let Some(value) = outcome { + self.adjust_gas(charged, costs(value.len() as u32)); + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value, + false, + already_charged, + )?; + Ok(ReturnErrorCode::Success) + } else { + self.adjust_gas(charged, costs(0)); + Ok(ReturnErrorCode::KeyNotFound) + } + } + + fn contains_storage( + &mut self, + memory: &M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::ContainsTransientStorage(len) + } else { + RuntimeCosts::ContainsStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.get_transient_storage_size(&key) + } else { + self.ext.get_storage_size(&key) + }; + self.adjust_gas(charged, costs(outcome.unwrap_or(0))); + Ok(outcome.unwrap_or(SENTINEL)) + } + + fn take_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + let transient = Self::is_transient(flags)?; + let costs = |len| { + if transient { + RuntimeCosts::TakeTransientStorage(len) + } else { + RuntimeCosts::TakeStorage(len) + } + }; + let charged = self.charge_gas(costs(self.ext.max_value_size()))?; + let key = self.decode_key(memory, key_ptr, key_len)?; + let outcome = if transient { + self.ext.set_transient_storage(&key, None, true)? + } else { + self.ext.set_storage(&key, None, true)? + }; + + if let crate::storage::WriteOutcome::Taken(value) = outcome { + self.adjust_gas(charged, costs(value.len() as u32)); + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value, + false, + already_charged, + )?; + Ok(ReturnErrorCode::Success) + } else { + self.adjust_gas(charged, costs(0)); + Ok(ReturnErrorCode::KeyNotFound) + } + } + + fn call( + &mut self, + memory: &mut M, + flags: CallFlags, + call_type: CallType, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + self.charge_gas(call_type.cost())?; + + let input_data = if flags.contains(CallFlags::CLONE_INPUT) { + let input = self.input_data.as_ref().ok_or(Error::::InputForwarded)?; + charge_gas!(self, RuntimeCosts::CallInputCloned(input.len() as u32))?; + input.clone() + } else if flags.contains(CallFlags::FORWARD_INPUT) { + self.input_data.take().ok_or(Error::::InputForwarded)? + } else { + self.charge_gas(RuntimeCosts::CopyFromContract(input_data_len))?; + memory.read(input_data_ptr, input_data_len)? + }; + + let call_outcome = match call_type { + CallType::Call { callee_ptr, value_ptr, deposit_ptr, weight } => { + let callee: <::T as frame_system::Config>::AccountId = + memory.read_as(callee_ptr)?; + let deposit_limit: BalanceOf<::T> = if deposit_ptr == SENTINEL { + BalanceOf::<::T>::zero() + } else { + memory.read_as(deposit_ptr)? + }; + let read_only = flags.contains(CallFlags::READ_ONLY); + let value: BalanceOf<::T> = memory.read_as(value_ptr)?; + if value > 0u32.into() { + // If the call value is non-zero and state change is not allowed, issue an + // error. + if read_only || self.ext.is_read_only() { + return Err(Error::::StateChangeDenied.into()); + } + self.charge_gas(RuntimeCosts::CallTransferSurcharge)?; + } + self.ext.call( + weight, + deposit_limit, + callee, + value, + input_data, + flags.contains(CallFlags::ALLOW_REENTRY), + read_only, + ) + }, + CallType::DelegateCall { code_hash_ptr } => { + if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) { + return Err(Error::::InvalidCallFlags.into()) + } + let code_hash = memory.read_as(code_hash_ptr)?; + self.ext.delegate_call(code_hash, input_data) + }, + }; + + // `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to + // a halt anyways without anymore code being executed. + if flags.contains(CallFlags::TAIL_CALL) { + if let Ok(return_value) = call_outcome { + return Err(TrapReason::Return(ReturnData { + flags: return_value.flags.bits(), + data: return_value.data, + })) + } + } + + if let Ok(output) = &call_outcome { + self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + )?; + } + Ok(Self::exec_into_return_code(call_outcome)?) + } + + fn instantiate( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + weight: Weight, + deposit_ptr: u32, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + address_ptr: u32, + address_len_ptr: u32, + output_ptr: u32, + output_len_ptr: u32, + salt_ptr: u32, + salt_len: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::Instantiate { input_data_len, salt_len })?; + let deposit_limit: BalanceOf<::T> = if deposit_ptr == SENTINEL { + BalanceOf::<::T>::zero() + } else { + memory.read_as(deposit_ptr)? + }; + let value: BalanceOf<::T> = memory.read_as(value_ptr)?; + let code_hash: CodeHash<::T> = memory.read_as(code_hash_ptr)?; + let input_data = memory.read(input_data_ptr, input_data_len)?; + let salt = memory.read(salt_ptr, salt_len)?; + let instantiate_outcome = + self.ext.instantiate(weight, deposit_limit, code_hash, value, input_data, &salt); + if let Ok((address, output)) = &instantiate_outcome { + if !output.flags.contains(ReturnFlags::REVERT) { + self.write_sandbox_output( + memory, + address_ptr, + address_len_ptr, + &address.encode(), + true, + already_charged, + )?; + } + self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + )?; + } + Ok(Self::exec_into_return_code(instantiate_outcome.map(|(_, retval)| retval))?) + } + + fn terminate(&mut self, memory: &M, beneficiary_ptr: u32) -> Result<(), TrapReason> { + let count = self.ext.locked_delegate_dependencies_count() as _; + self.charge_gas(RuntimeCosts::Terminate(count))?; + + let beneficiary: <::T as frame_system::Config>::AccountId = + memory.read_as(beneficiary_ptr)?; + self.ext.terminate(&beneficiary)?; + Err(TrapReason::Termination) + } +} + +// This is the API exposed to contracts. +// +// # Note +// +// Any input that leads to a out of bound error (reading or writing) or failing to decode +// data passed to the supervisor will lead to a trap. This is not documented explicitly +// for every function. +#[define_env] +pub mod env { + /// Noop function used to benchmark the time it takes to execute an empty function. + #[cfg(feature = "runtime-benchmarks")] + fn noop(&mut self, memory: &mut M) -> Result<(), TrapReason> { + Ok(()) + } + + /// Set the value at the given key in the contract storage. + /// See [`pallet_revive_uapi::HostFn::set_storage_v2`] + #[api_version(0)] + #[mutating] + fn set_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + value_ptr: u32, + value_len: u32, + ) -> Result { + self.set_storage(memory, flags, key_ptr, key_len, value_ptr, value_len) + } + + /// Clear the value at the given key in the contract storage. + /// See [`pallet_revive_uapi::HostFn::clear_storage`] + #[api_version(0)] + #[mutating] + fn clear_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + self.clear_storage(memory, flags, key_ptr, key_len) + } + + /// Retrieve the value under the given key from storage. + /// See [`pallet_revive_uapi::HostFn::get_storage`] + #[api_version(0)] + fn get_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + self.get_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) + } + + /// Checks whether there is a value stored under the given key. + /// See [`pallet_revive_uapi::HostFn::contains_storage`] + #[api_version(0)] + fn contains_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + self.contains_storage(memory, flags, key_ptr, key_len) + } + + /// Retrieve and remove the value under the given key from storage. + /// See [`pallet_revive_uapi::HostFn::take_storage`] + #[api_version(0)] + #[mutating] + fn take_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + self.take_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) + } + + /// Transfer some value to another account. + /// See [`pallet_revive_uapi::HostFn::transfer`]. + #[api_version(0)] + #[mutating] + fn transfer( + &mut self, + memory: &mut M, + account_ptr: u32, + value_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::Transfer)?; + let callee: <::T as frame_system::Config>::AccountId = + memory.read_as(account_ptr)?; + let value: BalanceOf<::T> = memory.read_as(value_ptr)?; + let result = self.ext.transfer(&callee, value); + match result { + Ok(()) => Ok(ReturnErrorCode::Success), + Err(err) => { + let code = Self::err_into_return_code(err)?; + Ok(code) + }, + } + } + + /// Make a call to another contract. + /// See [`pallet_revive_uapi::HostFn::call`]. + #[api_version(0)] + fn call( + &mut self, + memory: &mut M, + flags: u32, + callee_ptr: u32, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: u32, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + self.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::Call { + callee_ptr, + value_ptr, + deposit_ptr, + weight: Weight::from_parts(ref_time_limit, proof_size_limit), + }, + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// Execute code in the context (storage, caller, value) of the current contract. + /// See [`pallet_revive_uapi::HostFn::delegate_call`]. + #[api_version(0)] + fn delegate_call( + &mut self, + memory: &mut M, + flags: u32, + code_hash_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + self.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::DelegateCall { code_hash_ptr }, + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// Instantiate a contract with the specified code hash. + /// See [`pallet_revive_uapi::HostFn::instantiate`]. + #[api_version(0)] + #[mutating] + fn instantiate( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: u32, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + address_ptr: u32, + address_len_ptr: u32, + output_ptr: u32, + output_len_ptr: u32, + salt_ptr: u32, + salt_len: u32, + ) -> Result { + self.instantiate( + memory, + code_hash_ptr, + Weight::from_parts(ref_time_limit, proof_size_limit), + deposit_ptr, + value_ptr, + input_data_ptr, + input_data_len, + address_ptr, + address_len_ptr, + output_ptr, + output_len_ptr, + salt_ptr, + salt_len, + ) + } + + /// Remove the calling account and transfer remaining **free** balance. + /// See [`pallet_revive_uapi::HostFn::terminate`]. + #[api_version(0)] + #[mutating] + fn terminate(&mut self, memory: &mut M, beneficiary_ptr: u32) -> Result<(), TrapReason> { + self.terminate(memory, beneficiary_ptr) + } + + /// Stores the input passed by the caller into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::input`]. + #[api_version(0)] + fn input(&mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + if let Some(input) = self.input_data.take() { + self.write_sandbox_output(memory, out_ptr, out_len_ptr, &input, false, |len| { + Some(RuntimeCosts::CopyToContract(len)) + })?; + self.input_data = Some(input); + Ok(()) + } else { + Err(Error::::InputForwarded.into()) + } + } + + /// Cease contract execution and save a data buffer as a result of the execution. + /// See [`pallet_revive_uapi::HostFn::return_value`]. + #[api_version(0)] + fn seal_return( + &mut self, + memory: &mut M, + flags: u32, + data_ptr: u32, + data_len: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CopyFromContract(data_len))?; + Err(TrapReason::Return(ReturnData { flags, data: memory.read(data_ptr, data_len)? })) + } + + /// Stores the address of the caller into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::caller`]. + #[api_version(0)] + fn caller(&mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Caller)?; + let caller = self.ext.caller().account_id()?.clone(); + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &caller.encode(), + false, + already_charged, + )?) + } + + /// Checks whether a specified address belongs to a contract. + /// See [`pallet_revive_uapi::HostFn::is_contract`]. + #[api_version(0)] + fn is_contract(&mut self, memory: &mut M, account_ptr: u32) -> Result { + self.charge_gas(RuntimeCosts::IsContract)?; + let address: <::T as frame_system::Config>::AccountId = + memory.read_as(account_ptr)?; + + Ok(self.ext.is_contract(&address) as u32) + } + + /// Retrieve the code hash for a specified contract address. + /// See [`pallet_revive_uapi::HostFn::code_hash`]. + #[api_version(0)] + fn code_hash( + &mut self, + memory: &mut M, + account_ptr: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::CodeHash)?; + let address: <::T as frame_system::Config>::AccountId = + memory.read_as(account_ptr)?; + if let Some(value) = self.ext.code_hash(&address) { + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &value.encode(), + false, + already_charged, + )?; + Ok(ReturnErrorCode::Success) + } else { + Ok(ReturnErrorCode::KeyNotFound) + } + } + + /// Retrieve the code hash of the currently executing contract. + /// See [`pallet_revive_uapi::HostFn::own_code_hash`]. + #[api_version(0)] + fn own_code_hash( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::OwnCodeHash)?; + let code_hash_encoded = &self.ext.own_code_hash().encode(); + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + code_hash_encoded, + false, + already_charged, + )?) + } + + /// Checks whether the caller of the current contract is the origin of the whole call stack. + /// See [`pallet_revive_uapi::HostFn::caller_is_origin`]. + #[api_version(0)] + fn caller_is_origin(&mut self, _memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::CallerIsOrigin)?; + Ok(self.ext.caller_is_origin() as u32) + } + + /// Checks whether the caller of the current contract is root. + /// See [`pallet_revive_uapi::HostFn::caller_is_root`]. + #[api_version(0)] + fn caller_is_root(&mut self, _memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::CallerIsRoot)?; + Ok(self.ext.caller_is_root() as u32) + } + + /// Stores the address of the current contract into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::address`]. + #[api_version(0)] + fn address( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Address)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.address().encode(), + false, + already_charged, + )?) + } + + /// Stores the price for the specified amount of weight into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::weight_to_fee`]. + #[api_version(0)] + fn weight_to_fee( + &mut self, + memory: &mut M, + ref_time_limit: u64, + proof_size_limit: u64, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + let weight = Weight::from_parts(ref_time_limit, proof_size_limit); + self.charge_gas(RuntimeCosts::WeightToFee)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.get_weight_price(weight).encode(), + false, + already_charged, + )?) + } + + /// Stores the amount of weight left into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::weight_left`]. + #[api_version(0)] + fn weight_left( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::GasLeft)?; + let gas_left = &self.ext.gas_meter().gas_left().encode(); + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + gas_left, + false, + already_charged, + )?) + } + + /// Stores the *free* balance of the current account into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::balance`]. + #[api_version(0)] + fn balance( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Balance)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.balance().encode(), + false, + already_charged, + )?) + } + + /// Stores the value transferred along with this call/instantiate into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::value_transferred`]. + #[api_version(0)] + fn value_transferred( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::ValueTransferred)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.value_transferred().encode(), + false, + already_charged, + )?) + } + + /// Load the latest block timestamp into the supplied buffer + /// See [`pallet_revive_uapi::HostFn::now`]. + #[api_version(0)] + fn now(&mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::Now)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.now().encode(), + false, + already_charged, + )?) + } + + /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::minimum_balance`]. + #[api_version(0)] + fn minimum_balance( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::MinimumBalance)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.minimum_balance().encode(), + false, + already_charged, + )?) + } + + /// Deposit a contract event with the data buffer and optional list of topics. + /// See [pallet_revive_uapi::HostFn::deposit_event] + #[api_version(0)] + #[mutating] + fn deposit_event( + &mut self, + memory: &mut M, + topics_ptr: u32, + topics_len: u32, + data_ptr: u32, + data_len: u32, + ) -> Result<(), TrapReason> { + let num_topic = topics_len + .checked_div(core::mem::size_of::>() as u32) + .ok_or("Zero sized topics are not allowed")?; + self.charge_gas(RuntimeCosts::DepositEvent { num_topic, len: data_len })?; + if data_len > self.ext.max_value_size() { + return Err(Error::::ValueTooLarge.into()); + } + + let topics: Vec::T>> = match topics_len { + 0 => Vec::new(), + _ => memory.read_as_unbounded(topics_ptr, topics_len)?, + }; + + // If there are more than `event_topics`, then trap. + if topics.len() as u32 > limits::NUM_EVENT_TOPICS { + return Err(Error::::TooManyTopics.into()); + } + + let event_data = memory.read(data_ptr, data_len)?; + + self.ext.deposit_event(topics, event_data); + + Ok(()) + } + + /// Stores the current block number of the current contract into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::block_number`]. + #[api_version(0)] + fn block_number( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BlockNumber)?; + Ok(self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &self.ext.block_number().encode(), + false, + already_charged, + )?) + } + + /// Computes the SHA2 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_sha2_256`]. + #[api_version(0)] + fn hash_sha2_256( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashSha256(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, sha2_256, input_ptr, input_len, output_ptr, + )?) + } + + /// Computes the KECCAK 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_keccak_256`]. + #[api_version(0)] + fn hash_keccak_256( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashKeccak256(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, keccak_256, input_ptr, input_len, output_ptr, + )?) + } + + /// Computes the BLAKE2 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_blake2_256`]. + #[api_version(0)] + fn hash_blake2_256( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashBlake256(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, blake2_256, input_ptr, input_len, output_ptr, + )?) + } + + /// Computes the BLAKE2 128-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_blake2_128`]. + #[api_version(0)] + fn hash_blake2_128( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashBlake128(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, blake2_128, input_ptr, input_len, output_ptr, + )?) + } + + /// Call into the chain extension provided by the chain if any. + /// See [`pallet_revive_uapi::HostFn::call_chain_extension`]. + fn call_chain_extension( + &mut self, + memory: &mut M, + id: u32, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + use crate::chain_extension::{ChainExtension, Environment, RetVal}; + if !::ChainExtension::enabled() { + return Err(Error::::NoChainExtension.into()); + } + let mut chain_extension = self.chain_extension.take().expect( + "Constructor initializes with `Some`. This is the only place where it is set to `None`.\ + It is always reset to `Some` afterwards. qed", + ); + let env = + Environment::new(self, memory, id, input_ptr, input_len, output_ptr, output_len_ptr); + let ret = match chain_extension.call(env)? { + RetVal::Converging(val) => Ok(val), + RetVal::Diverging { flags, data } => + Err(TrapReason::Return(ReturnData { flags: flags.bits(), data })), + }; + self.chain_extension = Some(chain_extension); + ret + } + + /// Emit a custom debug message. + /// See [`pallet_revive_uapi::HostFn::debug_message`]. + #[api_version(0)] + fn debug_message( + &mut self, + memory: &mut M, + str_ptr: u32, + str_len: u32, + ) -> Result { + let str_len = str_len.min(limits::DEBUG_BUFFER_BYTES); + self.charge_gas(RuntimeCosts::DebugMessage(str_len))?; + if self.ext.append_debug_buffer("") { + let data = memory.read(str_ptr, str_len)?; + if let Some(msg) = core::str::from_utf8(&data).ok() { + self.ext.append_debug_buffer(msg); + } + Ok(ReturnErrorCode::Success) + } else { + Ok(ReturnErrorCode::LoggingDisabled) + } + } + + /// Call some dispatchable of the runtime. + /// See [`frame_support::traits::call_runtime`]. + #[mutating] + fn call_runtime( + &mut self, + memory: &mut M, + call_ptr: u32, + call_len: u32, + ) -> Result { + use frame_support::dispatch::GetDispatchInfo; + self.charge_gas(RuntimeCosts::CopyFromContract(call_len))?; + let call: ::RuntimeCall = memory.read_as_unbounded(call_ptr, call_len)?; + self.call_dispatchable::( + call.get_dispatch_info(), + RuntimeCosts::CallRuntime, + |runtime| runtime.ext.call_runtime(call), + ) + } + + /// Execute an XCM program locally, using the contract's address as the origin. + /// See [`pallet_revive_uapi::HostFn::execute_xcm`]. + #[mutating] + fn xcm_execute( + &mut self, + memory: &mut M, + msg_ptr: u32, + msg_len: u32, + ) -> Result { + use frame_support::dispatch::DispatchInfo; + use xcm::VersionedXcm; + use xcm_builder::{ExecuteController, ExecuteControllerWeightInfo}; + + self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; + let message: VersionedXcm> = memory.read_as_unbounded(msg_ptr, msg_len)?; + + let execute_weight = + <::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); + let weight = self.ext.gas_meter().gas_left().max(execute_weight); + let dispatch_info = DispatchInfo { weight, ..Default::default() }; + + self.call_dispatchable::( + dispatch_info, + RuntimeCosts::CallXcmExecute, + |runtime| { + let origin = crate::RawOrigin::Signed(runtime.ext.address().clone()).into(); + let weight_used = <::Xcm>::execute( + origin, + Box::new(message), + weight.saturating_sub(execute_weight), + )?; + + Ok(Some(weight_used.saturating_add(execute_weight)).into()) + }, + ) + } + + /// Send an XCM program from the contract to the specified destination. + /// See [`pallet_revive_uapi::HostFn::send_xcm`]. + #[mutating] + fn xcm_send( + &mut self, + memory: &mut M, + dest_ptr: u32, + msg_ptr: u32, + msg_len: u32, + output_ptr: u32, + ) -> Result { + use xcm::{VersionedLocation, VersionedXcm}; + use xcm_builder::{SendController, SendControllerWeightInfo}; + + self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; + let dest: VersionedLocation = memory.read_as(dest_ptr)?; + + let message: VersionedXcm<()> = memory.read_as_unbounded(msg_ptr, msg_len)?; + let weight = <::Xcm as SendController<_>>::WeightInfo::send(); + self.charge_gas(RuntimeCosts::CallRuntime(weight))?; + let origin = crate::RawOrigin::Signed(self.ext.address().clone()).into(); + + match <::Xcm>::send(origin, dest.into(), message.into()) { + Ok(message_id) => { + memory.write(output_ptr, &message_id.encode())?; + Ok(ReturnErrorCode::Success) + }, + Err(e) => { + if self.ext.append_debug_buffer("") { + self.ext.append_debug_buffer("seal0::xcm_send failed with: "); + self.ext.append_debug_buffer(e.into()); + }; + Ok(ReturnErrorCode::XcmSendFailed) + }, + } + } + + /// Recovers the ECDSA public key from the given message hash and signature. + /// See [`pallet_revive_uapi::HostFn::ecdsa_recover`]. + #[api_version(0)] + fn ecdsa_recover( + &mut self, + memory: &mut M, + signature_ptr: u32, + message_hash_ptr: u32, + output_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::EcdsaRecovery)?; + + let mut signature: [u8; 65] = [0; 65]; + memory.read_into_buf(signature_ptr, &mut signature)?; + let mut message_hash: [u8; 32] = [0; 32]; + memory.read_into_buf(message_hash_ptr, &mut message_hash)?; + + let result = self.ext.ecdsa_recover(&signature, &message_hash); + + match result { + Ok(pub_key) => { + // Write the recovered compressed ecdsa public key back into the sandboxed output + // buffer. + memory.write(output_ptr, pub_key.as_ref())?; + + Ok(ReturnErrorCode::Success) + }, + Err(_) => Ok(ReturnErrorCode::EcdsaRecoveryFailed), + } + } + + /// Verify a sr25519 signature + /// See [`pallet_revive_uapi::HostFn::sr25519_verify`]. + #[api_version(0)] + fn sr25519_verify( + &mut self, + memory: &mut M, + signature_ptr: u32, + pub_key_ptr: u32, + message_len: u32, + message_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::Sr25519Verify(message_len))?; + + let mut signature: [u8; 64] = [0; 64]; + memory.read_into_buf(signature_ptr, &mut signature)?; + + let mut pub_key: [u8; 32] = [0; 32]; + memory.read_into_buf(pub_key_ptr, &mut pub_key)?; + + let message: Vec = memory.read(message_ptr, message_len)?; + + if self.ext.sr25519_verify(&signature, &message, &pub_key) { + Ok(ReturnErrorCode::Success) + } else { + Ok(ReturnErrorCode::Sr25519VerifyFailed) + } + } + + /// Replace the contract code at the specified address with new code. + /// See [`pallet_revive_uapi::HostFn::set_code_hash`]. + #[api_version(0)] + #[mutating] + fn set_code_hash( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::SetCodeHash)?; + let code_hash: CodeHash<::T> = memory.read_as(code_hash_ptr)?; + match self.ext.set_code_hash(code_hash) { + Err(err) => { + let code = Self::err_into_return_code(err)?; + Ok(code) + }, + Ok(()) => Ok(ReturnErrorCode::Success), + } + } + + /// Calculates Ethereum address from the ECDSA compressed public key and stores + /// See [`pallet_revive_uapi::HostFn::ecdsa_to_eth_address`]. + #[api_version(0)] + fn ecdsa_to_eth_address( + &mut self, + memory: &mut M, + key_ptr: u32, + out_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::EcdsaToEthAddress)?; + let mut compressed_key: [u8; 33] = [0; 33]; + memory.read_into_buf(key_ptr, &mut compressed_key)?; + let result = self.ext.ecdsa_to_eth_address(&compressed_key); + match result { + Ok(eth_address) => { + memory.write(out_ptr, eth_address.as_ref())?; + Ok(ReturnErrorCode::Success) + }, + Err(_) => Ok(ReturnErrorCode::EcdsaRecoveryFailed), + } + } + + /// Adds a new delegate dependency to the contract. + /// See [`pallet_revive_uapi::HostFn::lock_delegate_dependency`]. + #[api_version(0)] + #[mutating] + fn lock_delegate_dependency( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::LockDelegateDependency)?; + let code_hash = memory.read_as(code_hash_ptr)?; + self.ext.lock_delegate_dependency(code_hash)?; + Ok(()) + } + + /// Removes the delegate dependency from the contract. + /// see [`pallet_revive_uapi::HostFn::unlock_delegate_dependency`]. + #[api_version(0)] + #[mutating] + fn unlock_delegate_dependency( + &mut self, + memory: &mut M, + code_hash_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::UnlockDelegateDependency)?; + let code_hash = memory.read_as(code_hash_ptr)?; + self.ext.unlock_delegate_dependency(&code_hash)?; + Ok(()) + } +} diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs new file mode 100644 index 000000000000..6a0d27529d87 --- /dev/null +++ b/substrate/frame/revive/src/weights.rs @@ -0,0 +1,2120 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_revive` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-07-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-yaoqqom-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_revive +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/contracts/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_revive`. +pub trait WeightInfo { + fn on_process_deletion_queue_batch() -> Weight; + fn on_initialize_per_trie_key(k: u32, ) -> Weight; + fn v9_migration_step(c: u32, ) -> Weight; + fn v10_migration_step() -> Weight; + fn v11_migration_step(k: u32, ) -> Weight; + fn v12_migration_step(c: u32, ) -> Weight; + fn v13_migration_step() -> Weight; + fn v14_migration_step() -> Weight; + fn v15_migration_step() -> Weight; + fn v16_migration_step() -> Weight; + fn migration_noop() -> Weight; + fn migrate() -> Weight; + fn on_runtime_upgrade_noop() -> Weight; + fn on_runtime_upgrade_in_progress() -> Weight; + fn on_runtime_upgrade() -> Weight; + fn call_with_code_per_byte(c: u32, ) -> Weight; + fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight; + fn instantiate(i: u32, s: u32, ) -> Weight; + fn call() -> Weight; + fn upload_code_determinism_enforced(c: u32, ) -> Weight; + fn upload_code_determinism_relaxed(c: u32, ) -> Weight; + fn remove_code() -> Weight; + fn set_code() -> Weight; + fn noop_host_fn(r: u32, ) -> Weight; + fn seal_caller() -> Weight; + fn seal_is_contract() -> Weight; + fn seal_code_hash() -> Weight; + fn seal_own_code_hash() -> Weight; + fn seal_caller_is_origin() -> Weight; + fn seal_caller_is_root() -> Weight; + fn seal_address() -> Weight; + fn seal_gas_left() -> Weight; + fn seal_balance() -> Weight; + fn seal_value_transferred() -> Weight; + fn seal_minimum_balance() -> Weight; + fn seal_block_number() -> Weight; + fn seal_now() -> Weight; + fn seal_weight_to_fee() -> Weight; + fn seal_input(n: u32, ) -> Weight; + fn seal_return(n: u32, ) -> Weight; + fn seal_terminate(n: u32, ) -> Weight; + fn seal_random() -> Weight; + fn seal_deposit_event(t: u32, n: u32, ) -> Weight; + fn seal_debug_message(i: u32, ) -> Weight; + fn get_storage_empty() -> Weight; + fn get_storage_full() -> Weight; + fn set_storage_empty() -> Weight; + fn set_storage_full() -> Weight; + fn seal_set_storage(n: u32, o: u32, ) -> Weight; + fn seal_clear_storage(n: u32, ) -> Weight; + fn seal_get_storage(n: u32, ) -> Weight; + fn seal_contains_storage(n: u32, ) -> Weight; + fn seal_take_storage(n: u32, ) -> Weight; + fn set_transient_storage_empty() -> Weight; + fn set_transient_storage_full() -> Weight; + fn get_transient_storage_empty() -> Weight; + fn get_transient_storage_full() -> Weight; + fn rollback_transient_storage() -> Weight; + fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight; + fn seal_clear_transient_storage(n: u32, ) -> Weight; + fn seal_get_transient_storage(n: u32, ) -> Weight; + fn seal_contains_transient_storage(n: u32, ) -> Weight; + fn seal_take_transient_storage(n: u32, ) -> Weight; + fn seal_transfer() -> Weight; + fn seal_call(t: u32, i: u32, ) -> Weight; + fn seal_delegate_call() -> Weight; + fn seal_instantiate(i: u32, s: u32, ) -> Weight; + fn seal_hash_sha2_256(n: u32, ) -> Weight; + fn seal_hash_keccak_256(n: u32, ) -> Weight; + fn seal_hash_blake2_256(n: u32, ) -> Weight; + fn seal_hash_blake2_128(n: u32, ) -> Weight; + fn seal_sr25519_verify(n: u32, ) -> Weight; + fn seal_ecdsa_recover() -> Weight; + fn seal_ecdsa_to_eth_address() -> Weight; + fn seal_set_code_hash() -> Weight; + fn lock_delegate_dependency() -> Weight; + fn unlock_delegate_dependency() -> Weight; + fn seal_reentrance_count() -> Weight; + fn seal_account_reentrance_count() -> Weight; + fn seal_instantiation_nonce() -> Weight; + fn instr_i64_load_store(r: u32, ) -> Weight; +} + +/// Weights for `pallet_revive` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:0) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + fn on_process_deletion_queue_batch() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 1_915_000 picoseconds. + Weight::from_parts(1_986_000, 1627) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `k` is `[0, 1024]`. + fn on_initialize_per_trie_key(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `452 + k * (69 ±0)` + // Estimated: `442 + k * (70 ±0)` + // Minimum execution time: 11_103_000 picoseconds. + Weight::from_parts(11_326_000, 442) + // Standard Error: 2_291 + .saturating_add(Weight::from_parts(1_196_329, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// The range of component `c` is `[0, 125952]`. + fn v9_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `211 + c * (1 ±0)` + // Estimated: `6149 + c * (1 ±0)` + // Minimum execution time: 7_783_000 picoseconds. + Weight::from_parts(4_462_075, 6149) + // Standard Error: 5 + .saturating_add(Weight::from_parts(1_634, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn v10_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `510` + // Estimated: `6450` + // Minimum execution time: 15_971_000 picoseconds. + Weight::from_parts(16_730_000, 6450) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::DeletionQueue` (r:1 w:1025) + /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + /// Storage: `Contracts::DeletionQueueCounter` (r:0 w:1) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// The range of component `k` is `[0, 1024]`. + fn v11_migration_step(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + k * (1 ±0)` + // Estimated: `3635 + k * (1 ±0)` + // Minimum execution time: 3_149_000 picoseconds. + Weight::from_parts(3_264_000, 3635) + // Standard Error: 559 + .saturating_add(Weight::from_parts(1_111_209, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(k.into())) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:0 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn v12_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + c * (1 ±0)` + // Estimated: `6263 + c * (1 ±0)` + // Minimum execution time: 15_072_000 picoseconds. + Weight::from_parts(15_721_891, 6263) + // Standard Error: 2 + .saturating_add(Weight::from_parts(428, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn v13_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `440` + // Estimated: `6380` + // Minimum execution time: 12_047_000 picoseconds. + Weight::from_parts(12_500_000, 6380) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:2 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + fn v14_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `352` + // Estimated: `6292` + // Minimum execution time: 47_488_000 picoseconds. + Weight::from_parts(48_482_000, 6292) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn v15_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `594` + // Estimated: `6534` + // Minimum execution time: 52_801_000 picoseconds. + Weight::from_parts(54_230_000, 6534) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn v16_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `409` + // Estimated: `6349` + // Minimum execution time: 11_618_000 picoseconds. + Weight::from_parts(12_068_000, 6349) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn migration_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 2_131_000 picoseconds. + Weight::from_parts(2_255_000, 1627) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + fn migrate() -> Weight { + // Proof Size summary in bytes: + // Measured: `166` + // Estimated: `3631` + // Minimum execution time: 10_773_000 picoseconds. + Weight::from_parts(11_118_000, 3631) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + fn on_runtime_upgrade_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 4_371_000 picoseconds. + Weight::from_parts(4_624_000, 3607) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn on_runtime_upgrade_in_progress() -> Weight { + // Proof Size summary in bytes: + // Measured: `167` + // Estimated: `3632` + // Minimum execution time: 5_612_000 picoseconds. + Weight::from_parts(5_838_000, 3632) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn on_runtime_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 5_487_000 picoseconds. + Weight::from_parts(5_693_000, 3607) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn call_with_code_per_byte(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `800 + c * (1 ±0)` + // Estimated: `4266 + c * (1 ±0)` + // Minimum execution time: 247_545_000 picoseconds. + Weight::from_parts(268_016_699, 4266) + // Standard Error: 4 + .saturating_add(Weight::from_parts(700, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:2 w:2) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:1) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + /// The range of component `i` is `[0, 1048576]`. + /// The range of component `s` is `[0, 1048576]`. + fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `323` + // Estimated: `6262` + // Minimum execution time: 4_396_772_000 picoseconds. + Weight::from_parts(235_107_907, 6262) + // Standard Error: 185 + .saturating_add(Weight::from_parts(53_843, 0).saturating_mul(c.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(2_143, 0).saturating_mul(i.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(2_210, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:1) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// The range of component `i` is `[0, 1048576]`. + /// The range of component `s` is `[0, 1048576]`. + fn instantiate(i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `560` + // Estimated: `4017` + // Minimum execution time: 2_240_868_000 picoseconds. + Weight::from_parts(2_273_668_000, 4017) + // Standard Error: 32 + .saturating_add(Weight::from_parts(934, 0).saturating_mul(i.into())) + // Standard Error: 32 + .saturating_add(Weight::from_parts(920, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn call() -> Weight { + // Proof Size summary in bytes: + // Measured: `826` + // Estimated: `4291` + // Minimum execution time: 165_067_000 picoseconds. + Weight::from_parts(168_582_000, 4291) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn upload_code_determinism_enforced(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 229_454_000 picoseconds. + Weight::from_parts(251_495_551, 3607) + // Standard Error: 71 + .saturating_add(Weight::from_parts(51_428, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn upload_code_determinism_relaxed(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 240_390_000 picoseconds. + Weight::from_parts(273_854_266, 3607) + // Standard Error: 243 + .saturating_add(Weight::from_parts(51_836, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn remove_code() -> Weight { + // Proof Size summary in bytes: + // Measured: `315` + // Estimated: `3780` + // Minimum execution time: 39_374_000 picoseconds. + Weight::from_parts(40_247_000, 3780) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:2 w:2) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + fn set_code() -> Weight { + // Proof Size summary in bytes: + // Measured: `552` + // Estimated: `6492` + // Minimum execution time: 24_473_000 picoseconds. + Weight::from_parts(25_890_000, 6492) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// The range of component `r` is `[0, 1600]`. + fn noop_host_fn(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_528_000 picoseconds. + Weight::from_parts(9_301_010, 0) + // Standard Error: 98 + .saturating_add(Weight::from_parts(53_173, 0).saturating_mul(r.into())) + } + fn seal_caller() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 643_000 picoseconds. + Weight::from_parts(678_000, 0) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn seal_is_contract() -> Weight { + // Proof Size summary in bytes: + // Measured: `354` + // Estimated: `3819` + // Minimum execution time: 6_107_000 picoseconds. + Weight::from_parts(6_235_000, 3819) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn seal_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `3912` + // Minimum execution time: 7_316_000 picoseconds. + Weight::from_parts(7_653_000, 3912) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + fn seal_own_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 721_000 picoseconds. + Weight::from_parts(764_000, 0) + } + fn seal_caller_is_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 369_000 picoseconds. + Weight::from_parts(417_000, 0) + } + fn seal_caller_is_root() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 318_000 picoseconds. + Weight::from_parts(349_000, 0) + } + fn seal_address() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 590_000 picoseconds. + Weight::from_parts(628_000, 0) + } + fn seal_gas_left() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(730_000, 0) + } + fn seal_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `140` + // Estimated: `0` + // Minimum execution time: 4_361_000 picoseconds. + Weight::from_parts(4_577_000, 0) + } + fn seal_value_transferred() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 560_000 picoseconds. + Weight::from_parts(603_000, 0) + } + fn seal_minimum_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 561_000 picoseconds. + Weight::from_parts(610_000, 0) + } + fn seal_block_number() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 557_000 picoseconds. + Weight::from_parts(583_000, 0) + } + fn seal_now() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 550_000 picoseconds. + Weight::from_parts(602_000, 0) + } + /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) + /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) + fn seal_weight_to_fee() -> Weight { + // Proof Size summary in bytes: + // Measured: `67` + // Estimated: `1552` + // Minimum execution time: 4_065_000 picoseconds. + Weight::from_parts(4_291_000, 1552) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// The range of component `n` is `[0, 1048572]`. + fn seal_input(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 487_000 picoseconds. + Weight::from_parts(517_000, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(301, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048572]`. + fn seal_return(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 318_000 picoseconds. + Weight::from_parts(372_000, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(411, 0).saturating_mul(n.into())) + } + /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:1) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:33 w:33) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::DeletionQueue` (r:0 w:1) + /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + /// The range of component `n` is `[0, 32]`. + fn seal_terminate(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `319 + n * (78 ±0)` + // Estimated: `3784 + n * (2553 ±0)` + // Minimum execution time: 13_251_000 picoseconds. + Weight::from_parts(15_257_892, 3784) + // Standard Error: 7_089 + .saturating_add(Weight::from_parts(3_443_907, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2553).saturating_mul(n.into())) + } + /// Storage: `RandomnessCollectiveFlip::RandomMaterial` (r:1 w:0) + /// Proof: `RandomnessCollectiveFlip::RandomMaterial` (`max_values`: Some(1), `max_size`: Some(2594), added: 3089, mode: `Measured`) + fn seal_random() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 3_434_000 picoseconds. + Weight::from_parts(3_605_000, 1561) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `System::EventTopics` (r:4 w:4) + /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `t` is `[0, 4]`. + /// The range of component `n` is `[0, 16384]`. + fn seal_deposit_event(t: u32, n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `990 + t * (2475 ±0)` + // Minimum execution time: 3_668_000 picoseconds. + Weight::from_parts(3_999_591, 990) + // Standard Error: 5_767 + .saturating_add(Weight::from_parts(2_011_090, 0).saturating_mul(t.into())) + // Standard Error: 1 + .saturating_add(Weight::from_parts(12, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 2475).saturating_mul(t.into())) + } + /// The range of component `i` is `[0, 1048576]`. + fn seal_debug_message(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 443_000 picoseconds. + Weight::from_parts(472_000, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(1_207, 0).saturating_mul(i.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn get_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `16618` + // Estimated: `16618` + // Minimum execution time: 13_752_000 picoseconds. + Weight::from_parts(14_356_000, 16618) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn get_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `26628` + // Estimated: `26628` + // Minimum execution time: 43_444_000 picoseconds. + Weight::from_parts(45_087_000, 26628) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `16618` + // Estimated: `16618` + // Minimum execution time: 15_616_000 picoseconds. + Weight::from_parts(16_010_000, 16618) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `26628` + // Estimated: `26628` + // Minimum execution time: 47_020_000 picoseconds. + Weight::from_parts(50_152_000, 26628) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + /// The range of component `o` is `[0, 16384]`. + fn seal_set_storage(n: u32, o: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `250 + o * (1 ±0)` + // Estimated: `249 + o * (1 ±0)` + // Minimum execution time: 8_824_000 picoseconds. + Weight::from_parts(8_915_233, 249) + // Standard Error: 1 + .saturating_add(Weight::from_parts(255, 0).saturating_mul(n.into())) + // Standard Error: 1 + .saturating_add(Weight::from_parts(39, 0).saturating_mul(o.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_clear_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 7_133_000 picoseconds. + Weight::from_parts(7_912_778, 248) + // Standard Error: 1 + .saturating_add(Weight::from_parts(88, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_get_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 6_746_000 picoseconds. + Weight::from_parts(7_647_236, 248) + // Standard Error: 2 + .saturating_add(Weight::from_parts(603, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_contains_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 6_247_000 picoseconds. + Weight::from_parts(6_952_661, 248) + // Standard Error: 1 + .saturating_add(Weight::from_parts(77, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_take_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 7_428_000 picoseconds. + Weight::from_parts(8_384_015, 248) + // Standard Error: 2 + .saturating_add(Weight::from_parts(625, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + fn set_transient_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_478_000 picoseconds. + Weight::from_parts(1_533_000, 0) + } + fn set_transient_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_485_000 picoseconds. + Weight::from_parts(2_728_000, 0) + } + fn get_transient_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_195_000 picoseconds. + Weight::from_parts(3_811_000, 0) + } + fn get_transient_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_902_000 picoseconds. + Weight::from_parts(4_118_000, 0) + } + fn rollback_transient_storage() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_571_000 picoseconds. + Weight::from_parts(1_662_000, 0) + } + /// The range of component `n` is `[0, 16384]`. + /// The range of component `o` is `[0, 16384]`. + fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_250_000 picoseconds. + Weight::from_parts(2_465_568, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(201, 0).saturating_mul(n.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(223, 0).saturating_mul(o.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_clear_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_012_000 picoseconds. + Weight::from_parts(2_288_004, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(239, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_get_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_906_000 picoseconds. + Weight::from_parts(2_121_040, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(225, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_contains_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_736_000 picoseconds. + Weight::from_parts(1_954_728, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(111, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_take_transient_storage(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_872_000 picoseconds. + Weight::from_parts(8_125_644, 0) + } + fn seal_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `140` + // Estimated: `0` + // Minimum execution time: 8_489_000 picoseconds. + Weight::from_parts(8_791_000, 0) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `t` is `[0, 1]`. + /// The range of component `i` is `[0, 1048576]`. + fn seal_call(t: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `620 + t * (280 ±0)` + // Estimated: `4085 + t * (2182 ±0)` + // Minimum execution time: 122_759_000 picoseconds. + Weight::from_parts(120_016_020, 4085) + // Standard Error: 173_118 + .saturating_add(Weight::from_parts(42_848_338, 0).saturating_mul(t.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(6, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 2182).saturating_mul(t.into())) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn seal_delegate_call() -> Weight { + // Proof Size summary in bytes: + // Measured: `430` + // Estimated: `3895` + // Minimum execution time: 111_566_000 picoseconds. + Weight::from_parts(115_083_000, 3895) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:0) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `i` is `[0, 983040]`. + /// The range of component `s` is `[0, 983040]`. + fn seal_instantiate(i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `676` + // Estimated: `4132` + // Minimum execution time: 1_871_402_000 picoseconds. + Weight::from_parts(1_890_038_000, 4132) + // Standard Error: 24 + .saturating_add(Weight::from_parts(581, 0).saturating_mul(i.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(915, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_sha2_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 966_000 picoseconds. + Weight::from_parts(9_599_151, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_336, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_keccak_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_416_000 picoseconds. + Weight::from_parts(10_964_255, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(3_593, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 821_000 picoseconds. + Weight::from_parts(6_579_283, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_466, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_128(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 773_000 picoseconds. + Weight::from_parts(10_990_209, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_457, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 125697]`. + fn seal_sr25519_verify(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 43_195_000 picoseconds. + Weight::from_parts(41_864_855, 0) + // Standard Error: 9 + .saturating_add(Weight::from_parts(5_154, 0).saturating_mul(n.into())) + } + fn seal_ecdsa_recover() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 47_747_000 picoseconds. + Weight::from_parts(49_219_000, 0) + } + fn seal_ecdsa_to_eth_address() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 12_854_000 picoseconds. + Weight::from_parts(12_962_000, 0) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn seal_set_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `430` + // Estimated: `3895` + // Minimum execution time: 17_868_000 picoseconds. + Weight::from_parts(18_486_000, 3895) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + fn lock_delegate_dependency() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3820` + // Minimum execution time: 8_393_000 picoseconds. + Weight::from_parts(8_640_000, 3820) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `MaxEncodedLen`) + fn unlock_delegate_dependency() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3558` + // Minimum execution time: 7_489_000 picoseconds. + Weight::from_parts(7_815_000, 3558) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + fn seal_reentrance_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 299_000 picoseconds. + Weight::from_parts(339_000, 0) + } + fn seal_account_reentrance_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 324_000 picoseconds. + Weight::from_parts(380_000, 0) + } + /// Storage: `Contracts::Nonce` (r:1 w:0) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + fn seal_instantiation_nonce() -> Weight { + // Proof Size summary in bytes: + // Measured: `219` + // Estimated: `1704` + // Minimum execution time: 2_768_000 picoseconds. + Weight::from_parts(3_025_000, 1704) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// The range of component `r` is `[0, 5000]`. + fn instr_i64_load_store(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 766_000 picoseconds. + Weight::from_parts(722_169, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(7_191, 0).saturating_mul(r.into())) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:0) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + fn on_process_deletion_queue_batch() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 1_915_000 picoseconds. + Weight::from_parts(1_986_000, 1627) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `k` is `[0, 1024]`. + fn on_initialize_per_trie_key(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `452 + k * (69 ±0)` + // Estimated: `442 + k * (70 ±0)` + // Minimum execution time: 11_103_000 picoseconds. + Weight::from_parts(11_326_000, 442) + // Standard Error: 2_291 + .saturating_add(Weight::from_parts(1_196_329, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// The range of component `c` is `[0, 125952]`. + fn v9_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `211 + c * (1 ±0)` + // Estimated: `6149 + c * (1 ±0)` + // Minimum execution time: 7_783_000 picoseconds. + Weight::from_parts(4_462_075, 6149) + // Standard Error: 5 + .saturating_add(Weight::from_parts(1_634, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn v10_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `510` + // Estimated: `6450` + // Minimum execution time: 15_971_000 picoseconds. + Weight::from_parts(16_730_000, 6450) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::DeletionQueue` (r:1 w:1025) + /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + /// Storage: `Contracts::DeletionQueueCounter` (r:0 w:1) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// The range of component `k` is `[0, 1024]`. + fn v11_migration_step(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + k * (1 ±0)` + // Estimated: `3635 + k * (1 ±0)` + // Minimum execution time: 3_149_000 picoseconds. + Weight::from_parts(3_264_000, 3635) + // Standard Error: 559 + .saturating_add(Weight::from_parts(1_111_209, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(k.into())) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:0 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn v12_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + c * (1 ±0)` + // Estimated: `6263 + c * (1 ±0)` + // Minimum execution time: 15_072_000 picoseconds. + Weight::from_parts(15_721_891, 6263) + // Standard Error: 2 + .saturating_add(Weight::from_parts(428, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn v13_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `440` + // Estimated: `6380` + // Minimum execution time: 12_047_000 picoseconds. + Weight::from_parts(12_500_000, 6380) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:2 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + fn v14_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `352` + // Estimated: `6292` + // Minimum execution time: 47_488_000 picoseconds. + Weight::from_parts(48_482_000, 6292) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn v15_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `594` + // Estimated: `6534` + // Minimum execution time: 52_801_000 picoseconds. + Weight::from_parts(54_230_000, 6534) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:2 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn v16_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `409` + // Estimated: `6349` + // Minimum execution time: 11_618_000 picoseconds. + Weight::from_parts(12_068_000, 6349) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn migration_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 2_131_000 picoseconds. + Weight::from_parts(2_255_000, 1627) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + fn migrate() -> Weight { + // Proof Size summary in bytes: + // Measured: `166` + // Estimated: `3631` + // Minimum execution time: 10_773_000 picoseconds. + Weight::from_parts(11_118_000, 3631) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + fn on_runtime_upgrade_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 4_371_000 picoseconds. + Weight::from_parts(4_624_000, 3607) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn on_runtime_upgrade_in_progress() -> Weight { + // Proof Size summary in bytes: + // Measured: `167` + // Estimated: `3632` + // Minimum execution time: 5_612_000 picoseconds. + Weight::from_parts(5_838_000, 3632) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: `Contracts::MigrationInProgress` (r:1 w:1) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + fn on_runtime_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 5_487_000 picoseconds. + Weight::from_parts(5_693_000, 3607) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn call_with_code_per_byte(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `800 + c * (1 ±0)` + // Estimated: `4266 + c * (1 ±0)` + // Minimum execution time: 247_545_000 picoseconds. + Weight::from_parts(268_016_699, 4266) + // Standard Error: 4 + .saturating_add(Weight::from_parts(700, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:2 w:2) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:1) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + /// The range of component `i` is `[0, 1048576]`. + /// The range of component `s` is `[0, 1048576]`. + fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `323` + // Estimated: `6262` + // Minimum execution time: 4_396_772_000 picoseconds. + Weight::from_parts(235_107_907, 6262) + // Standard Error: 185 + .saturating_add(Weight::from_parts(53_843, 0).saturating_mul(c.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(2_143, 0).saturating_mul(i.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(2_210, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:1) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// The range of component `i` is `[0, 1048576]`. + /// The range of component `s` is `[0, 1048576]`. + fn instantiate(i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `560` + // Estimated: `4017` + // Minimum execution time: 2_240_868_000 picoseconds. + Weight::from_parts(2_273_668_000, 4017) + // Standard Error: 32 + .saturating_add(Weight::from_parts(934, 0).saturating_mul(i.into())) + // Standard Error: 32 + .saturating_add(Weight::from_parts(920, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + fn call() -> Weight { + // Proof Size summary in bytes: + // Measured: `826` + // Estimated: `4291` + // Minimum execution time: 165_067_000 picoseconds. + Weight::from_parts(168_582_000, 4291) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn upload_code_determinism_enforced(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 229_454_000 picoseconds. + Weight::from_parts(251_495_551, 3607) + // Standard Error: 71 + .saturating_add(Weight::from_parts(51_428, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// The range of component `c` is `[0, 125952]`. + fn upload_code_determinism_relaxed(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 240_390_000 picoseconds. + Weight::from_parts(273_854_266, 3607) + // Standard Error: 243 + .saturating_add(Weight::from_parts(51_836, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:0 w:1) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn remove_code() -> Weight { + // Proof Size summary in bytes: + // Measured: `315` + // Estimated: `3780` + // Minimum execution time: 39_374_000 picoseconds. + Weight::from_parts(40_247_000, 3780) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) + /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:2 w:2) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + fn set_code() -> Weight { + // Proof Size summary in bytes: + // Measured: `552` + // Estimated: `6492` + // Minimum execution time: 24_473_000 picoseconds. + Weight::from_parts(25_890_000, 6492) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// The range of component `r` is `[0, 1600]`. + fn noop_host_fn(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_528_000 picoseconds. + Weight::from_parts(9_301_010, 0) + // Standard Error: 98 + .saturating_add(Weight::from_parts(53_173, 0).saturating_mul(r.into())) + } + fn seal_caller() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 643_000 picoseconds. + Weight::from_parts(678_000, 0) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn seal_is_contract() -> Weight { + // Proof Size summary in bytes: + // Measured: `354` + // Estimated: `3819` + // Minimum execution time: 6_107_000 picoseconds. + Weight::from_parts(6_235_000, 3819) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + fn seal_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `3912` + // Minimum execution time: 7_316_000 picoseconds. + Weight::from_parts(7_653_000, 3912) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + fn seal_own_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 721_000 picoseconds. + Weight::from_parts(764_000, 0) + } + fn seal_caller_is_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 369_000 picoseconds. + Weight::from_parts(417_000, 0) + } + fn seal_caller_is_root() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 318_000 picoseconds. + Weight::from_parts(349_000, 0) + } + fn seal_address() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 590_000 picoseconds. + Weight::from_parts(628_000, 0) + } + fn seal_gas_left() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(730_000, 0) + } + fn seal_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `140` + // Estimated: `0` + // Minimum execution time: 4_361_000 picoseconds. + Weight::from_parts(4_577_000, 0) + } + fn seal_value_transferred() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 560_000 picoseconds. + Weight::from_parts(603_000, 0) + } + fn seal_minimum_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 561_000 picoseconds. + Weight::from_parts(610_000, 0) + } + fn seal_block_number() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 557_000 picoseconds. + Weight::from_parts(583_000, 0) + } + fn seal_now() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 550_000 picoseconds. + Weight::from_parts(602_000, 0) + } + /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) + /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) + fn seal_weight_to_fee() -> Weight { + // Proof Size summary in bytes: + // Measured: `67` + // Estimated: `1552` + // Minimum execution time: 4_065_000 picoseconds. + Weight::from_parts(4_291_000, 1552) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// The range of component `n` is `[0, 1048572]`. + fn seal_input(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 487_000 picoseconds. + Weight::from_parts(517_000, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(301, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048572]`. + fn seal_return(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 318_000 picoseconds. + Weight::from_parts(372_000, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(411, 0).saturating_mul(n.into())) + } + /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:1) + /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:33 w:33) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::DeletionQueue` (r:0 w:1) + /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + /// The range of component `n` is `[0, 32]`. + fn seal_terminate(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `319 + n * (78 ±0)` + // Estimated: `3784 + n * (2553 ±0)` + // Minimum execution time: 13_251_000 picoseconds. + Weight::from_parts(15_257_892, 3784) + // Standard Error: 7_089 + .saturating_add(Weight::from_parts(3_443_907, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2553).saturating_mul(n.into())) + } + /// Storage: `RandomnessCollectiveFlip::RandomMaterial` (r:1 w:0) + /// Proof: `RandomnessCollectiveFlip::RandomMaterial` (`max_values`: Some(1), `max_size`: Some(2594), added: 3089, mode: `Measured`) + fn seal_random() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 3_434_000 picoseconds. + Weight::from_parts(3_605_000, 1561) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `System::EventTopics` (r:4 w:4) + /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `t` is `[0, 4]`. + /// The range of component `n` is `[0, 16384]`. + fn seal_deposit_event(t: u32, n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `990 + t * (2475 ±0)` + // Minimum execution time: 3_668_000 picoseconds. + Weight::from_parts(3_999_591, 990) + // Standard Error: 5_767 + .saturating_add(Weight::from_parts(2_011_090, 0).saturating_mul(t.into())) + // Standard Error: 1 + .saturating_add(Weight::from_parts(12, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 2475).saturating_mul(t.into())) + } + /// The range of component `i` is `[0, 1048576]`. + fn seal_debug_message(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 443_000 picoseconds. + Weight::from_parts(472_000, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(1_207, 0).saturating_mul(i.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn get_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `16618` + // Estimated: `16618` + // Minimum execution time: 13_752_000 picoseconds. + Weight::from_parts(14_356_000, 16618) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn get_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `26628` + // Estimated: `26628` + // Minimum execution time: 43_444_000 picoseconds. + Weight::from_parts(45_087_000, 26628) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `16618` + // Estimated: `16618` + // Minimum execution time: 15_616_000 picoseconds. + Weight::from_parts(16_010_000, 16618) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn set_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `26628` + // Estimated: `26628` + // Minimum execution time: 47_020_000 picoseconds. + Weight::from_parts(50_152_000, 26628) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + /// The range of component `o` is `[0, 16384]`. + fn seal_set_storage(n: u32, o: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `250 + o * (1 ±0)` + // Estimated: `249 + o * (1 ±0)` + // Minimum execution time: 8_824_000 picoseconds. + Weight::from_parts(8_915_233, 249) + // Standard Error: 1 + .saturating_add(Weight::from_parts(255, 0).saturating_mul(n.into())) + // Standard Error: 1 + .saturating_add(Weight::from_parts(39, 0).saturating_mul(o.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_clear_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 7_133_000 picoseconds. + Weight::from_parts(7_912_778, 248) + // Standard Error: 1 + .saturating_add(Weight::from_parts(88, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_get_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 6_746_000 picoseconds. + Weight::from_parts(7_647_236, 248) + // Standard Error: 2 + .saturating_add(Weight::from_parts(603, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_contains_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 6_247_000 picoseconds. + Weight::from_parts(6_952_661, 248) + // Standard Error: 1 + .saturating_add(Weight::from_parts(77, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 16384]`. + fn seal_take_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `248 + n * (1 ±0)` + // Estimated: `248 + n * (1 ±0)` + // Minimum execution time: 7_428_000 picoseconds. + Weight::from_parts(8_384_015, 248) + // Standard Error: 2 + .saturating_add(Weight::from_parts(625, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + fn set_transient_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_478_000 picoseconds. + Weight::from_parts(1_533_000, 0) + } + fn set_transient_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_485_000 picoseconds. + Weight::from_parts(2_728_000, 0) + } + fn get_transient_storage_empty() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_195_000 picoseconds. + Weight::from_parts(3_811_000, 0) + } + fn get_transient_storage_full() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_902_000 picoseconds. + Weight::from_parts(4_118_000, 0) + } + fn rollback_transient_storage() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_571_000 picoseconds. + Weight::from_parts(1_662_000, 0) + } + /// The range of component `n` is `[0, 16384]`. + /// The range of component `o` is `[0, 16384]`. + fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_250_000 picoseconds. + Weight::from_parts(2_465_568, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(201, 0).saturating_mul(n.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(223, 0).saturating_mul(o.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_clear_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_012_000 picoseconds. + Weight::from_parts(2_288_004, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(239, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_get_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_906_000 picoseconds. + Weight::from_parts(2_121_040, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(225, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_contains_transient_storage(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_736_000 picoseconds. + Weight::from_parts(1_954_728, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(111, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 16384]`. + fn seal_take_transient_storage(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_872_000 picoseconds. + Weight::from_parts(8_125_644, 0) + } + fn seal_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `140` + // Estimated: `0` + // Minimum execution time: 8_489_000 picoseconds. + Weight::from_parts(8_791_000, 0) + } + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `t` is `[0, 1]`. + /// The range of component `i` is `[0, 1048576]`. + fn seal_call(t: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `620 + t * (280 ±0)` + // Estimated: `4085 + t * (2182 ±0)` + // Minimum execution time: 122_759_000 picoseconds. + Weight::from_parts(120_016_020, 4085) + // Standard Error: 173_118 + .saturating_add(Weight::from_parts(42_848_338, 0).saturating_mul(t.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(6, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 2182).saturating_mul(t.into())) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn seal_delegate_call() -> Weight { + // Proof Size summary in bytes: + // Measured: `430` + // Estimated: `3895` + // Minimum execution time: 111_566_000 picoseconds. + Weight::from_parts(115_083_000, 3895) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Contracts::Nonce` (r:1 w:0) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) + /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// The range of component `i` is `[0, 983040]`. + /// The range of component `s` is `[0, 983040]`. + fn seal_instantiate(i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `676` + // Estimated: `4132` + // Minimum execution time: 1_871_402_000 picoseconds. + Weight::from_parts(1_890_038_000, 4132) + // Standard Error: 24 + .saturating_add(Weight::from_parts(581, 0).saturating_mul(i.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(915, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_sha2_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 966_000 picoseconds. + Weight::from_parts(9_599_151, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_336, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_keccak_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_416_000 picoseconds. + Weight::from_parts(10_964_255, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(3_593, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_256(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 821_000 picoseconds. + Weight::from_parts(6_579_283, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_466, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_128(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 773_000 picoseconds. + Weight::from_parts(10_990_209, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_457, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 125697]`. + fn seal_sr25519_verify(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 43_195_000 picoseconds. + Weight::from_parts(41_864_855, 0) + // Standard Error: 9 + .saturating_add(Weight::from_parts(5_154, 0).saturating_mul(n.into())) + } + fn seal_ecdsa_recover() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 47_747_000 picoseconds. + Weight::from_parts(49_219_000, 0) + } + fn seal_ecdsa_to_eth_address() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 12_854_000 picoseconds. + Weight::from_parts(12_962_000, 0) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Contracts::PristineCode` (r:1 w:0) + /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + fn seal_set_code_hash() -> Weight { + // Proof Size summary in bytes: + // Measured: `430` + // Estimated: `3895` + // Minimum execution time: 17_868_000 picoseconds. + Weight::from_parts(18_486_000, 3895) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + fn lock_delegate_dependency() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3820` + // Minimum execution time: 8_393_000 picoseconds. + Weight::from_parts(8_640_000, 3820) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) + /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `MaxEncodedLen`) + fn unlock_delegate_dependency() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3558` + // Minimum execution time: 7_489_000 picoseconds. + Weight::from_parts(7_815_000, 3558) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + fn seal_reentrance_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 299_000 picoseconds. + Weight::from_parts(339_000, 0) + } + fn seal_account_reentrance_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 324_000 picoseconds. + Weight::from_parts(380_000, 0) + } + /// Storage: `Contracts::Nonce` (r:1 w:0) + /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + fn seal_instantiation_nonce() -> Weight { + // Proof Size summary in bytes: + // Measured: `219` + // Estimated: `1704` + // Minimum execution time: 2_768_000 picoseconds. + Weight::from_parts(3_025_000, 1704) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// The range of component `r` is `[0, 5000]`. + fn instr_i64_load_store(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 766_000 picoseconds. + Weight::from_parts(722_169, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(7_191, 0).saturating_mul(r.into())) + } +} diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml new file mode 100644 index 000000000000..862bf36f07cd --- /dev/null +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "pallet-revive-uapi" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "Exposes all the host functions that a contract can import." + +[lints] +workspace = true + +[dependencies] +paste = { workspace = true } +bitflags = { workspace = true } +scale-info = { features = ["derive"], optional = true, workspace = true } +codec = { features = [ + "derive", + "max-encoded-len", +], optional = true, workspace = true } + +[target.'cfg(target_arch = "riscv32")'.dependencies] +polkavm-derive = { version = "0.10.0" } + +[package.metadata.docs.rs] +default-target = ["wasm32-unknown-unknown"] + +[features] +default = ["scale"] +scale = ["dep:codec", "scale-info"] diff --git a/substrate/frame/revive/uapi/src/flags.rs b/substrate/frame/revive/uapi/src/flags.rs new file mode 100644 index 000000000000..17b91ce2b3be --- /dev/null +++ b/substrate/frame/revive/uapi/src/flags.rs @@ -0,0 +1,89 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use bitflags::bitflags; + +bitflags! { + /// Flags used by a contract to customize exit behaviour. + #[cfg_attr(feature = "scale", derive(codec::Encode, codec::Decode, scale_info::TypeInfo))] + pub struct ReturnFlags: u32 { + /// If this bit is set all changes made by the contract execution are rolled back. + const REVERT = 0x0000_0001; + } +} + +bitflags! { + /// Flags used to change the behaviour of `seal_call` and `seal_delegate_call`. + pub struct CallFlags: u32 { + /// Forward the input of current function to the callee. + /// + /// Supplied input pointers are ignored when set. + /// + /// # Note + /// + /// A forwarding call will consume the current contracts input. Any attempt to + /// access the input after this call returns will lead to [`Error::InputForwarded`]. + /// It does not matter if this is due to calling `seal_input` or trying another + /// forwarding call. Consider using [`Self::CLONE_INPUT`] in order to preserve + /// the input. + const FORWARD_INPUT = 0b0000_0001; + /// Identical to [`Self::FORWARD_INPUT`] but without consuming the input. + /// + /// This adds some additional weight costs to the call. + /// + /// # Note + /// + /// This implies [`Self::FORWARD_INPUT`] and takes precedence when both are set. + const CLONE_INPUT = 0b0000_0010; + /// Do not return from the call but rather return the result of the callee to the + /// callers caller. + /// + /// # Note + /// + /// This makes the current contract completely transparent to its caller by replacing + /// this contracts potential output by the callee ones. Any code after `seal_call` + /// can be safely considered unreachable. + const TAIL_CALL = 0b0000_0100; + /// Allow the callee to reenter into the current contract. + /// + /// Without this flag any reentrancy into the current contract that originates from + /// the callee (or any of its callees) is denied. This includes the first callee: + /// You cannot call into yourself with this flag set. + /// + /// # Note + /// + /// For `seal_delegate_call` should be always unset, otherwise + /// [`Error::InvalidCallFlags`] is returned. + const ALLOW_REENTRY = 0b0000_1000; + /// Indicates that the callee is restricted from modifying the state during call execution, + /// equivalent to Ethereum's STATICCALL. + /// + /// # Note + /// + /// For `seal_delegate_call` should be always unset, otherwise + /// [`Error::InvalidCallFlags`] is returned. + const READ_ONLY = 0b0001_0000; + } +} + +bitflags! { + /// Flags used by a contract to customize storage behaviour. + pub struct StorageFlags: u32 { + /// Access the transient storage instead of the persistent one. + const TRANSIENT = 0x0000_0001; + } +} diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs new file mode 100644 index 000000000000..6eb662363f7c --- /dev/null +++ b/substrate/frame/revive/uapi/src/host.rs @@ -0,0 +1,643 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::{CallFlags, Result, ReturnFlags, StorageFlags}; +use paste::paste; + +#[cfg(target_arch = "riscv32")] +mod riscv32; + +macro_rules! hash_fn { + ( $name:ident, $bytes:literal ) => { + paste! { + #[doc = "Computes the " $name " " $bytes "-bit hash on the given input buffer."] + #[doc = "\n# Notes\n"] + #[doc = "- The `input` and `output` buffer may overlap."] + #[doc = "- The output buffer is expected to hold at least " $bytes " bits."] + #[doc = "- It is the callers responsibility to provide an output buffer that is large enough to hold the expected amount of bytes returned by the hash function."] + #[doc = "\n# Parameters\n"] + #[doc = "- `input`: The input data buffer."] + #[doc = "- `output`: The output buffer to write the hash result to."] + fn [](input: &[u8], output: &mut [u8; $bytes]); + } + }; +} + +/// Implements [`HostFn`] when compiled on supported architectures (RISC-V). +pub enum HostFnImpl {} + +/// Defines all the host apis available to contracts. +pub trait HostFn: private::Sealed { + /// Stores the address of the current contract into the supplied buffer. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the address. + fn address(output: &mut &mut [u8]); + + /// Lock a new delegate dependency to the contract. + /// + /// Traps if the maximum number of delegate_dependencies is reached or if + /// the delegate dependency already exists. + /// + /// # Parameters + /// + /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps + /// otherwise. + fn lock_delegate_dependency(code_hash: &[u8]); + + /// Stores the *free* balance of the current account into the supplied buffer. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the balance. + fn balance(output: &mut &mut [u8]); + + /// Stores the current block number of the current contract into the supplied buffer. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the block number. + fn block_number(output: &mut &mut [u8]); + + /// Call (possibly transferring some amount of funds) into the specified account. + /// + /// # Parameters + /// + /// - `flags`: See [`CallFlags`] for a documentation of the supported flags. + /// - `callee`: The address of the callee. Should be decodable as an `T::AccountId`. Traps + /// otherwise. + /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. + /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. + /// - `deposit`: The storage deposit limit for instantiation. Should be decodable as a + /// `Option`. Traps otherwise. Passing `None` means setting no specific limit for + /// the call, which implies storage usage up to the limit of the parent call. + /// - `value`: The value to transfer into the contract. Should be decodable as a `T::Balance`. + /// Traps otherwise. + /// - `input`: The input data buffer used to call the contract. + /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` + /// is provided then the output buffer is not copied. + /// + /// # Errors + /// + /// An error means that the call wasn't successful output buffer is returned unless + /// stated otherwise. + /// + /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. + /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] + /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] + /// - [NotCallable][`crate::ReturnErrorCode::NotCallable] + fn call( + flags: CallFlags, + callee: &[u8], + ref_time_limit: u64, + proof_size_limit: u64, + deposit: Option<&[u8]>, + value: &[u8], + input_data: &[u8], + output: Option<&mut &mut [u8]>, + ) -> Result; + + /// Call into the chain extension provided by the chain if any. + /// + /// Handling of the input values is up to the specific chain extension and so is the + /// return value. The extension can decide to use the inputs as primitive inputs or as + /// in/out arguments by interpreting them as pointers. Any caller of this function + /// must therefore coordinate with the chain that it targets. + /// + /// # Note + /// + /// If no chain extension exists the contract will trap with the `NoChainExtension` + /// module error. + /// + /// # Parameters + /// + /// - `func_id`: The function id of the chain extension. + /// - `input`: The input data buffer. + /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` + /// is provided then the output buffer is not copied. + /// + /// # Return + /// + /// The chain extension returned value, if executed successfully. + fn call_chain_extension(func_id: u32, input: &[u8], output: Option<&mut &mut [u8]>) -> u32; + + /// Call some dispatchable of the runtime. + /// + /// # Parameters + /// + /// - `call`: The call data. + /// + /// # Return + /// + /// Returns `Error::Success` when the dispatchable was successfully executed and + /// returned `Ok`. When the dispatchable was executed but returned an error + /// `Error::CallRuntimeFailed` is returned. The full error is not + /// provided because it is not guaranteed to be stable. + /// + /// # Comparison with `ChainExtension` + /// + /// Just as a chain extension this API allows the runtime to extend the functionality + /// of contracts. While making use of this function is generally easier it cannot be + /// used in all cases. Consider writing a chain extension if you need to do perform + /// one of the following tasks: + /// + /// - Return data. + /// - Provide functionality **exclusively** to contracts. + /// - Provide custom weights. + /// - Avoid the need to keep the `Call` data structure stable. + fn call_runtime(call: &[u8]) -> Result; + + /// Stores the address of the caller into the supplied buffer. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the + /// extrinsic will be returned. Otherwise, if this call is initiated by another contract then + /// the address of the contract will be returned. + /// + /// If there is no address associated with the caller (e.g. because the caller is root) then + /// it traps with `BadOrigin`. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the caller address. + fn caller(output: &mut &mut [u8]); + + /// Checks whether the caller of the current contract is the origin of the whole call stack. + /// + /// Prefer this over [`is_contract()`][`Self::is_contract`] when checking whether your contract + /// is being called by a contract or a plain account. The reason is that it performs better + /// since it does not need to do any storage lookups. + /// + /// # Return + /// + /// A return value of `true` indicates that this contract is being called by a plain account + /// and `false` indicates that the caller is another contract. + fn caller_is_origin() -> bool; + + /// Checks whether the caller of the current contract is root. + /// + /// Note that only the origin of the call stack can be root. Hence this function returning + /// `true` implies that the contract is being called by the origin. + /// + /// A return value of `true` indicates that this contract is being called by a root origin, + /// and `false` indicates that the caller is a signed origin. + fn caller_is_root() -> u32; + + /// Clear the value at the given key in the contract storage. + /// + /// # Parameters + /// + /// - `key`: The storage key. + /// + /// # Return + /// + /// Returns the size of the pre-existing value at the specified key if any. + fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option; + + /// Retrieve the code hash for a specified contract address. + /// + /// # Parameters + /// + /// - `account_id`: The address of the contract.Should be decodable as an `T::AccountId`. Traps + /// otherwise. + /// - `output`: A reference to the output data buffer to write the code hash. + /// + /// + /// # Errors + /// + /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + fn code_hash(account_id: &[u8], output: &mut [u8]) -> Result; + + /// Checks whether there is a value stored under the given key. + /// + /// The key length must not exceed the maximum defined by the contracts module parameter. + /// + /// # Parameters + /// - `key`: The storage key. + /// + /// # Return + /// + /// Returns the size of the pre-existing value at the specified key if any. + fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option; + + /// Emit a custom debug message. + /// + /// No newlines are added to the supplied message. + /// Specifying invalid UTF-8 just drops the message with no trap. + /// + /// This is a no-op if debug message recording is disabled which is always the case + /// when the code is executing on-chain. The message is interpreted as UTF-8 and + /// appended to the debug buffer which is then supplied to the calling RPC client. + /// + /// # Note + /// + /// Even though no action is taken when debug message recording is disabled there is still + /// a non trivial overhead (and weight cost) associated with calling this function. Contract + /// languages should remove calls to this function (either at runtime or compile time) when + /// not being executed as an RPC. For example, they could allow users to disable logging + /// through compile time flags (cargo features) for on-chain deployment. Additionally, the + /// return value of this function can be cached in order to prevent further calls at runtime. + fn debug_message(str: &[u8]) -> Result; + + /// Execute code in the context (storage, caller, value) of the current contract. + /// + /// Reentrancy protection is always disabled since the callee is allowed + /// to modify the callers storage. This makes going through a reentrancy attack + /// unnecessary for the callee when it wants to exploit the caller. + /// + /// # Parameters + /// + /// - `flags`: See [`CallFlags`] for a documentation of the supported flags. + /// - `code_hash`: The hash of the code to be executed. + /// - `input`: The input data buffer used to call the contract. + /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` + /// is provided then the output buffer is not copied. + /// + /// # Errors + /// + /// An error means that the call wasn't successful and no output buffer is returned unless + /// stated otherwise. + /// + /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. + /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] + /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + fn delegate_call( + flags: CallFlags, + code_hash: &[u8], + input_data: &[u8], + output: Option<&mut &mut [u8]>, + ) -> Result; + + /// Deposit a contract event with the data buffer and optional list of topics. There is a limit + /// on the maximum number of topics specified by `event_topics`. + /// + /// There should not be any duplicates in `topics`. + /// + /// # Parameters + /// + /// - `topics`: The topics list encoded as `Vec`. It can't contain duplicates. + fn deposit_event(topics: &[u8], data: &[u8]); + + /// Recovers the ECDSA public key from the given message hash and signature. + /// + /// Writes the public key into the given output buffer. + /// Assumes the secp256k1 curve. + /// + /// # Parameters + /// + /// - `signature`: The signature bytes. + /// - `message_hash`: The message hash bytes. + /// - `output`: A reference to the output data buffer to write the public key. + /// + /// # Errors + /// + /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] + fn ecdsa_recover( + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], + ) -> Result; + + /// Calculates Ethereum address from the ECDSA compressed public key and stores + /// it into the supplied buffer. + /// + /// # Parameters + /// + /// - `pubkey`: The public key bytes. + /// - `output`: A reference to the output data buffer to write the address. + /// + /// # Errors + /// + /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] + fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result; + + /// Stores the amount of weight left into the supplied buffer. + /// The data is encoded as Weight. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the weight left. + fn weight_left(output: &mut &mut [u8]); + + /// Retrieve the value under the given key from storage. + /// + /// The key length must not exceed the maximum defined by the contracts module parameter. + /// + /// # Parameters + /// - `key`: The storage key. + /// - `output`: A reference to the output data buffer to write the storage entry. + /// + /// # Errors + /// + /// [KeyNotFound][`crate::ReturnErrorCode::KeyNotFound] + fn get_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result; + + hash_fn!(sha2_256, 32); + hash_fn!(keccak_256, 32); + hash_fn!(blake2_256, 32); + hash_fn!(blake2_128, 16); + + /// Stores the input passed by the caller into the supplied buffer. + /// + /// # Note + /// + /// This function traps if: + /// - the input is larger than the available space. + /// - the input was previously forwarded by a [`call()`][`Self::call()`]. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the input data. + fn input(output: &mut &mut [u8]); + + /// Instantiate a contract with the specified code hash. + /// + /// This function creates an account and executes the constructor defined in the code specified + /// by the code hash. + /// + /// # Parameters + /// + /// - `code_hash`: The hash of the code to be instantiated. + /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. + /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. + /// - `deposit`: The storage deposit limit for instantiation. Should be decodable as a + /// `Option`. Traps otherwise. Passing `None` means setting no specific limit for + /// the call, which implies storage usage up to the limit of the parent call. + /// - `value`: The value to transfer into the contract. Should be decodable as a `T::Balance`. + /// Traps otherwise. + /// - `input`: The input data buffer. + /// - `address`: A reference to the address buffer to write the address of the contract. If + /// `None` is provided then the output buffer is not copied. + /// - `output`: A reference to the return value buffer to write the constructor output buffer. + /// If `None` is provided then the output buffer is not copied. + /// - `salt`: The salt bytes to use for this instantiation. + /// + /// # Errors + /// + /// Please consult the [ReturnErrorCode][`crate::ReturnErrorCode`] enum declaration for more + /// information on those errors. Here we only note things specific to this function. + /// + /// An error means that the account wasn't created and no address or output buffer + /// is returned unless stated otherwise. + /// + /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. + /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] + /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] + /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + fn instantiate( + code_hash: &[u8], + ref_time_limit: u64, + proof_size_limit: u64, + deposit: Option<&[u8]>, + value: &[u8], + input: &[u8], + address: Option<&mut &mut [u8]>, + output: Option<&mut &mut [u8]>, + salt: &[u8], + ) -> Result; + + /// Checks whether a specified address belongs to a contract. + /// + /// # Parameters + /// + /// - `account_id`: The address to check. Should be decodable as an `T::AccountId`. Traps + /// otherwise. + /// + /// # Return + /// + /// Returns `true` if the address belongs to a contract. + fn is_contract(account_id: &[u8]) -> bool; + + /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// The data is encoded as `T::Balance`. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the minimum balance. + fn minimum_balance(output: &mut &mut [u8]); + + /// Retrieve the code hash of the currently executing contract. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the code hash. + fn own_code_hash(output: &mut [u8]); + + /// Load the latest block timestamp into the supplied buffer + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the timestamp. + fn now(output: &mut &mut [u8]); + + /// Removes the delegate dependency from the contract. + /// + /// Traps if the delegate dependency does not exist. + /// + /// # Parameters + /// + /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps + /// otherwise. + fn unlock_delegate_dependency(code_hash: &[u8]); + + /// Cease contract execution and save a data buffer as a result of the execution. + /// + /// This function never returns as it stops execution of the caller. + /// This is the only way to return a data buffer to the caller. Returning from + /// execution without calling this function is equivalent to calling: + /// ```nocompile + /// return_value(ReturnFlags::empty(), &[]) + /// ``` + /// + /// Using an unnamed non empty `ReturnFlags` triggers a trap. + /// + /// # Parameters + /// + /// - `flags`: Flag used to signal special return conditions to the supervisor. See + /// [`ReturnFlags`] for a documentation of the supported flags. + /// - `return_value`: The return value buffer. + fn return_value(flags: ReturnFlags, return_value: &[u8]) -> !; + + /// Replace the contract code at the specified address with new code. + /// + /// # Note + /// + /// There are a couple of important considerations which must be taken into account when + /// using this API: + /// + /// 1. The storage at the code address will remain untouched. This means that contract + /// developers must ensure that the storage layout of the new code is compatible with that of + /// the old code. + /// + /// 2. Contracts using this API can't be assumed as having deterministic addresses. Said another + /// way, when using this API you lose the guarantee that an address always identifies a specific + /// code hash. + /// + /// 3. If a contract calls into itself after changing its code the new call would use + /// the new code. However, if the original caller panics after returning from the sub call it + /// would revert the changes made by [`set_code_hash()`][`Self::set_code_hash`] and the next + /// caller would use the old code. + /// + /// # Parameters + /// + /// - `code_hash`: The hash of the new code. Should be decodable as an `T::Hash`. Traps + /// otherwise. + /// + /// # Errors + /// + /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + fn set_code_hash(code_hash: &[u8]) -> Result; + + /// Set the value at the given key in the contract storage. + /// + /// The key and value lengths must not exceed the maximums defined by the contracts module + /// parameters. + /// + /// # Parameters + /// + /// - `key`: The storage key. + /// - `encoded_value`: The storage value. + /// + /// # Return + /// + /// Returns the size of the pre-existing value at the specified key if any. + fn set_storage(flags: StorageFlags, key: &[u8], value: &[u8]) -> Option; + + /// Verify a sr25519 signature + /// + /// # Parameters + /// + /// - `signature`: The signature bytes. + /// - `message`: The message bytes. + /// + /// # Errors + /// + /// - [Sr25519VerifyFailed][`crate::ReturnErrorCode::Sr25519VerifyFailed] + fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result; + + /// Retrieve and remove the value under the given key from storage. + /// + /// # Parameters + /// - `key`: The storage key. + /// - `output`: A reference to the output data buffer to write the storage entry. + /// + /// # Errors + /// + /// [KeyNotFound][`crate::ReturnErrorCode::KeyNotFound] + fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result; + + /// Transfer some amount of funds into the specified account. + /// + /// # Parameters + /// + /// - `account_id`: The address of the account to transfer funds to. Should be decodable as an + /// `T::AccountId`. Traps otherwise. + /// - `value`: The value to transfer. Should be decodable as a `T::Balance`. Traps otherwise. + /// + /// # Errors + /// + /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] + fn transfer(account_id: &[u8], value: &[u8]) -> Result; + + /// Remove the calling account and transfer remaining **free** balance. + /// + /// This function never returns. Either the termination was successful and the + /// execution of the destroyed contract is halted. Or it failed during the termination + /// which is considered fatal and results in a trap + rollback. + /// + /// # Parameters + /// + /// - `beneficiary`: The address of the beneficiary account, Should be decodable as an + /// `T::AccountId`. + /// + /// # Traps + /// + /// - The contract is live i.e is already on the call stack. + /// - Failed to send the balance to the beneficiary. + /// - The deletion queue is full. + fn terminate(beneficiary: &[u8]) -> !; + + /// Stores the value transferred along with this call/instantiate into the supplied buffer. + /// The data is encoded as `T::Balance`. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the transferred value. + fn value_transferred(output: &mut &mut [u8]); + + /// Stores the price for the specified amount of gas into the supplied buffer. + /// The data is encoded as `T::Balance`. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. + /// + /// # Parameters + /// + /// - `ref_time_limit`: The *ref_time* Weight limit to query the price for. + /// - `proof_size_limit`: The *proof_size* Weight limit to query the price for. + /// - `output`: A reference to the output data buffer to write the price. + fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut &mut [u8]); + + /// Execute an XCM program locally, using the contract's address as the origin. + /// This is equivalent to dispatching `pallet_xcm::execute` through call_runtime, except that + /// the function is called directly instead of being dispatched. + /// + /// # Parameters + /// + /// - `msg`: The message, should be decodable as a [VersionedXcm](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedXcm.html), + /// traps otherwise. + /// - `output`: A reference to the output data buffer to write the [Outcome](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/v3/enum.Outcome.html) + /// + /// # Return + /// + /// Returns `Error::Success` when the XCM execution attempt is successful. When the XCM + /// execution fails, `ReturnCode::XcmExecutionFailed` is returned + fn xcm_execute(msg: &[u8]) -> Result; + + /// Send an XCM program from the contract to the specified destination. + /// This is equivalent to dispatching `pallet_xcm::send` through `call_runtime`, except that + /// the function is called directly instead of being dispatched. + /// + /// # Parameters + /// + /// - `dest`: The XCM destination, should be decodable as [VersionedLocation](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedLocation.html), + /// traps otherwise. + /// - `msg`: The message, should be decodable as a [VersionedXcm](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedXcm.html), + /// traps otherwise. + /// + /// # Return + /// + /// Returns `ReturnCode::Success` when the message was successfully sent. When the XCM + /// execution fails, `ReturnErrorCode::XcmSendFailed` is returned. + fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result; +} + +mod private { + pub trait Sealed {} + impl Sealed for super::HostFnImpl {} +} diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs new file mode 100644 index 000000000000..0b7130015f13 --- /dev/null +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -0,0 +1,561 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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(unused_variables)] + +use crate::{ + host::{CallFlags, HostFn, HostFnImpl, Result, StorageFlags}, + ReturnFlags, +}; + +mod sys { + use crate::ReturnCode; + + #[polkavm_derive::polkavm_define_abi] + mod abi {} + + impl abi::FromHost for ReturnCode { + type Regs = (u32,); + + fn from_host((a0,): Self::Regs) -> Self { + ReturnCode(a0) + } + } + + #[polkavm_derive::polkavm_import(abi = self::abi)] + extern "C" { + pub fn set_storage( + flags: u32, + key_ptr: *const u8, + key_len: u32, + value_ptr: *const u8, + value_len: u32, + ) -> ReturnCode; + pub fn clear_storage(flags: u32, key_ptr: *const u8, key_len: u32) -> ReturnCode; + pub fn get_storage( + flags: u32, + key_ptr: *const u8, + key_len: u32, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ) -> ReturnCode; + pub fn contains_storage(flags: u32, key_ptr: *const u8, key_len: u32) -> ReturnCode; + pub fn take_storage( + flags: u32, + key_ptr: *const u8, + key_len: u32, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ) -> ReturnCode; + pub fn transfer(account_ptr: *const u8, value_ptr: *const u8) -> ReturnCode; + pub fn call(ptr: *const u8) -> ReturnCode; + pub fn delegate_call( + flags: u32, + code_hash_ptr: *const u8, + input_data_ptr: *const u8, + input_data_len: u32, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ) -> ReturnCode; + pub fn instantiate(ptr: *const u8) -> ReturnCode; + pub fn terminate(beneficiary_ptr: *const u8); + pub fn input(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn seal_return(flags: u32, data_ptr: *const u8, data_len: u32); + pub fn caller(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn is_contract(account_ptr: *const u8) -> ReturnCode; + pub fn code_hash( + account_ptr: *const u8, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ) -> ReturnCode; + pub fn own_code_hash(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn caller_is_origin() -> ReturnCode; + pub fn caller_is_root() -> ReturnCode; + pub fn address(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn weight_to_fee( + ref_time: u64, + proof_size: u64, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ); + pub fn weight_left(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn balance(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn value_transferred(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn now(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn minimum_balance(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn deposit_event( + topics_ptr: *const u8, + topics_len: u32, + data_ptr: *const u8, + data_len: u32, + ); + pub fn block_number(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn hash_sha2_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); + pub fn hash_keccak_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); + pub fn hash_blake2_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); + pub fn hash_blake2_128(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); + pub fn call_chain_extension( + id: u32, + input_ptr: *const u8, + input_len: u32, + out_ptr: *mut u8, + out_len_ptr: *mut u32, + ) -> ReturnCode; + pub fn debug_message(str_ptr: *const u8, str_len: u32) -> ReturnCode; + pub fn call_runtime(call_ptr: *const u8, call_len: u32) -> ReturnCode; + pub fn ecdsa_recover( + signature_ptr: *const u8, + message_hash_ptr: *const u8, + out_ptr: *mut u8, + ) -> ReturnCode; + pub fn sr25519_verify( + signature_ptr: *const u8, + pub_key_ptr: *const u8, + message_len: u32, + message_ptr: *const u8, + ) -> ReturnCode; + pub fn set_code_hash(code_hash_ptr: *const u8) -> ReturnCode; + pub fn ecdsa_to_eth_address(key_ptr: *const u8, out_ptr: *mut u8) -> ReturnCode; + pub fn instantiation_nonce() -> u64; + pub fn lock_delegate_dependency(code_hash_ptr: *const u8); + pub fn unlock_delegate_dependency(code_hash_ptr: *const u8); + pub fn xcm_execute(msg_ptr: *const u8, msg_len: u32) -> ReturnCode; + pub fn xcm_send( + dest_ptr: *const u8, + msg_ptr: *const u8, + msg_len: u32, + out_ptr: *mut u8, + ) -> ReturnCode; + } +} + +macro_rules! impl_wrapper_for { + ( $( $name:ident, )* ) => { + $( + fn $name(output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + unsafe { + sys::$name( + output.as_mut_ptr(), + &mut output_len, + ) + } + extract_from_slice(output, output_len as usize) + } + )* + } +} + +macro_rules! impl_hash_fn { + ( $name:ident, $bytes_result:literal ) => { + paste::item! { + fn [](input: &[u8], output: &mut [u8; $bytes_result]) { + unsafe { + sys::[]( + input.as_ptr(), + input.len() as u32, + output.as_mut_ptr(), + ) + } + } + } + }; +} + +#[inline(always)] +fn extract_from_slice(output: &mut &mut [u8], new_len: usize) { + debug_assert!(new_len <= output.len()); + let tmp = core::mem::take(output); + *output = &mut tmp[..new_len]; +} + +#[inline(always)] +fn ptr_len_or_sentinel(data: &mut Option<&mut &mut [u8]>) -> (*mut u8, u32) { + match data { + Some(ref mut data) => (data.as_mut_ptr(), data.len() as _), + None => (crate::SENTINEL as _, 0), + } +} + +#[inline(always)] +fn ptr_or_sentinel(data: &Option<&[u8]>) -> *const u8 { + match data { + Some(ref data) => data.as_ptr(), + None => crate::SENTINEL as _, + } +} + +impl HostFn for HostFnImpl { + fn instantiate( + code_hash: &[u8], + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: Option<&[u8]>, + value: &[u8], + input: &[u8], + mut address: Option<&mut &mut [u8]>, + mut output: Option<&mut &mut [u8]>, + salt: &[u8], + ) -> Result { + let (address_ptr, mut address_len) = ptr_len_or_sentinel(&mut address); + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + #[repr(packed)] + #[allow(dead_code)] + struct Args { + code_hash: *const u8, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: *const u8, + value: *const u8, + input: *const u8, + input_len: usize, + address: *const u8, + address_len: *mut u32, + output: *mut u8, + output_len: *mut u32, + salt: *const u8, + salt_len: usize, + } + let args = Args { + code_hash: code_hash.as_ptr(), + ref_time_limit, + proof_size_limit, + deposit_limit: deposit_limit_ptr, + value: value.as_ptr(), + input: input.as_ptr(), + input_len: input.len(), + address: address_ptr, + address_len: &mut address_len as *mut _, + output: output_ptr, + output_len: &mut output_len as *mut _, + salt: salt.as_ptr(), + salt_len: salt.len(), + }; + + let ret_code = { unsafe { sys::instantiate(&args as *const Args as *const _) } }; + + if let Some(ref mut address) = address { + extract_from_slice(address, address_len as usize); + } + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } + + ret_code.into() + } + + fn call( + flags: CallFlags, + callee: &[u8], + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: Option<&[u8]>, + value: &[u8], + input: &[u8], + mut output: Option<&mut &mut [u8]>, + ) -> Result { + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + #[repr(packed)] + #[allow(dead_code)] + struct Args { + flags: u32, + callee: *const u8, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_limit: *const u8, + value: *const u8, + input: *const u8, + input_len: usize, + output: *mut u8, + output_len: *mut u32, + } + let args = Args { + flags: flags.bits(), + callee: callee.as_ptr(), + ref_time_limit, + proof_size_limit, + deposit_limit: deposit_limit_ptr, + value: value.as_ptr(), + input: input.as_ptr(), + input_len: input.len(), + output: output_ptr, + output_len: &mut output_len as *mut _, + }; + + let ret_code = { unsafe { sys::call(&args as *const Args as *const _) } }; + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } + + ret_code.into() + } + + fn caller_is_root() -> u32 { + unsafe { sys::caller_is_root() }.into_u32() + } + + fn delegate_call( + flags: CallFlags, + code_hash: &[u8], + input: &[u8], + mut output: Option<&mut &mut [u8]>, + ) -> Result { + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let ret_code = { + unsafe { + sys::delegate_call( + flags.bits(), + code_hash.as_ptr(), + input.as_ptr(), + input.len() as u32, + output_ptr, + &mut output_len, + ) + } + }; + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } + + ret_code.into() + } + + fn transfer(account_id: &[u8], value: &[u8]) -> Result { + let ret_code = unsafe { sys::transfer(account_id.as_ptr(), value.as_ptr()) }; + ret_code.into() + } + + fn deposit_event(topics: &[u8], data: &[u8]) { + unsafe { + sys::deposit_event( + topics.as_ptr(), + topics.len() as u32, + data.as_ptr(), + data.len() as u32, + ) + } + } + + fn set_storage(flags: StorageFlags, key: &[u8], encoded_value: &[u8]) -> Option { + let ret_code = unsafe { + sys::set_storage( + flags.bits(), + key.as_ptr(), + key.len() as u32, + encoded_value.as_ptr(), + encoded_value.len() as u32, + ) + }; + ret_code.into() + } + + fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option { + let ret_code = unsafe { sys::clear_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; + ret_code.into() + } + + fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option { + let ret_code = + unsafe { sys::contains_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; + ret_code.into() + } + + fn get_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::get_storage( + flags.bits(), + key.as_ptr(), + key.len() as u32, + output.as_mut_ptr(), + &mut output_len, + ) + } + }; + extract_from_slice(output, output_len as usize); + ret_code.into() + } + + fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::take_storage( + flags.bits(), + key.as_ptr(), + key.len() as u32, + output.as_mut_ptr(), + &mut output_len, + ) + } + }; + extract_from_slice(output, output_len as usize); + ret_code.into() + } + + fn debug_message(str: &[u8]) -> Result { + let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) }; + ret_code.into() + } + + fn terminate(beneficiary: &[u8]) -> ! { + unsafe { sys::terminate(beneficiary.as_ptr()) } + panic!("terminate does not return"); + } + + fn call_chain_extension(func_id: u32, input: &[u8], mut output: Option<&mut &mut [u8]>) -> u32 { + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); + let ret_code = { + unsafe { + sys::call_chain_extension( + func_id, + input.as_ptr(), + input.len() as u32, + output_ptr, + &mut output_len, + ) + } + }; + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } + ret_code.into_u32() + } + + fn input(output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + { + unsafe { sys::input(output.as_mut_ptr(), &mut output_len) }; + } + extract_from_slice(output, output_len as usize); + } + + fn return_value(flags: ReturnFlags, return_value: &[u8]) -> ! { + unsafe { sys::seal_return(flags.bits(), return_value.as_ptr(), return_value.len() as u32) } + panic!("seal_return does not return"); + } + + fn call_runtime(call: &[u8]) -> Result { + let ret_code = unsafe { sys::call_runtime(call.as_ptr(), call.len() as u32) }; + ret_code.into() + } + + impl_wrapper_for! { + caller, block_number, address, balance, + value_transferred,now, minimum_balance, + weight_left, + } + + fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + { + unsafe { + sys::weight_to_fee( + ref_time_limit, + proof_size_limit, + output.as_mut_ptr(), + &mut output_len, + ) + }; + } + extract_from_slice(output, output_len as usize); + } + + impl_hash_fn!(sha2_256, 32); + impl_hash_fn!(keccak_256, 32); + impl_hash_fn!(blake2_256, 32); + impl_hash_fn!(blake2_128, 16); + + fn ecdsa_recover( + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], + ) -> Result { + let ret_code = unsafe { + sys::ecdsa_recover(signature.as_ptr(), message_hash.as_ptr(), output.as_mut_ptr()) + }; + ret_code.into() + } + + fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result { + let ret_code = unsafe { sys::ecdsa_to_eth_address(pubkey.as_ptr(), output.as_mut_ptr()) }; + ret_code.into() + } + + fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result { + let ret_code = unsafe { + sys::sr25519_verify( + signature.as_ptr(), + pub_key.as_ptr(), + message.len() as u32, + message.as_ptr(), + ) + }; + ret_code.into() + } + + fn is_contract(account_id: &[u8]) -> bool { + let ret_val = unsafe { sys::is_contract(account_id.as_ptr()) }; + ret_val.into_bool() + } + + fn caller_is_origin() -> bool { + let ret_val = unsafe { sys::caller_is_origin() }; + ret_val.into_bool() + } + + fn set_code_hash(code_hash: &[u8]) -> Result { + let ret_val = unsafe { sys::set_code_hash(code_hash.as_ptr()) }; + ret_val.into() + } + + fn code_hash(account_id: &[u8], output: &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_val = + unsafe { sys::code_hash(account_id.as_ptr(), output.as_mut_ptr(), &mut output_len) }; + ret_val.into() + } + + fn own_code_hash(output: &mut [u8]) { + let mut output_len = output.len() as u32; + unsafe { sys::own_code_hash(output.as_mut_ptr(), &mut output_len) } + } + + fn lock_delegate_dependency(code_hash: &[u8]) { + unsafe { sys::lock_delegate_dependency(code_hash.as_ptr()) } + } + + fn unlock_delegate_dependency(code_hash: &[u8]) { + unsafe { sys::unlock_delegate_dependency(code_hash.as_ptr()) } + } + + fn xcm_execute(msg: &[u8]) -> Result { + let ret_code = unsafe { sys::xcm_execute(msg.as_ptr(), msg.len() as _) }; + ret_code.into() + } + + fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { + let ret_code = unsafe { + sys::xcm_send(dest.as_ptr(), msg.as_ptr(), msg.len() as _, output.as_mut_ptr()) + }; + ret_code.into() + } +} diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs new file mode 100644 index 000000000000..e660ce36ef75 --- /dev/null +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -0,0 +1,131 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! External C API to communicate with substrate contracts runtime module. +//! +//! Refer to substrate FRAME contract module for more documentation. + +#![no_std] + +mod flags; +pub use flags::*; +mod host; + +pub use host::{HostFn, HostFnImpl}; + +macro_rules! define_error_codes { + ( + $( + $( #[$attr:meta] )* + $name:ident = $discr:literal, + )* + ) => { + /// Every error that can be returned to a contract when it calls any of the host functions. + #[derive(Debug, PartialEq, Eq)] + #[repr(u32)] + pub enum ReturnErrorCode { + /// API call successful. + Success = 0, + $( + $( #[$attr] )* + $name = $discr, + )* + /// Returns if an unknown error was received from the host module. + Unknown, + } + + impl From for Result { + fn from(return_code: ReturnCode) -> Self { + match return_code.0 { + 0 => Ok(()), + $( + $discr => Err(ReturnErrorCode::$name), + )* + _ => Err(ReturnErrorCode::Unknown), + } + } + } + }; +} + +impl From for u32 { + fn from(code: ReturnErrorCode) -> u32 { + code as u32 + } +} + +define_error_codes! { + /// The called function trapped and has its state changes reverted. + /// In this case no output buffer is returned. + /// Can only be returned from `call` and `instantiate`. + CalleeTrapped = 1, + /// The called function ran to completion but decided to revert its state. + /// An output buffer is returned when one was supplied. + /// Can only be returned from `call` and `instantiate`. + CalleeReverted = 2, + /// The passed key does not exist in storage. + KeyNotFound = 3, + /// Transfer failed for other not further specified reason. Most probably + /// reserved or locked balance of the sender that was preventing the transfer. + TransferFailed = 4, + /// No code could be found at the supplied code hash. + CodeNotFound = 5, + /// The account that was called is no contract. + NotCallable = 6, + /// The call to `debug_message` had no effect because debug message + /// recording was disabled. + LoggingDisabled = 7, + /// The call dispatched by `call_runtime` was executed but returned an error. + CallRuntimeFailed = 8, + /// ECDSA public key recovery failed. Most probably wrong recovery id or signature. + EcdsaRecoveryFailed = 9, + /// sr25519 signature verification failed. + Sr25519VerifyFailed = 10, + /// The `xcm_execute` call failed. + XcmExecutionFailed = 11, + /// The `xcm_send` call failed. + XcmSendFailed = 12, +} + +/// The raw return code returned by the host side. +#[repr(transparent)] +pub struct ReturnCode(u32); + +/// Used as a sentinel value when reading and writing contract memory. +/// +/// We use this value to signal `None` to a contract when only a primitive is +/// allowed and we don't want to go through encoding a full Rust type. +/// Using `u32::Max` is a safe sentinel because contracts are never +/// allowed to use such a large amount of resources. So this value doesn't +/// make sense for a memory location or length. +const SENTINEL: u32 = u32::MAX; + +impl From for Option { + fn from(code: ReturnCode) -> Self { + (code.0 < SENTINEL).then_some(code.0) + } +} + +impl ReturnCode { + /// Returns the raw underlying `u32` representation. + pub fn into_u32(self) -> u32 { + self.0 + } + /// Returns the underlying `u32` converted into `bool`. + pub fn into_bool(self) -> bool { + self.0.ne(&0) + } +} + +type Result = core::result::Result<(), ReturnErrorCode>; diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 235d75a4b752..e5fb15cdd07c 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -177,8 +177,15 @@ pub mod runtime { pub use frame_executive::*; /// Macro to amalgamate the runtime into `struct Runtime`. + /// + /// Consider using the new version of this [`frame_construct_runtime`]. pub use frame_support::construct_runtime; + /// Macro to amalgamate the runtime into `struct Runtime`. + /// + /// This is the newer version of [`construct_runtime`]. + pub use frame_support::runtime as frame_construct_runtime; + /// Macro to easily derive the `Config` trait of various pallet for `Runtime`. pub use frame_support::derive_impl; @@ -186,12 +193,18 @@ pub mod runtime { // TODO: using linking in the Get in the line above triggers an ICE :/ pub use frame_support::{ord_parameter_types, parameter_types}; + /// For building genesis config. + pub use frame_support::genesis_builder_helper::{build_state, get_preset}; + /// Const types that can easily be used in conjuncture with `Get`. pub use frame_support::traits::{ ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, ConstU32, ConstU64, ConstU8, }; + /// Used for simple fee calculation. + pub use frame_support::weights::{self, FixedFee, NoFee}; + /// Primary types used to parameterize `EnsureOrigin` and `EnsureRootWithArg`. pub use frame_system::{ EnsureNever, EnsureNone, EnsureRoot, EnsureRootWithSuccess, EnsureSigned, @@ -201,11 +214,16 @@ pub mod runtime { /// Types to define your runtime version. pub use sp_version::{create_runtime_str, runtime_version, RuntimeVersion}; + #[cfg(feature = "std")] + pub use sp_version::NativeVersion; + /// Macro to implement runtime APIs. pub use sp_api::impl_runtime_apis; - #[cfg(feature = "std")] - pub use sp_version::NativeVersion; + // Types often used in the runtime APIs. + pub use sp_core::OpaqueMetadata; + pub use sp_inherents::{CheckInherentsResult, InherentData}; + pub use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode}; } /// Types and traits for runtimes that implement runtime APIs. @@ -223,11 +241,6 @@ pub mod runtime { // moved to file similarly. #[allow(ambiguous_glob_reexports)] pub mod apis { - // Types often used in the runtime APIs. - pub use sp_core::OpaqueMetadata; - pub use sp_inherents::{CheckInherentsResult, InherentData}; - pub use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode}; - pub use frame_system_rpc_runtime_api::*; pub use sp_api::{self, *}; pub use sp_block_builder::*; diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index a423656c394f..f635ed32a124 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -92,6 +92,8 @@ pub use hooks::{ pub mod schedule; mod storage; +#[cfg(feature = "experimental")] +pub use storage::MaybeConsideration; pub use storage::{ Consideration, Footprint, Incrementable, Instance, LinearStoragePrice, PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, WhitelistedStorageKeys, diff --git a/substrate/frame/support/src/traits/misc.rs b/substrate/frame/support/src/traits/misc.rs index 7c8c22d1ae5a..492475d6f63c 100644 --- a/substrate/frame/support/src/traits/misc.rs +++ b/substrate/frame/support/src/traits/misc.rs @@ -48,6 +48,10 @@ impl VariantCount for () { const VARIANT_COUNT: u32 = 0; } +impl VariantCount for u8 { + const VARIANT_COUNT: u32 = 256; +} + /// Adapter for `Get` to access `VARIANT_COUNT` from `trait pub trait VariantCount {`. pub struct VariantCountOf(core::marker::PhantomData); impl Get for VariantCountOf { diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs index a954af14d259..2b8e43707389 100644 --- a/substrate/frame/support/src/traits/storage.rs +++ b/substrate/frame/support/src/traits/storage.rs @@ -201,7 +201,7 @@ where } /// Some sort of cost taken from account temporarily in order to offset the cost to the chain of -/// holding some data `Footprint` (e.g. [`Footprint`]) in state. +/// holding some data [`Footprint`] in state. /// /// The cost may be increased, reduced or dropped entirely as the footprint changes. /// @@ -217,16 +217,14 @@ pub trait Consideration: Member + FullCodec + TypeInfo + MaxEncodedLen { /// Create a ticket for the `new` footprint attributable to `who`. This ticket *must* ultimately - /// be consumed through `update` or `drop` once the footprint changes or is removed. `None` - /// implies no cost for a given footprint. - fn new(who: &AccountId, new: Footprint) -> Result, DispatchError>; + /// be consumed through `update` or `drop` once the footprint changes or is removed. + fn new(who: &AccountId, new: Footprint) -> Result; /// Optionally consume an old ticket and alter the footprint, enforcing the new cost to `who` - /// and returning the new ticket (or an error if there was an issue). `None` implies no cost for - /// a given footprint. + /// and returning the new ticket (or an error if there was an issue). /// /// For creating tickets and dropping them, you can use the simpler `new` and `drop` instead. - fn update(self, who: &AccountId, new: Footprint) -> Result, DispatchError>; + fn update(self, who: &AccountId, new: Footprint) -> Result; /// Consume a ticket for some `old` footprint attributable to `who` which should now been freed. fn drop(self, who: &AccountId) -> Result<(), DispatchError>; @@ -239,18 +237,42 @@ pub trait Consideration: fn burn(self, _: &AccountId) { let _ = self; } + /// Ensure that creating a ticket for a given account and footprint will be successful if done + /// immediately after this call. + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(who: &AccountId, new: Footprint); } impl Consideration for () { - fn new(_: &A, _: F) -> Result, DispatchError> { - Ok(Some(())) + fn new(_: &A, _: F) -> Result { + Ok(()) } - fn update(self, _: &A, _: F) -> Result, DispatchError> { - Ok(Some(())) + fn update(self, _: &A, _: F) -> Result<(), DispatchError> { + Ok(()) } fn drop(self, _: &A) -> Result<(), DispatchError> { Ok(()) } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(_: &A, _: F) {} +} + +#[cfg(feature = "experimental")] +/// An extension of the [`Consideration`] trait that allows for the management of tickets that may +/// represent no cost. While the [`MaybeConsideration`] still requires proper handling, it +/// introduces the ability to determine if a ticket represents no cost and can be safely forgotten +/// without any side effects. +pub trait MaybeConsideration: Consideration { + /// Returns `true` if this [`Consideration`] represents a no-cost ticket and can be forgotten + /// without any side effects. + fn is_none(&self) -> bool; +} + +#[cfg(feature = "experimental")] +impl MaybeConsideration for () { + fn is_none(&self) -> bool { + true + } } macro_rules! impl_incrementable { diff --git a/substrate/frame/support/src/traits/tokens/fungible/mod.rs b/substrate/frame/support/src/traits/tokens/fungible/mod.rs index f40e494b930d..c67755e133bf 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/mod.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/mod.rs @@ -164,6 +164,8 @@ use codec::{Decode, Encode, MaxEncodedLen}; use core::marker::PhantomData; use frame_support_procedural::{CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; use scale_info::TypeInfo; +#[cfg(feature = "runtime-benchmarks")] +use sp_runtime::Saturating; use super::{ Fortitude::{Force, Polite}, @@ -184,6 +186,8 @@ use sp_core::Get; use sp_runtime::{traits::Convert, DispatchError}; pub use union_of::{NativeFromLeft, NativeOrWithId, UnionOf}; +#[cfg(feature = "experimental")] +use crate::traits::MaybeConsideration; use crate::{ ensure, traits::{Consideration, Footprint}, @@ -209,38 +213,49 @@ pub struct FreezeConsideration(F::Balance, PhantomData ( where F: MutateFreeze; impl< - A: 'static, - F: 'static + MutateFreeze, + A: 'static + Eq, + #[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateFreeze, + #[cfg(feature = "runtime-benchmarks")] F: 'static + MutateFreeze + Mutate, R: 'static + Get, D: 'static + Convert, Fp: 'static, > Consideration for FreezeConsideration { - fn new(who: &A, footprint: Fp) -> Result, DispatchError> { + fn new(who: &A, footprint: Fp) -> Result { let new = D::convert(footprint); - if new.is_zero() { - Ok(None) - } else { - F::increase_frozen(&R::get(), who, new)?; - Ok(Some(Self(new, PhantomData))) - } + F::increase_frozen(&R::get(), who, new)?; + Ok(Self(new, PhantomData)) } - fn update(self, who: &A, footprint: Fp) -> Result, DispatchError> { + fn update(self, who: &A, footprint: Fp) -> Result { let new = D::convert(footprint); if self.0 > new { F::decrease_frozen(&R::get(), who, self.0 - new)?; } else if new > self.0 { F::increase_frozen(&R::get(), who, new - self.0)?; } - if new.is_zero() { - Ok(None) - } else { - Ok(Some(Self(new, PhantomData))) - } + Ok(Self(new, PhantomData)) } fn drop(self, who: &A) -> Result<(), DispatchError> { F::decrease_frozen(&R::get(), who, self.0).map(|_| ()) } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(who: &A, fp: Fp) { + let _ = F::mint_into(who, F::minimum_balance().saturating_add(D::convert(fp))); + } +} +#[cfg(feature = "experimental")] +impl< + A: 'static + Eq, + #[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateFreeze, + #[cfg(feature = "runtime-benchmarks")] F: 'static + MutateFreeze + Mutate, + R: 'static + Get, + D: 'static + Convert, + Fp: 'static, + > MaybeConsideration for FreezeConsideration +{ + fn is_none(&self) -> bool { + self.0.is_zero() + } } /// Consideration method using a `fungible` balance frozen as the cost exacted for the footprint. @@ -263,34 +278,27 @@ pub struct HoldConsideration( where F: MutateHold; impl< - A: 'static, - F: 'static + MutateHold, + A: 'static + Eq, + #[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateHold, + #[cfg(feature = "runtime-benchmarks")] F: 'static + MutateHold + Mutate, R: 'static + Get, D: 'static + Convert, Fp: 'static, > Consideration for HoldConsideration { - fn new(who: &A, footprint: Fp) -> Result, DispatchError> { + fn new(who: &A, footprint: Fp) -> Result { let new = D::convert(footprint); - if new.is_zero() { - Ok(None) - } else { - F::hold(&R::get(), who, new)?; - Ok(Some(Self(new, PhantomData))) - } + F::hold(&R::get(), who, new)?; + Ok(Self(new, PhantomData)) } - fn update(self, who: &A, footprint: Fp) -> Result, DispatchError> { + fn update(self, who: &A, footprint: Fp) -> Result { let new = D::convert(footprint); if self.0 > new { F::release(&R::get(), who, self.0 - new, BestEffort)?; } else if new > self.0 { F::hold(&R::get(), who, new - self.0)?; } - if new.is_zero() { - Ok(None) - } else { - Ok(Some(Self(new, PhantomData))) - } + Ok(Self(new, PhantomData)) } fn drop(self, who: &A) -> Result<(), DispatchError> { F::release(&R::get(), who, self.0, BestEffort).map(|_| ()) @@ -298,6 +306,24 @@ impl< fn burn(self, who: &A) { let _ = F::burn_held(&R::get(), who, self.0, BestEffort, Force); } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(who: &A, fp: Fp) { + let _ = F::mint_into(who, F::minimum_balance().saturating_add(D::convert(fp))); + } +} +#[cfg(feature = "experimental")] +impl< + A: 'static + Eq, + #[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateHold, + #[cfg(feature = "runtime-benchmarks")] F: 'static + MutateHold + Mutate, + R: 'static + Get, + D: 'static + Convert, + Fp: 'static, + > MaybeConsideration for HoldConsideration +{ + fn is_none(&self) -> bool { + self.0.is_zero() + } } /// Basic consideration method using a `fungible` balance frozen as the cost exacted for the @@ -321,34 +347,28 @@ impl< #[codec(mel_bound())] pub struct LoneFreezeConsideration(PhantomData (A, Fx, Rx, D, Fp)>); impl< - A: 'static, - Fx: 'static + MutateFreeze, + A: 'static + Eq, + #[cfg(not(feature = "runtime-benchmarks"))] Fx: 'static + MutateFreeze, + #[cfg(feature = "runtime-benchmarks")] Fx: 'static + MutateFreeze + Mutate, Rx: 'static + Get, D: 'static + Convert, Fp: 'static, > Consideration for LoneFreezeConsideration { - fn new(who: &A, footprint: Fp) -> Result, DispatchError> { + fn new(who: &A, footprint: Fp) -> Result { ensure!(Fx::balance_frozen(&Rx::get(), who).is_zero(), DispatchError::Unavailable); - let new = D::convert(footprint); - if new.is_zero() { - Ok(None) - } else { - Fx::set_frozen(&Rx::get(), who, new, Polite).map(|_| Some(Self(PhantomData))) - } + Fx::set_frozen(&Rx::get(), who, D::convert(footprint), Polite).map(|_| Self(PhantomData)) } - fn update(self, who: &A, footprint: Fp) -> Result, DispatchError> { - let new = D::convert(footprint); - let _ = Fx::set_frozen(&Rx::get(), who, new, Polite)?; - if new.is_zero() { - Ok(None) - } else { - Ok(Some(Self(PhantomData))) - } + fn update(self, who: &A, footprint: Fp) -> Result { + Fx::set_frozen(&Rx::get(), who, D::convert(footprint), Polite).map(|_| Self(PhantomData)) } fn drop(self, who: &A) -> Result<(), DispatchError> { Fx::thaw(&Rx::get(), who).map(|_| ()) } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(who: &A, fp: Fp) { + let _ = Fx::mint_into(who, Fx::minimum_balance().saturating_add(D::convert(fp))); + } } /// Basic consideration method using a `fungible` balance placed on hold as the cost exacted for the @@ -372,30 +392,20 @@ impl< #[codec(mel_bound())] pub struct LoneHoldConsideration(PhantomData (A, Fx, Rx, D, Fp)>); impl< - A: 'static, - F: 'static + MutateHold, + A: 'static + Eq, + #[cfg(not(feature = "runtime-benchmarks"))] F: 'static + MutateHold, + #[cfg(feature = "runtime-benchmarks")] F: 'static + MutateHold + Mutate, R: 'static + Get, D: 'static + Convert, Fp: 'static, > Consideration for LoneHoldConsideration { - fn new(who: &A, footprint: Fp) -> Result, DispatchError> { + fn new(who: &A, footprint: Fp) -> Result { ensure!(F::balance_on_hold(&R::get(), who).is_zero(), DispatchError::Unavailable); - let new = D::convert(footprint); - if new.is_zero() { - Ok(None) - } else { - F::set_on_hold(&R::get(), who, new).map(|_| Some(Self(PhantomData))) - } + F::set_on_hold(&R::get(), who, D::convert(footprint)).map(|_| Self(PhantomData)) } - fn update(self, who: &A, footprint: Fp) -> Result, DispatchError> { - let new = D::convert(footprint); - let _ = F::set_on_hold(&R::get(), who, new)?; - if new.is_zero() { - Ok(None) - } else { - Ok(Some(Self(PhantomData))) - } + fn update(self, who: &A, footprint: Fp) -> Result { + F::set_on_hold(&R::get(), who, D::convert(footprint)).map(|_| Self(PhantomData)) } fn drop(self, who: &A) -> Result<(), DispatchError> { F::release_all(&R::get(), who, BestEffort).map(|_| ()) @@ -403,4 +413,8 @@ impl< fn burn(self, who: &A) { let _ = F::burn_all_held(&R::get(), who, BestEffort, Force); } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(who: &A, fp: Fp) { + let _ = F::mint_into(who, F::minimum_balance().saturating_add(D::convert(fp))); + } } diff --git a/substrate/frame/support/src/traits/tokens/misc.rs b/substrate/frame/support/src/traits/tokens/misc.rs index 9fa1df862097..52d3e8c014b3 100644 --- a/substrate/frame/support/src/traits/tokens/misc.rs +++ b/substrate/frame/support/src/traits/tokens/misc.rs @@ -98,7 +98,7 @@ pub enum WithdrawConsequence { /// There has been an overflow in the system. This is indicative of a corrupt state and /// likely unrecoverable. Overflow, - /// Not enough of the funds in the account are unavailable for withdrawal. + /// Not enough of the funds in the account are available for withdrawal. Frozen, /// Account balance would reduce to zero, potentially destroying it. The parameter is the /// amount of balance which is destroyed. diff --git a/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs b/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs index 326f3530e26e..81cdd40d1bcf 100644 --- a/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs +++ b/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs @@ -19,8 +19,6 @@ use frame_support::derive_impl; mod common; -use common::outer_enums::{pallet, pallet2}; - pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; @@ -75,8 +73,10 @@ frame_support::construct_runtime!( } ); +#[cfg(feature = "experimental")] #[test] fn module_error_outer_enum_expand_explicit() { + use common::outer_enums::{pallet, pallet2}; // The Runtime has *all* parts explicitly defined. // Check that all error types are propagated @@ -90,9 +90,7 @@ fn module_error_outer_enum_expand_explicit() { frame_system::Error::NonZeroRefCount => (), frame_system::Error::CallFiltered => (), frame_system::Error::MultiBlockMigrationsOngoing => (), - #[cfg(feature = "experimental")] frame_system::Error::InvalidTask => (), - #[cfg(feature = "experimental")] frame_system::Error::FailedTask => (), frame_system::Error::NothingAuthorized => (), frame_system::Error::Unauthorized => (), diff --git a/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs b/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs index 4149c4880cca..d2e759640c73 100644 --- a/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs +++ b/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs @@ -19,8 +19,6 @@ use frame_support::derive_impl; mod common; -use common::outer_enums::{pallet, pallet2}; - pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; @@ -75,8 +73,10 @@ frame_support::construct_runtime!( } ); +#[cfg(feature = "experimental")] #[test] fn module_error_outer_enum_expand_implicit() { + use common::outer_enums::{pallet, pallet2}; // The Runtime has *all* parts implicitly defined. // Check that all error types are propagated @@ -90,9 +90,7 @@ fn module_error_outer_enum_expand_implicit() { frame_system::Error::NonZeroRefCount => (), frame_system::Error::CallFiltered => (), frame_system::Error::MultiBlockMigrationsOngoing => (), - #[cfg(feature = "experimental")] frame_system::Error::InvalidTask => (), - #[cfg(feature = "experimental")] frame_system::Error::FailedTask => (), frame_system::Error::NothingAuthorized => (), frame_system::Error::Unauthorized => (), diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 0c6ff2cb8ddb..abacfa7b62cc 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -917,7 +917,7 @@ pub mod pallet { /// Total length (in bytes) for all extrinsics put together, for the current block. #[pallet::storage] - pub(super) type AllExtrinsicsLen = StorageValue<_, u32>; + pub type AllExtrinsicsLen = StorageValue<_, u32>; /// Map of block numbers to block hashes. #[pallet::storage] diff --git a/substrate/frame/treasury/src/benchmarking.rs b/substrate/frame/treasury/src/benchmarking.rs index 63978c94e682..e63febb5798b 100644 --- a/substrate/frame/treasury/src/benchmarking.rs +++ b/substrate/frame/treasury/src/benchmarking.rs @@ -78,7 +78,7 @@ fn create_approved_proposals, I: 'static>(n: u32) -> Result<(), &'s let (_, value, lookup) = setup_proposal::(i); Treasury::::spend_local(origin.clone(), value, lookup)?; } - ensure!(>::get().len() == n as usize, "Not all approved"); + ensure!(Approvals::::get().len() == n as usize, "Not all approved"); Ok(()) } @@ -130,7 +130,7 @@ mod benchmarks { T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let (_, value, beneficiary_lookup) = setup_proposal::(SEED); Treasury::::spend_local(origin, value, beneficiary_lookup)?; - let proposal_id = Treasury::::proposal_count() - 1; + let proposal_id = ProposalCount::::get() - 1; let reject_origin = T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; diff --git a/substrate/frame/treasury/src/lib.rs b/substrate/frame/treasury/src/lib.rs index 3954489a2d15..edb39f230642 100644 --- a/substrate/frame/treasury/src/lib.rs +++ b/substrate/frame/treasury/src/lib.rs @@ -100,7 +100,7 @@ use frame_support::{ ReservableCurrency, WithdrawReasons, }, weights::Weight, - PalletId, + BoundedVec, PalletId, }; pub use pallet::*; @@ -278,12 +278,10 @@ pub mod pallet { /// Number of proposals that have been made. #[pallet::storage] - #[pallet::getter(fn proposal_count)] - pub(crate) type ProposalCount = StorageValue<_, ProposalIndex, ValueQuery>; + pub type ProposalCount = StorageValue<_, ProposalIndex, ValueQuery>; /// Proposals that have been made. #[pallet::storage] - #[pallet::getter(fn proposals)] pub type Proposals, I: 'static = ()> = StorageMap< _, Twox64Concat, @@ -299,7 +297,6 @@ pub mod pallet { /// Proposal indices that have been approved but not yet awarded. #[pallet::storage] - #[pallet::getter(fn approvals)] pub type Approvals, I: 'static = ()> = StorageValue<_, BoundedVec, ValueQuery>; @@ -335,7 +332,7 @@ pub mod pallet { impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { // Create Treasury account - let account_id = >::account_id(); + let account_id = Pallet::::account_id(); let min = T::Currency::minimum_balance(); if T::Currency::free_balance(&account_id) < min { let _ = T::Currency::make_free_balance_be(&account_id, min); @@ -501,7 +498,7 @@ pub mod pallet { .unwrap_or(Ok(()))?; let beneficiary = T::Lookup::lookup(beneficiary)?; - let proposal_index = Self::proposal_count(); + let proposal_index = ProposalCount::::get(); Approvals::::try_append(proposal_index) .map_err(|_| Error::::TooManyApprovals)?; let proposal = Proposal { @@ -794,6 +791,21 @@ impl, I: 'static> Pallet { T::PalletId::get().into_account_truncating() } + /// Public function to proposal_count storage. + pub fn proposal_count() -> ProposalIndex { + ProposalCount::::get() + } + + /// Public function to proposals storage. + pub fn proposals(index: ProposalIndex) -> Option>> { + Proposals::::get(index) + } + + /// Public function to approvals storage. + pub fn approvals() -> BoundedVec { + Approvals::::get() + } + /// Spend some money! returns number of approvals before spend. pub fn spend_funds() -> Weight { let mut total_weight = Weight::zero(); @@ -803,15 +815,15 @@ impl, I: 'static> Pallet { let account_id = Self::account_id(); let mut missed_any = false; - let mut imbalance = >::zero(); + let mut imbalance = PositiveImbalanceOf::::zero(); let proposals_len = Approvals::::mutate(|v| { let proposals_approvals_len = v.len() as u32; v.retain(|&index| { // Should always be true, but shouldn't panic if false or we're screwed. - if let Some(p) = Self::proposals(index) { + if let Some(p) = Proposals::::get(index) { if p.value <= budget_remaining { budget_remaining -= p.value; - >::remove(index); + Proposals::::remove(index); // return their deposit. let err_amount = T::Currency::unreserve(&p.proposer, p.bond); @@ -982,6 +994,6 @@ where { type Type = ::AccountId; fn get() -> Self::Type { - >::account_id() + crate::Pallet::::account_id() } } diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs index f38a06f1fdf4..a895ea8151af 100644 --- a/substrate/frame/treasury/src/tests.rs +++ b/substrate/frame/treasury/src/tests.rs @@ -219,7 +219,7 @@ fn get_payment_id(i: SpendIndex) -> Option { fn genesis_config_works() { ExtBuilder::default().build().execute_with(|| { assert_eq!(Treasury::pot(), 0); - assert_eq!(Treasury::proposal_count(), 0); + assert_eq!(ProposalCount::::get(), 0); }); } @@ -437,9 +437,9 @@ fn remove_already_removed_approval_fails() { assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(14), 100, 3)); - assert_eq!(Treasury::approvals(), vec![0]); + assert_eq!(Approvals::::get(), vec![0]); assert_ok!(Treasury::remove_approval(RuntimeOrigin::root(), 0)); - assert_eq!(Treasury::approvals(), vec![]); + assert_eq!(Approvals::::get(), vec![]); assert_noop!( Treasury::remove_approval(RuntimeOrigin::root(), 0), diff --git a/substrate/frame/try-runtime/Cargo.toml b/substrate/frame/try-runtime/Cargo.toml index 2bd791f52385..7f7d1f2b50e0 100644 --- a/substrate/frame/try-runtime/Cargo.toml +++ b/substrate/frame/try-runtime/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true license = "Apache-2.0" homepage.workspace = true repository.workspace = true -description = "FRAME pallet for democracy" +description = "Supporting types for try-runtime, testing and dry-running commands." [lints] workspace = true diff --git a/substrate/frame/whitelist/Cargo.toml b/substrate/frame/whitelist/Cargo.toml index e910fc323714..a347174ed2eb 100644 --- a/substrate/frame/whitelist/Cargo.toml +++ b/substrate/frame/whitelist/Cargo.toml @@ -6,7 +6,7 @@ edition.workspace = true license = "Apache-2.0" homepage.workspace = true repository.workspace = true -description = "FRAME pallet for whitelisting call, and dispatch from specific origin" +description = "FRAME pallet for whitelisting calls, and dispatching from a specific origin" [lints] workspace = true diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index f31aa5756ba2..57ddab9a70ce 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -26,6 +26,7 @@ sp-io = { workspace = true } sp-mmr-primitives = { workspace = true } sp-runtime = { workspace = true } sp-keystore = { workspace = true } +sp-weights = { workspace = true } strum = { features = ["derive"], workspace = true } lazy_static = { optional = true, workspace = true } @@ -48,6 +49,7 @@ std = [ "sp-keystore/std", "sp-mmr-primitives/std", "sp-runtime/std", + "sp-weights/std", "strum/std", ] diff --git a/substrate/primitives/consensus/beefy/src/lib.rs b/substrate/primitives/consensus/beefy/src/lib.rs index 6ec4a727e276..e977fb0ea25f 100644 --- a/substrate/primitives/consensus/beefy/src/lib.rs +++ b/substrate/primitives/consensus/beefy/src/lib.rs @@ -56,6 +56,7 @@ use sp_runtime::{ traits::{Hash, Header as HeaderT, Keccak256, NumberFor}, OpaqueValue, }; +use sp_weights::Weight; /// Key type for BEEFY module. pub const KEY_TYPE: sp_core::crypto::KeyTypeId = sp_application_crypto::key_types::BEEFY; @@ -460,6 +461,15 @@ pub trait AncestryHelper { ) -> bool; } +/// Weight information for the logic in `AncestryHelper`. +pub trait AncestryHelperWeightInfo: AncestryHelper
{ + /// Weight info for the `AncestryHelper::extract_validation_context()` method. + fn extract_validation_context() -> Weight; + + /// Weight info for the `AncestryHelper::is_non_canonical()` method. + fn is_non_canonical(proof: &>::Proof) -> Weight; +} + /// An opaque type used to represent the key ownership proof at the runtime API /// boundary. The inner value is an encoded representation of the actual key /// ownership proof which will be parameterized when defining the runtime. At diff --git a/substrate/primitives/merkle-mountain-range/src/lib.rs b/substrate/primitives/merkle-mountain-range/src/lib.rs index 3740047e0278..061e5dbb6c7d 100644 --- a/substrate/primitives/merkle-mountain-range/src/lib.rs +++ b/substrate/primitives/merkle-mountain-range/src/lib.rs @@ -144,7 +144,7 @@ impl FullLeaf for OpaqueLeaf { /// /// It is different from [`OpaqueLeaf`], because it does implement `Codec` /// and the encoding has to match raw `Vec` encoding. -#[derive(codec::Encode, codec::Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] +#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo)] pub struct EncodableOpaqueLeaf(pub Vec); impl EncodableOpaqueLeaf { diff --git a/substrate/primitives/merkle-mountain-range/src/utils.rs b/substrate/primitives/merkle-mountain-range/src/utils.rs index 72674e24a272..2460af4b800f 100644 --- a/substrate/primitives/merkle-mountain-range/src/utils.rs +++ b/substrate/primitives/merkle-mountain-range/src/utils.rs @@ -91,7 +91,7 @@ impl NodesUtils { Self::leaf_node_index_to_leaf_index(rightmost_leaf_pos) } - // Translate a _leaf_ `NodeIndex` to its `LeafIndex`. + /// Translate a _leaf_ `NodeIndex` to its `LeafIndex`. fn leaf_node_index_to_leaf_index(pos: NodeIndex) -> LeafIndex { if pos == 0 { return 0 @@ -100,8 +100,13 @@ impl NodesUtils { (pos + peaks.len() as u64) >> 1 } - // Starting from any node position get position of rightmost leaf; this is the leaf - // responsible for the addition of node `pos`. + /// Translate a `LeafIndex` to its _leaf_ `NodeIndex`. + pub fn leaf_index_to_leaf_node_index(leaf_index: NodeIndex) -> LeafIndex { + helper::leaf_index_to_pos(leaf_index) + } + + /// Starting from any node position get position of rightmost leaf; this is the leaf + /// responsible for the addition of node `pos`. fn rightmost_leaf_node_index_from_pos(pos: NodeIndex) -> NodeIndex { pos - (helper::pos_height_in_tree(pos) as u64) } diff --git a/substrate/primitives/runtime/src/lib.rs b/substrate/primitives/runtime/src/lib.rs index d313d23395a0..fd10dee2a7c5 100644 --- a/substrate/primitives/runtime/src/lib.rs +++ b/substrate/primitives/runtime/src/lib.rs @@ -438,10 +438,10 @@ impl TryFrom for ecdsa::Public { #[cfg(feature = "std")] impl std::fmt::Display for MultiSigner { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - match *self { - Self::Ed25519(ref who) => write!(fmt, "ed25519: {}", who), - Self::Sr25519(ref who) => write!(fmt, "sr25519: {}", who), - Self::Ecdsa(ref who) => write!(fmt, "ecdsa: {}", who), + match self { + Self::Ed25519(who) => write!(fmt, "ed25519: {}", who), + Self::Sr25519(who) => write!(fmt, "sr25519: {}", who), + Self::Ecdsa(who) => write!(fmt, "ecdsa: {}", who), } } } @@ -449,23 +449,14 @@ impl std::fmt::Display for MultiSigner { impl Verify for MultiSignature { type Signer = MultiSigner; fn verify>(&self, mut msg: L, signer: &AccountId32) -> bool { - match (self, signer) { - (Self::Ed25519(ref sig), who) => match ed25519::Public::from_slice(who.as_ref()) { - Ok(signer) => sig.verify(msg, &signer), - Err(()) => false, - }, - (Self::Sr25519(ref sig), who) => match sr25519::Public::from_slice(who.as_ref()) { - Ok(signer) => sig.verify(msg, &signer), - Err(()) => false, - }, - (Self::Ecdsa(ref sig), who) => { + let who: [u8; 32] = *signer.as_ref(); + match self { + Self::Ed25519(sig) => sig.verify(msg, &who.into()), + Self::Sr25519(sig) => sig.verify(msg, &who.into()), + Self::Ecdsa(sig) => { let m = sp_io::hashing::blake2_256(msg.get()); - match sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) { - Ok(pubkey) => - &sp_io::hashing::blake2_256(pubkey.as_ref()) == - >::as_ref(who), - _ => false, - } + sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) + .map_or(false, |pubkey| sp_io::hashing::blake2_256(&pubkey) == who) }, } } diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 03c1af50240b..5e94524816a0 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -525,23 +525,26 @@ pub trait DelegationInterface { /// This takes into account any pending slashes to `Agent` against the delegated balance. fn agent_balance(agent: Agent) -> Option; + /// Returns the total amount of funds that is unbonded and can be withdrawn from the `Agent` + /// account. `None` if not an `Agent`. + fn agent_transferable_balance(agent: Agent) -> Option; + /// Returns the total amount of funds delegated. `None` if not a `Delegator`. fn delegator_balance(delegator: Delegator) -> Option; - /// Delegate funds to `Agent`. - /// - /// Only used for the initial delegation. Use [`Self::delegate_extra`] to add more delegation. - fn delegate( - delegator: Delegator, + /// Register `Agent` such that it can accept delegation. + fn register_agent( agent: Agent, reward_account: &Self::AccountId, - amount: Self::Balance, ) -> DispatchResult; - /// Add more delegation to the `Agent`. + /// Removes `Agent` registration. /// - /// If this is the first delegation, use [`Self::delegate`] instead. - fn delegate_extra( + /// This should only be allowed if the agent has no staked funds. + fn remove_agent(agent: Agent) -> DispatchResult; + + /// Add delegation to the `Agent`. + fn delegate( delegator: Delegator, agent: Agent, amount: Self::Balance, @@ -616,7 +619,7 @@ pub trait DelegationMigrator { /// /// Also removed from [`StakingUnchecked`] as a Virtual Staker. Useful for testing. #[cfg(feature = "runtime-benchmarks")] - fn drop_agent(agent: Agent); + fn migrate_to_direct_staker(agent: Agent); } sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); diff --git a/substrate/primitives/wasm-interface/Cargo.toml b/substrate/primitives/wasm-interface/Cargo.toml index 96ea8f4235d5..9d0310fd22e8 100644 --- a/substrate/primitives/wasm-interface/Cargo.toml +++ b/substrate/primitives/wasm-interface/Cargo.toml @@ -25,5 +25,9 @@ anyhow = { optional = true, workspace = true } [features] default = ["std"] -std = ["codec/std", "log/std"] +std = [ + "anyhow?/std", + "codec/std", + "log/std", +] wasmtime = ["anyhow", "dep:wasmtime"] diff --git a/substrate/test-utils/client/src/client_ext.rs b/substrate/test-utils/client/src/client_ext.rs index 9dc4739eb795..a4f91f2ec836 100644 --- a/substrate/test-utils/client/src/client_ext.rs +++ b/substrate/test-utils/client/src/client_ext.rs @@ -42,25 +42,22 @@ pub trait ClientExt: Sized { #[async_trait::async_trait] pub trait ClientBlockImportExt: Sized { /// Import block to the chain. No finality. - async fn import(&mut self, origin: BlockOrigin, block: Block) -> Result<(), ConsensusError>; + async fn import(&self, origin: BlockOrigin, block: Block) -> Result<(), ConsensusError>; /// Import a block and make it our best block if possible. - async fn import_as_best( - &mut self, - origin: BlockOrigin, - block: Block, - ) -> Result<(), ConsensusError>; + async fn import_as_best(&self, origin: BlockOrigin, block: Block) + -> Result<(), ConsensusError>; /// Import a block and finalize it. async fn import_as_final( - &mut self, + &self, origin: BlockOrigin, block: Block, ) -> Result<(), ConsensusError>; /// Import block with justification(s), finalizes block. async fn import_justified( - &mut self, + &self, origin: BlockOrigin, block: Block, justifications: Justifications, @@ -94,7 +91,7 @@ where for<'r> &'r T: BlockImport, T: Send + Sync, { - async fn import(&mut self, origin: BlockOrigin, block: Block) -> Result<(), ConsensusError> { + async fn import(&self, origin: BlockOrigin, block: Block) -> Result<(), ConsensusError> { let (header, extrinsics) = block.deconstruct(); let mut import = BlockImportParams::new(origin, header); import.body = Some(extrinsics); @@ -104,7 +101,7 @@ where } async fn import_as_best( - &mut self, + &self, origin: BlockOrigin, block: Block, ) -> Result<(), ConsensusError> { @@ -117,7 +114,7 @@ where } async fn import_as_final( - &mut self, + &self, origin: BlockOrigin, block: Block, ) -> Result<(), ConsensusError> { @@ -131,7 +128,7 @@ where } async fn import_justified( - &mut self, + &self, origin: BlockOrigin, block: Block, justifications: Justifications, @@ -151,11 +148,11 @@ where impl ClientBlockImportExt for Client where Self: BlockImport, - RA: Send, + RA: Send + Sync, B: Send + Sync, E: Send + Sync, { - async fn import(&mut self, origin: BlockOrigin, block: Block) -> Result<(), ConsensusError> { + async fn import(&self, origin: BlockOrigin, block: Block) -> Result<(), ConsensusError> { let (header, extrinsics) = block.deconstruct(); let mut import = BlockImportParams::new(origin, header); import.body = Some(extrinsics); @@ -165,7 +162,7 @@ where } async fn import_as_best( - &mut self, + &self, origin: BlockOrigin, block: Block, ) -> Result<(), ConsensusError> { @@ -178,7 +175,7 @@ where } async fn import_as_final( - &mut self, + &self, origin: BlockOrigin, block: Block, ) -> Result<(), ConsensusError> { @@ -192,7 +189,7 @@ where } async fn import_justified( - &mut self, + &self, origin: BlockOrigin, block: Block, justifications: Justifications, diff --git a/substrate/test-utils/runtime/client/src/trait_tests.rs b/substrate/test-utils/runtime/client/src/trait_tests.rs index 6f6bb5c36ee4..c3a5f173d14e 100644 --- a/substrate/test-utils/runtime/client/src/trait_tests.rs +++ b/substrate/test-utils/runtime/client/src/trait_tests.rs @@ -46,7 +46,7 @@ where // B2 -> C3 // A1 -> D2 - let mut client = TestClientBuilder::with_backend(backend.clone()).build(); + let client = TestClientBuilder::with_backend(backend.clone()).build(); let blockchain = backend.blockchain(); let genesis_hash = client.chain_info().genesis_hash; @@ -221,7 +221,7 @@ where // B2 -> C3 // A1 -> D2 - let mut client = TestClientBuilder::with_backend(backend.clone()).build(); + let client = TestClientBuilder::with_backend(backend.clone()).build(); let blockchain = backend.blockchain(); let genesis_hash = client.chain_info().genesis_hash; @@ -390,7 +390,7 @@ where // A1 -> B2 -> B3 -> B4 // B2 -> C3 // A1 -> D2 - let mut client = TestClientBuilder::with_backend(backend.clone()).build(); + let client = TestClientBuilder::with_backend(backend.clone()).build(); let blockchain = backend.blockchain(); let genesis_hash = client.chain_info().genesis_hash; diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index d1a3eaa2daa9..514f3bcba204 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -1062,7 +1062,7 @@ mod tests { // This tests that the on-chain `HEAP_PAGES` parameter is respected. // Create a client devoting only 8 pages of wasm memory. This gives us ~512k of heap memory. - let mut client = TestClientBuilder::new().set_heap_pages(8).build(); + let client = TestClientBuilder::new().set_heap_pages(8).build(); let best_hash = client.chain_info().best_hash; // Try to allocate 1024k of memory on heap. This is going to fail since it is twice larger diff --git a/substrate/utils/binary-merkle-tree/src/lib.rs b/substrate/utils/binary-merkle-tree/src/lib.rs index 030e9f69b9b6..f2d338cf028e 100644 --- a/substrate/utils/binary-merkle-tree/src/lib.rs +++ b/substrate/utils/binary-merkle-tree/src/lib.rs @@ -356,7 +356,6 @@ mod tests { #[test] fn should_generate_empty_root() { // given - sp_tracing::init_for_tests(); let data: Vec<[u8; 1]> = Default::default(); // when @@ -372,7 +371,6 @@ mod tests { #[test] fn should_generate_single_root() { // given - sp_tracing::init_for_tests(); let data = vec![array_bytes::hex2array_unchecked::<_, 20>( "E04CC55ebEE1cBCE552f250e85c57B70B2E2625b", )]; @@ -390,7 +388,6 @@ mod tests { #[test] fn should_generate_root_pow_2() { // given - sp_tracing::init_for_tests(); let data = vec![ array_bytes::hex2array_unchecked::<_, 20>("E04CC55ebEE1cBCE552f250e85c57B70B2E2625b"), array_bytes::hex2array_unchecked::<_, 20>("25451A4de12dcCc2D166922fA938E900fCc4ED24"), @@ -408,7 +405,6 @@ mod tests { #[test] fn should_generate_root_complex() { - sp_tracing::init_for_tests(); let test = |root, data| { assert_eq!(array_bytes::bytes2hex("", &merkle_root::(data)), root); }; @@ -437,7 +433,6 @@ mod tests { #[test] fn should_generate_and_verify_proof_simple() { // given - sp_tracing::init_for_tests(); let data = vec!["a", "b", "c"]; // when @@ -501,7 +496,6 @@ mod tests { #[test] fn should_generate_and_verify_proof_complex() { // given - sp_tracing::init_for_tests(); let data = vec!["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]; for l in 0..data.len() { @@ -521,7 +515,6 @@ mod tests { #[test] fn should_generate_and_verify_proof_large() { // given - sp_tracing::init_for_tests(); let mut data = vec![]; for i in 1..16 { for c in 'a'..'z' { @@ -548,7 +541,6 @@ mod tests { #[test] fn should_generate_and_verify_proof_large_tree() { // given - sp_tracing::init_for_tests(); let mut data = vec![]; for i in 0..6000 { data.push(format!("{}", i)); @@ -571,7 +563,6 @@ mod tests { #[test] #[should_panic] fn should_panic_on_invalid_leaf_index() { - sp_tracing::init_for_tests(); merkle_proof::(vec!["a"], 5); } diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs index cfbbab4df7ca..471919815206 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -243,7 +243,7 @@ impl PalletCmd { let state = &state_without_tracking; let runtime = self.runtime_blob(&state_without_tracking)?; let runtime_code = runtime.code()?; - let alloc_strategy = Self::alloc_strategy(runtime_code.heap_pages); + let alloc_strategy = self.alloc_strategy(runtime_code.heap_pages); let executor = WasmExecutor::<( sp_io::SubstrateHostFunctions, @@ -753,9 +753,9 @@ impl PalletCmd { } /// Allocation strategy for pallet benchmarking. - fn alloc_strategy(heap_pages: Option) -> HeapAllocStrategy { - heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |p| HeapAllocStrategy::Static { - extra_pages: p as _, + fn alloc_strategy(&self, runtime_heap_pages: Option) -> HeapAllocStrategy { + self.heap_pages.or(runtime_heap_pages).map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |p| { + HeapAllocStrategy::Static { extra_pages: p as _ } }) } diff --git a/substrate/utils/frame/omni-bencher/Cargo.toml b/substrate/utils/frame/omni-bencher/Cargo.toml index 89ef2a48e01d..e2ffca8b4714 100644 --- a/substrate/utils/frame/omni-bencher/Cargo.toml +++ b/substrate/utils/frame/omni-bencher/Cargo.toml @@ -6,6 +6,7 @@ authors.workspace = true edition.workspace = true repository.workspace = true license.workspace = true +readme = "README.md" [lints] workspace = true @@ -17,5 +18,5 @@ frame-benchmarking-cli = { workspace = true } sc-cli = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } -sp-tracing = { workspace = true } +tracing-subscriber = { workspace = true } log = { workspace = true } diff --git a/substrate/utils/frame/omni-bencher/README.md b/substrate/utils/frame/omni-bencher/README.md new file mode 100644 index 000000000000..29bfaeb6450b --- /dev/null +++ b/substrate/utils/frame/omni-bencher/README.md @@ -0,0 +1,60 @@ +# Polkadot Omni Benchmarking CLI + +The Polkadot Omni benchmarker allows to benchmark the extrinsics of any Polkadot runtime. It is +meant to replace the current manual integration of the `benchmark pallet` into every parachain node. +This reduces duplicate code and makes maintenance for builders easier. The CLI is currently only +able to benchmark extrinsics. In the future it is planned to extend this to some other areas. + +General FRAME runtimes could also be used with this benchmarker, as long as they don't utilize any +host functions that are not part of the Polkadot host specification. + +## Installation + +Directly via crates.io: + +```sh +cargo install frame-omni-bencher --profile=production +``` + +from GitHub: + +```sh +cargo install --git https://github.com/paritytech/polkadot-sdk frame-omni-bencher --profile=production +``` + +or locally from the sources: + +```sh +cargo install --path substrate/utils/frame/omni-bencher --profile=production +``` + +Check the installed version and print the docs: + +```sh +frame-omni-bencher --help +``` + +## Usage + +First we need to ensure that there is a runtime available. As example we will build the Westend +runtime: + +```sh +cargo build -p westend-runtime --profile production --features runtime-benchmarks +``` + +Now as an example, we benchmark the `balances` pallet: + +```sh +frame-omni-bencher v1 benchmark pallet \ +--runtime target/release/wbuild/westend-runtime/westend-runtime.compact.compressed.wasm \ +--pallet "pallet_balances" --extrinsic "" +``` + +The `--steps`, `--repeat`, `--heap-pages` and `--wasm-execution` arguments have sane defaults and do +not need be passed explicitly anymore. + +## Backwards Compatibility + +The exposed pallet sub-command is identical as the node-integrated CLI. The only difference is that +it needs to be prefixed with a `v1` to ensure drop-in compatibility. diff --git a/substrate/utils/frame/omni-bencher/src/command.rs b/substrate/utils/frame/omni-bencher/src/command.rs index f0159f4307d6..19177ed549b7 100644 --- a/substrate/utils/frame/omni-bencher/src/command.rs +++ b/substrate/utils/frame/omni-bencher/src/command.rs @@ -36,13 +36,19 @@ use sp_runtime::traits::BlakeTwo256; /// Directly via crates.io: /// /// ```sh -/// cargo install --locked frame-omni-bencher +/// cargo install frame-omni-bencher --profile=production /// ``` /// -/// or when the sources are locally checked out: +/// from GitHub: /// /// ```sh -/// cargo install --locked --path substrate/utils/frame/omni-bencher --profile=production +/// cargo install --git https://github.com/paritytech/polkadot-sdk frame-omni-bencher --profile=production +/// ``` +/// +/// or locally from the sources: +/// +/// ```sh +/// cargo install --path substrate/utils/frame/omni-bencher --profile=production /// ``` /// /// Check the installed version and print the docs: @@ -60,7 +66,7 @@ use sp_runtime::traits::BlakeTwo256; /// cargo build -p westend-runtime --profile production --features runtime-benchmarks /// ``` /// -/// Now as example we benchmark `pallet_balances`: +/// Now as an example, we benchmark the `balances` pallet: /// /// ```sh /// frame-omni-bencher v1 benchmark pallet \ diff --git a/substrate/utils/frame/omni-bencher/src/main.rs b/substrate/utils/frame/omni-bencher/src/main.rs index a8893b5a79af..ef3450add8e4 100644 --- a/substrate/utils/frame/omni-bencher/src/main.rs +++ b/substrate/utils/frame/omni-bencher/src/main.rs @@ -19,10 +19,22 @@ mod command; use clap::Parser; use sc_cli::Result; +use tracing_subscriber::EnvFilter; fn main() -> Result<()> { - sp_tracing::try_init_simple(); + setup_logger(); + log::warn!("The FRAME omni-bencher is not yet battle tested - double check the results.",); command::Command::parse().run() } + +/// Setup logging with `info` as default level. Can be set via `RUST_LOG` env. +fn setup_logger() { + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); + + tracing_subscriber::fmt() + .with_env_filter(env_filter) + .with_writer(std::io::stderr) + .init(); +} diff --git a/substrate/utils/frame/remote-externalities/src/lib.rs b/substrate/utils/frame/remote-externalities/src/lib.rs index 40864085349b..955e79008c8c 100644 --- a/substrate/utils/frame/remote-externalities/src/lib.rs +++ b/substrate/utils/frame/remote-externalities/src/lib.rs @@ -55,7 +55,7 @@ type ChildKeyValues = Vec<(ChildInfo, Vec)>; type SnapshotVersion = Compact; const LOG_TARGET: &str = "remote-ext"; -const DEFAULT_HTTP_ENDPOINT: &str = "https://polkadot-try-runtime-node.parity-chains.parity.io:443"; +const DEFAULT_HTTP_ENDPOINT: &str = "https://try-runtime.polkadot.io:443"; const SNAPSHOT_VERSION: SnapshotVersion = Compact(4); /// The snapshot that we store on disk. diff --git a/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs b/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs index c0333bb7dac0..c455d8d39b7d 100644 --- a/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs +++ b/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs @@ -21,8 +21,9 @@ use jsonrpsee::{ core::RpcResult, proc_macros::rpc, types::error::{ErrorCode, ErrorObject, ErrorObjectOwned}, + Extensions, }; -use sc_rpc_api::DenyUnsafe; +use sc_rpc_api::check_if_safe; use serde::{Deserialize, Serialize}; use sp_runtime::traits::Block as BlockT; use std::sync::Arc; @@ -134,7 +135,7 @@ pub trait StateMigrationApi { /// This call is performed locally without submitting any transactions. Thus executing this /// won't change any state. Nonetheless it is a VERY costly call that should be /// only exposed to trusted peers. - #[method(name = "state_trieMigrationStatus")] + #[method(name = "state_trieMigrationStatus", with_extensions)] fn call(&self, at: Option) -> RpcResult; } @@ -142,14 +143,13 @@ pub trait StateMigrationApi { pub struct StateMigration { client: Arc, backend: Arc, - deny_unsafe: DenyUnsafe, _marker: std::marker::PhantomData<(B, BA)>, } impl StateMigration { /// Create new state migration rpc for the given reference to the client. - pub fn new(client: Arc, backend: Arc, deny_unsafe: DenyUnsafe) -> Self { - StateMigration { client, backend, deny_unsafe, _marker: Default::default() } + pub fn new(client: Arc, backend: Arc) -> Self { + StateMigration { client, backend, _marker: Default::default() } } } @@ -159,8 +159,12 @@ where C: Send + Sync + 'static + sc_client_api::HeaderBackend, BA: 'static + sc_client_api::backend::Backend, { - fn call(&self, at: Option<::Hash>) -> RpcResult { - self.deny_unsafe.check_if_safe()?; + fn call( + &self, + ext: &Extensions, + at: Option<::Hash>, + ) -> RpcResult { + check_if_safe(ext)?; let hash = at.unwrap_or_else(|| self.client.info().best_hash); let state = self.backend.state_at(hash).map_err(error_into_rpc_err)?; diff --git a/substrate/utils/frame/rpc/system/src/lib.rs b/substrate/utils/frame/rpc/system/src/lib.rs index 8cb7b785bc7c..9fcaa53a35d8 100644 --- a/substrate/utils/frame/rpc/system/src/lib.rs +++ b/substrate/utils/frame/rpc/system/src/lib.rs @@ -24,9 +24,9 @@ use jsonrpsee::{ core::{async_trait, RpcResult}, proc_macros::rpc, types::error::ErrorObject, + Extensions, }; -use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; use sp_api::ApiExt; use sp_block_builder::BlockBuilder; @@ -49,7 +49,7 @@ pub trait SystemApi { async fn nonce(&self, account: AccountId) -> RpcResult; /// Dry run an extrinsic at a given block. Return SCALE encoded ApplyExtrinsicResult. - #[method(name = "system_dryRun", aliases = ["system_dryRunAt"])] + #[method(name = "system_dryRun", aliases = ["system_dryRunAt"], with_extensions)] async fn dry_run(&self, extrinsic: Bytes, at: Option) -> RpcResult; } @@ -74,14 +74,13 @@ impl From for i32 { pub struct System { client: Arc, pool: Arc

, - deny_unsafe: DenyUnsafe, _marker: std::marker::PhantomData, } impl System { /// Create new `FullSystem` given client and transaction pool. - pub fn new(client: Arc, pool: Arc

, deny_unsafe: DenyUnsafe) -> Self { - Self { client, pool, deny_unsafe, _marker: Default::default() } + pub fn new(client: Arc, pool: Arc

) -> Self { + Self { client, pool, _marker: Default::default() } } } @@ -115,10 +114,12 @@ where async fn dry_run( &self, + ext: &Extensions, extrinsic: Bytes, at: Option<::Hash>, ) -> RpcResult { - self.deny_unsafe.check_if_safe()?; + sc_rpc_api::check_if_safe(ext)?; + let api = self.client.runtime_api(); let best_hash = at.unwrap_or_else(|| // If the block hash is not supplied assume the best block. @@ -217,6 +218,7 @@ mod tests { use assert_matches::assert_matches; use futures::executor::block_on; + use sc_rpc_api::DenyUnsafe; use sc_transaction_pool::BasicPool; use sp_runtime::{ transaction_validity::{InvalidTransaction, TransactionValidityError}, @@ -224,6 +226,18 @@ mod tests { }; use substrate_test_runtime_client::{runtime::Transfer, AccountKeyring}; + fn deny_unsafe() -> Extensions { + let mut ext = Extensions::new(); + ext.insert(DenyUnsafe::Yes); + ext + } + + fn allow_unsafe() -> Extensions { + let mut ext = Extensions::new(); + ext.insert(DenyUnsafe::No); + ext + } + #[tokio::test] async fn should_return_next_nonce_for_some_account() { sp_tracing::try_init_simple(); @@ -251,7 +265,7 @@ mod tests { let ext1 = new_transaction(1); block_on(pool.submit_one(hash_of_block0, source, ext1)).unwrap(); - let accounts = System::new(client, pool, DenyUnsafe::Yes); + let accounts = System::new(client, pool); // when let nonce = accounts.nonce(AccountKeyring::Alice.into()).await; @@ -270,10 +284,10 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = System::new(client, pool, DenyUnsafe::Yes); + let accounts = System::new(client, pool); // when - let res = accounts.dry_run(vec![].into(), None).await; + let res = accounts.dry_run(&deny_unsafe(), vec![].into(), None).await; assert_matches!(res, Err(e) => { assert!(e.message().contains("RPC call is unsafe to be called externally")); }); @@ -289,7 +303,7 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = System::new(client, pool, DenyUnsafe::No); + let accounts = System::new(client, pool); let tx = Transfer { from: AccountKeyring::Alice.into(), @@ -300,7 +314,10 @@ mod tests { .into_unchecked_extrinsic(); // when - let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful"); + let bytes = accounts + .dry_run(&allow_unsafe(), tx.encode().into(), None) + .await + .expect("Call is successful"); // then let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_ref()).unwrap(); @@ -317,7 +334,7 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = System::new(client, pool, DenyUnsafe::No); + let accounts = System::new(client, pool); let tx = Transfer { from: AccountKeyring::Alice.into(), @@ -328,7 +345,10 @@ mod tests { .into_unchecked_extrinsic(); // when - let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful"); + let bytes = accounts + .dry_run(&allow_unsafe(), tx.encode().into(), None) + .await + .expect("Call is successful"); // then let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_ref()).unwrap(); diff --git a/templates/minimal/Cargo.toml b/templates/minimal/Cargo.toml deleted file mode 100644 index ba96e139bcf1..000000000000 --- a/templates/minimal/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "minimal-template" -description = "A minimal template built with Substrate, part of Polkadot Sdk." -version = "0.0.0" -license = "Unlicense" -authors.workspace = true -homepage.workspace = true -repository.workspace = true -edition.workspace = true -publish = false - -[dependencies] -minimal-template-node = { workspace = true } -minimal-template-runtime = { workspace = true } -pallet-minimal-template = { workspace = true, default-features = true } -polkadot-sdk-docs = { workspace = true } - -frame = { workspace = true, default-features = true } - -# How we build docs in rust-docs -simple-mermaid = "0.1.1" -docify = { workspace = true } diff --git a/templates/minimal/node/Cargo.toml b/templates/minimal/node/Cargo.toml index da5073ea687c..956efca34532 100644 --- a/templates/minimal/node/Cargo.toml +++ b/templates/minimal/node/Cargo.toml @@ -21,43 +21,15 @@ futures-timer = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } serde_json = { workspace = true, default-features = true } -sc-cli = { workspace = true, default-features = true } -sc-executor = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -sc-service = { workspace = true, default-features = true } -sc-telemetry = { workspace = true, default-features = true } -sc-transaction-pool = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -sc-consensus = { workspace = true, default-features = true } -sc-consensus-manual-seal = { workspace = true, default-features = true } -sc-rpc-api = { workspace = true, default-features = true } -sc-basic-authorship = { workspace = true, default-features = true } -sc-offchain = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } - -sp-timestamp = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-block-builder = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } - -substrate-frame-rpc-system = { workspace = true, default-features = true } - -# Once the native runtime is gone, there should be little to no dependency on FRAME here, and -# certainly no dependency on the runtime. -frame = { features = [ - "experimental", - "runtime", -], workspace = true, default-features = true } +polkadot-sdk = { workspace = true, features = ["experimental", "node"] } minimal-template-runtime = { workspace = true } [build-dependencies] -substrate-build-script-utils = { workspace = true, default-features = true } +polkadot-sdk = { workspace = true, features = ["substrate-build-script-utils"] } [features] default = ["std"] std = [ "minimal-template-runtime/std", + "polkadot-sdk/std", ] diff --git a/templates/minimal/node/build.rs b/templates/minimal/node/build.rs index fa7686e01099..47ab77ae2969 100644 --- a/templates/minimal/node/build.rs +++ b/templates/minimal/node/build.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; +use polkadot_sdk::substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; fn main() { generate_cargo_keys(); diff --git a/templates/minimal/node/src/chain_spec.rs b/templates/minimal/node/src/chain_spec.rs index 5b53b0f80ac0..0646460acef6 100644 --- a/templates/minimal/node/src/chain_spec.rs +++ b/templates/minimal/node/src/chain_spec.rs @@ -16,9 +16,12 @@ // limitations under the License. use minimal_template_runtime::{BalancesConfig, SudoConfig, WASM_BINARY}; -use sc_service::{ChainType, Properties}; +use polkadot_sdk::{ + sc_service::{ChainType, Properties}, + sp_keyring::AccountKeyring, + *, +}; use serde_json::{json, Value}; -use sp_keyring::AccountKeyring; /// This is a specialization of the general Substrate ChainSpec type. pub type ChainSpec = sc_service::GenericChainSpec; @@ -42,8 +45,8 @@ pub fn development_config() -> Result { /// Configure initial storage state for FRAME pallets. fn testnet_genesis() -> Value { - use frame::traits::Get; use minimal_template_runtime::interface::{Balance, MinimumBalance}; + use polkadot_sdk::polkadot_sdk_frame::traits::Get; let endowment = >::get().max(1) * 1000; let balances = AccountKeyring::iter() .map(|a| (a.to_account_id(), endowment)) diff --git a/templates/minimal/node/src/cli.rs b/templates/minimal/node/src/cli.rs index 22726b7eb9a3..54107df75a36 100644 --- a/templates/minimal/node/src/cli.rs +++ b/templates/minimal/node/src/cli.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use sc_cli::RunCmd; +use polkadot_sdk::{sc_cli::RunCmd, *}; #[derive(Debug, Clone)] pub enum Consensus { diff --git a/templates/minimal/node/src/command.rs b/templates/minimal/node/src/command.rs index 504be1ccc36e..b09ea1fab237 100644 --- a/templates/minimal/node/src/command.rs +++ b/templates/minimal/node/src/command.rs @@ -20,8 +20,7 @@ use crate::{ cli::{Cli, Subcommand}, service, }; -use sc_cli::SubstrateCli; -use sc_service::PartialComponents; +use polkadot_sdk::{sc_cli::SubstrateCli, sc_service::PartialComponents, *}; impl SubstrateCli for Cli { fn impl_name() -> String { diff --git a/templates/minimal/node/src/main.rs b/templates/minimal/node/src/main.rs index 3cf7d98311ea..8f36da5bf83a 100644 --- a/templates/minimal/node/src/main.rs +++ b/templates/minimal/node/src/main.rs @@ -24,6 +24,6 @@ mod command; mod rpc; mod service; -fn main() -> sc_cli::Result<()> { +fn main() -> polkadot_sdk::sc_cli::Result<()> { command::run() } diff --git a/templates/minimal/node/src/rpc.rs b/templates/minimal/node/src/rpc.rs index 451e7b21dd0c..64497c4bcaca 100644 --- a/templates/minimal/node/src/rpc.rs +++ b/templates/minimal/node/src/rpc.rs @@ -24,20 +24,19 @@ use jsonrpsee::RpcModule; use minimal_template_runtime::interface::{AccountId, Nonce, OpaqueBlock}; -use sc_transaction_pool_api::TransactionPool; -use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; +use polkadot_sdk::{ + sc_transaction_pool_api::TransactionPool, + sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}, + *, +}; use std::sync::Arc; -pub use sc_rpc_api::DenyUnsafe; - /// Full client dependencies. pub struct FullDeps { /// The client instance to use. pub client: Arc, /// Transaction pool instance. pub pool: Arc

, - /// Whether to deny unsafe calls - pub deny_unsafe: DenyUnsafe, } #[docify::export] @@ -57,11 +56,11 @@ where C::Api: substrate_frame_rpc_system::AccountNonceApi, P: TransactionPool + 'static, { - use substrate_frame_rpc_system::{System, SystemApiServer}; + use polkadot_sdk::substrate_frame_rpc_system::{System, SystemApiServer}; let mut module = RpcModule::new(()); - let FullDeps { client, pool, deny_unsafe } = deps; + let FullDeps { client, pool } = deps; - module.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; + module.merge(System::new(client.clone(), pool.clone()).into_rpc())?; Ok(module) } diff --git a/templates/minimal/node/src/service.rs b/templates/minimal/node/src/service.rs index 44b374fcc0a4..ce3afe1e6b76 100644 --- a/templates/minimal/node/src/service.rs +++ b/templates/minimal/node/src/service.rs @@ -17,12 +17,15 @@ use futures::FutureExt; use minimal_template_runtime::{interface::OpaqueBlock as Block, RuntimeApi}; -use sc_client_api::backend::Backend; -use sc_executor::WasmExecutor; -use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; -use sc_telemetry::{Telemetry, TelemetryWorker}; -use sc_transaction_pool_api::OffchainTransactionPoolFactory; -use sp_runtime::traits::Block as BlockT; +use polkadot_sdk::{ + sc_client_api::backend::Backend, + sc_executor::WasmExecutor, + sc_service::{error::Error as ServiceError, Configuration, TaskManager}, + sc_telemetry::{Telemetry, TelemetryWorker}, + sc_transaction_pool_api::OffchainTransactionPoolFactory, + sp_runtime::traits::Block as BlockT, + *, +}; use std::sync::Arc; use crate::cli::Consensus; @@ -138,7 +141,7 @@ pub fn new_full::Ha import_queue, net_config, block_announce_validator_builder: None, - warp_sync_params: None, + warp_sync_config: None, block_relay: None, metrics, })?; @@ -168,9 +171,8 @@ pub fn new_full::Ha let client = client.clone(); let pool = transaction_pool.clone(); - Box::new(move |deny_unsafe, _| { - let deps = - crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; + Box::new(move |_| { + let deps = crate::rpc::FullDeps { client: client.clone(), pool: pool.clone() }; crate::rpc::create_full(deps).map_err(Into::into) }) }; diff --git a/templates/minimal/pallets/template/Cargo.toml b/templates/minimal/pallets/template/Cargo.toml index 9d231fe7d7d4..9a02d4daeaac 100644 --- a/templates/minimal/pallets/template/Cargo.toml +++ b/templates/minimal/pallets/template/Cargo.toml @@ -13,18 +13,14 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { features = [ - "derive", -], workspace = true } -scale-info = { features = [ - "derive", -], workspace = true } -frame = { features = [ +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } +polkadot-sdk = { workspace = true, default-features = false, features = [ "experimental", "runtime", -], workspace = true } +] } [features] default = ["std"] -std = ["codec/std", "frame/std", "scale-info/std"] +std = ["codec/std", "polkadot-sdk/std", "scale-info/std"] diff --git a/templates/minimal/pallets/template/src/lib.rs b/templates/minimal/pallets/template/src/lib.rs index 92b90ad4412b..b8a8614932a6 100644 --- a/templates/minimal/pallets/template/src/lib.rs +++ b/templates/minimal/pallets/template/src/lib.rs @@ -5,7 +5,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame::prelude::*; +use polkadot_sdk::polkadot_sdk_frame as frame; // Re-export all pallet parts, this is needed to properly import the pallet into the runtime. pub use pallet::*; @@ -15,7 +15,7 @@ pub mod pallet { use super::*; #[pallet::config] - pub trait Config: frame_system::Config {} + pub trait Config: polkadot_sdk::frame_system::Config {} #[pallet::pallet] pub struct Pallet(_); diff --git a/templates/minimal/runtime/Cargo.toml b/templates/minimal/runtime/Cargo.toml index 5d3cf8492e52..49ddf3987e96 100644 --- a/templates/minimal/runtime/Cargo.toml +++ b/templates/minimal/runtime/Cargo.toml @@ -12,47 +12,29 @@ publish = false [dependencies] codec = { workspace = true } scale-info = { workspace = true } - -# this is a frame-based runtime, thus importing `frame` with runtime feature enabled. -frame = { features = [ +polkadot-sdk = { workspace = true, features = [ "experimental", + "pallet-balances", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", "runtime", -], workspace = true } - -# pallets that we want to use -pallet-balances = { workspace = true } -pallet-sudo = { workspace = true } -pallet-timestamp = { workspace = true } -pallet-transaction-payment = { workspace = true } -pallet-transaction-payment-rpc-runtime-api = { workspace = true } - -# genesis builder that allows us to interact with runtime genesis config -sp-genesis-builder = { workspace = true } -sp-runtime = { features = ["serde"], workspace = true } +] } # local pallet templates pallet-minimal-template = { workspace = true } [build-dependencies] -substrate-wasm-builder = { optional = true, workspace = true, default-features = true } +polkadot-sdk = { optional = true, workspace = true, features = [ + "substrate-wasm-builder", +] } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - - "frame/std", - - "pallet-balances/std", - "pallet-sudo/std", - "pallet-timestamp/std", - "pallet-transaction-payment-rpc-runtime-api/std", - "pallet-transaction-payment/std", - "pallet-minimal-template/std", - - "sp-genesis-builder/std", - "sp-runtime/std", - "substrate-wasm-builder", + "polkadot-sdk/std", + "scale-info/std", ] diff --git a/templates/minimal/runtime/build.rs b/templates/minimal/runtime/build.rs index e6f92757e225..2cb2966b5d82 100644 --- a/templates/minimal/runtime/build.rs +++ b/templates/minimal/runtime/build.rs @@ -18,6 +18,6 @@ fn main() { #[cfg(feature = "std")] { - substrate_wasm_builder::WasmBuilder::build_using_defaults(); + polkadot_sdk::substrate_wasm_builder::WasmBuilder::build_using_defaults(); } } diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 08ad537ecdd1..474d9ddfb9e8 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -26,20 +26,14 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); extern crate alloc; use alloc::{vec, vec::Vec}; -use frame::{ - deps::frame_support::{ - genesis_builder_helper::{build_state, get_preset}, - runtime, - weights::{FixedFee, NoFee}, - }, - prelude::*, - runtime::{ - apis::{ - self, impl_runtime_apis, ApplyExtrinsicResult, CheckInherentsResult, - ExtrinsicInclusionMode, OpaqueMetadata, - }, +use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; +use polkadot_sdk::{ + polkadot_sdk_frame::{ + self as frame, prelude::*, + runtime::{apis, prelude::*}, }, + *, }; /// The runtime version. @@ -83,7 +77,7 @@ type SignedExtra = ( ); // Composes the runtime by adding all the used pallets and deriving necessary types. -#[runtime] +#[frame_construct_runtime] mod runtime { /// The main runtime type. #[runtime::runtime] @@ -171,8 +165,6 @@ type Header = HeaderFor; type RuntimeExecutive = Executive, Runtime, AllPalletsWithSystem>; -use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; - impl_runtime_apis! { impl apis::Core for Runtime { fn version() -> RuntimeVersion { @@ -296,7 +288,7 @@ impl_runtime_apis! { // https://github.com/paritytech/substrate/issues/10579#issuecomment-1600537558 pub mod interface { use super::Runtime; - use frame::deps::frame_system; + use polkadot_sdk::{polkadot_sdk_frame as frame, *}; pub type Block = super::Block; pub use frame::runtime::types_common::OpaqueBlock; diff --git a/templates/minimal/src/lib.rs b/templates/minimal/src/lib.rs deleted file mode 100644 index 68825d190bb2..000000000000 --- a/templates/minimal/src/lib.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! # Minimal Template -//! -//! This is a minimal template for creating a blockchain using the Polkadot SDK. -//! -//! ## Components -//! -//! The template consists of the following components: -//! -//! ### Node -//! -//! A minimal blockchain [`node`](`minimal_template_node`) that is capable of running a -//! runtime. It uses a simple chain specification, provides an option to choose Manual or -//! InstantSeal for consensus and exposes a few commands to interact with the node. -//! -//! ### Runtime -//! -//! A minimal [`runtime`](`minimal_template_runtime`) (or a state transition function) that -//! is capable of being run on the node. It is built using the [`FRAME`](`frame`) framework -//! that enables the composition of the core logic via separate modules called "pallets". -//! FRAME defines a complete DSL for building such pallets and the runtime itself. -//! -//! #### Transaction Fees -//! -//! The runtime charges a transaction fee for every transaction that is executed. The fee is -//! calculated based on the weight of the transaction (accouting for the execution time) and -//! length of the call data. Please refer to -//! [`benchmarking docs`](`polkadot_sdk_docs::reference_docs::frame_benchmarking_weight`) for -//! more information on how the weight is calculated. -//! -//! This template sets the fee as independent of the weight of the extrinsic and fixed for any -//! length of the call data for demo purposes. -//! -//! ### Pallet -//! -//! A minimal [`pallet`](`pallet_minimal_template`) that is built using FRAME. It is a unit of -//! encapsulated logic that has a clearly defined responsibility and can be linked to other pallets. -//! -//! ## Getting Started -//! -//! To get started with the template, follow the steps below: -//! -//! ### Build the Node -//! -//! Build the node using the following command: -//! -//! ```bash -//! cargo build -p minimal-template-node --release -//! ``` -//! -//! ### Run the Node -//! -//! Run the node using the following command: -//! -//! ```bash -//! ./target/release/minimal-template-node --dev -//! ``` -//! -//! ### CLI Options -//! -//! The node exposes a few options that can be used to interact with the node. To see the list of -//! available options, run the following command: -//! -//! ```bash -//! ./target/release/minimal-template-node --help -//! ``` -//! -//! #### Consensus Algorithm -//! -//! In order to run the node with a specific consensus algorithm, use the `--consensus` flag. For -//! example, to run the node with ManualSeal consensus with a block time of 5000ms, use the -//! following command: -//! -//! ```bash -//! ./target/release/minimal-template-node --dev --consensus manual-seal-5000 -//! ``` diff --git a/templates/parachain/README.md b/templates/parachain/README.md index b7fa2bed0ed9..b912d8e005c7 100644 --- a/templates/parachain/README.md +++ b/templates/parachain/README.md @@ -59,15 +59,22 @@ docker build . -t polkadot-sdk-parachain-template You can grab a [released binary](https://github.com/paritytech/zombienet/releases/latest) or use an [npm version](https://www.npmjs.com/package/@zombienet/cli). This template produces a parachain node. +You can install it in your environment by running: + +```sh +cargo install --path node +``` + You still need a relaychain node - you can download the `polkadot` (and the accompanying `polkadot-prepare-worker` and `polkadot-execute-worker`) binaries from [Polkadot SDK releases](https://github.com/paritytech/polkadot-sdk/releases/latest). -Make sure to bring the parachain node - as well as `polkadot`, `polkadot-prepare-worker`, `polkadot-execute-worker`, -and `zombienet` - into `PATH` like so: +In addition to the installed parachain node, make sure to bring +`zombienet`, `polkadot`, `polkadot-prepare-worker`, and `polkadot-execute-worker` +into `PATH`, for example: ```sh -export PATH="./target/release/:$PATH" +export PATH=":$PATH" ``` This way, we can conveniently use them in the following steps. diff --git a/templates/parachain/node/src/command.rs b/templates/parachain/node/src/command.rs index eba7fdcdae71..f346ae4386a6 100644 --- a/templates/parachain/node/src/command.rs +++ b/templates/parachain/node/src/command.rs @@ -1,5 +1,3 @@ -use std::net::SocketAddr; - use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; @@ -7,7 +5,7 @@ use log::info; use parachain_template_runtime::Block; use sc_cli::{ ChainSpec, CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, - NetworkParams, Result, SharedParams, SubstrateCli, + NetworkParams, Result, RpcEndpoint, SharedParams, SubstrateCli, }; use sc_service::config::{BasePath, PrometheusConfig}; @@ -297,7 +295,7 @@ impl CliConfiguration for RelayChainCli { .or_else(|| self.base_path.clone().map(Into::into))) } - fn rpc_addr(&self, default_listen_port: u16) -> Result> { + fn rpc_addr(&self, default_listen_port: u16) -> Result>> { self.base.base.rpc_addr(default_listen_port) } diff --git a/templates/parachain/node/src/rpc.rs b/templates/parachain/node/src/rpc.rs index bb52b974f0ce..4937469e11e2 100644 --- a/templates/parachain/node/src/rpc.rs +++ b/templates/parachain/node/src/rpc.rs @@ -9,7 +9,6 @@ use std::sync::Arc; use parachain_template_runtime::{opaque::Block, AccountId, Balance, Nonce}; -pub use sc_rpc::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; @@ -24,8 +23,6 @@ pub struct FullDeps { pub client: Arc, /// Transaction pool instance. pub pool: Arc

, - /// Whether to deny unsafe calls - pub deny_unsafe: DenyUnsafe, } /// Instantiate all RPC extensions. @@ -48,9 +45,9 @@ where use substrate_frame_rpc_system::{System, SystemApiServer}; let mut module = RpcExtension::new(()); - let FullDeps { client, pool, deny_unsafe } = deps; + let FullDeps { client, pool } = deps; - module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; + module.merge(System::new(client.clone(), pool).into_rpc())?; module.merge(TransactionPayment::new(client).into_rpc())?; Ok(module) } diff --git a/templates/parachain/node/src/service.rs b/templates/parachain/node/src/service.rs index 88f2710a5cb8..b46b6ecfde18 100644 --- a/templates/parachain/node/src/service.rs +++ b/templates/parachain/node/src/service.rs @@ -305,12 +305,9 @@ pub async fn start_parachain_node( 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, - }; + Box::new(move |_| { + let deps = + crate::rpc::FullDeps { client: client.clone(), pool: transaction_pool.clone() }; crate::rpc::create_full(deps).map_err(Into::into) }) diff --git a/templates/parachain/zombienet.toml b/templates/parachain/zombienet.toml index 336ba1af4dde..c47a4bdeb6ad 100644 --- a/templates/parachain/zombienet.toml +++ b/templates/parachain/zombienet.toml @@ -1,16 +1,21 @@ [relaychain] default_command = "polkadot" -chain = "dev" +chain = "rococo-local" [[relaychain.nodes]] name = "alice" validator = true ws_port = 9944 +[[relaychain.nodes]] +name = "bob" +validator = true +ws_port = 9955 + [[parachains]] id = 1000 [parachains.collator] -name = "alice" +name = "charlie" ws_port = 9988 command = "parachain-template-node" diff --git a/templates/solochain/node/Cargo.toml b/templates/solochain/node/Cargo.toml index 9dd1b144d229..8f74c6b3cb55 100644 --- a/templates/solochain/node/Cargo.toml +++ b/templates/solochain/node/Cargo.toml @@ -36,7 +36,6 @@ sc-consensus = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sc-rpc-api = { workspace = true, default-features = true } sc-basic-authorship = { workspace = true, default-features = true } # substrate primitives diff --git a/templates/solochain/node/src/rpc.rs b/templates/solochain/node/src/rpc.rs index fe2b6ca72ede..1fc6eb0127e9 100644 --- a/templates/solochain/node/src/rpc.rs +++ b/templates/solochain/node/src/rpc.rs @@ -14,16 +14,12 @@ use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; -pub use sc_rpc_api::DenyUnsafe; - /// Full client dependencies. pub struct FullDeps { /// The client instance to use. pub client: Arc, /// Transaction pool instance. pub pool: Arc

, - /// Whether to deny unsafe calls - pub deny_unsafe: DenyUnsafe, } /// Instantiate all full RPC extensions. @@ -43,9 +39,9 @@ where use substrate_frame_rpc_system::{System, SystemApiServer}; let mut module = RpcModule::new(()); - let FullDeps { client, pool, deny_unsafe } = deps; + let FullDeps { client, pool } = deps; - module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; + module.merge(System::new(client.clone(), pool).into_rpc())?; module.merge(TransactionPayment::new(client).into_rpc())?; // Extend this RPC with a custom API by using the following syntax. diff --git a/templates/solochain/node/src/service.rs b/templates/solochain/node/src/service.rs index 5e84552f4ccd..2b43ecfa1ce2 100644 --- a/templates/solochain/node/src/service.rs +++ b/templates/solochain/node/src/service.rs @@ -4,7 +4,7 @@ use futures::FutureExt; use sc_client_api::{Backend, BlockBackend}; use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams}; use sc_consensus_grandpa::SharedVoterState; -use sc_service::{error::Error as ServiceError, Configuration, TaskManager, WarpSyncParams}; +use sc_service::{error::Error as ServiceError, Configuration, TaskManager, WarpSyncConfig}; use sc_telemetry::{Telemetry, TelemetryWorker}; use sc_transaction_pool_api::OffchainTransactionPoolFactory; use solochain_template_runtime::{self, apis::RuntimeApi, opaque::Block}; @@ -175,7 +175,7 @@ pub fn new_full< spawn_handle: task_manager.spawn_handle(), import_queue, block_announce_validator_builder: None, - warp_sync_params: Some(WarpSyncParams::WithProvider(warp_sync)), + warp_sync_config: Some(WarpSyncConfig::WithProvider(warp_sync)), block_relay: None, metrics, })?; @@ -212,9 +212,8 @@ pub fn new_full< let client = client.clone(); let pool = transaction_pool.clone(); - Box::new(move |deny_unsafe, _| { - let deps = - crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; + Box::new(move |_| { + let deps = crate::rpc::FullDeps { client: client.clone(), pool: pool.clone() }; crate::rpc::create_full(deps).map_err(Into::into) }) }; diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 65ff9a81e474..6d380a4bcbb7 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -120,6 +120,9 @@ std = [ "pallet-recovery?/std", "pallet-referenda?/std", "pallet-remark?/std", + "pallet-revive-fixtures?/std", + "pallet-revive-mock-network?/std", + "pallet-revive?/std", "pallet-root-offences?/std", "pallet-root-testing?/std", "pallet-safe-mode?/std", @@ -304,6 +307,8 @@ runtime-benchmarks = [ "pallet-recovery?/runtime-benchmarks", "pallet-referenda?/runtime-benchmarks", "pallet-remark?/runtime-benchmarks", + "pallet-revive-mock-network?/runtime-benchmarks", + "pallet-revive?/runtime-benchmarks", "pallet-root-offences?/runtime-benchmarks", "pallet-safe-mode?/runtime-benchmarks", "pallet-salary?/runtime-benchmarks", @@ -330,6 +335,7 @@ runtime-benchmarks = [ "parachains-common?/runtime-benchmarks", "polkadot-cli?/runtime-benchmarks", "polkadot-node-metrics?/runtime-benchmarks", + "polkadot-parachain-lib?/runtime-benchmarks", "polkadot-parachain-primitives?/runtime-benchmarks", "polkadot-primitives?/runtime-benchmarks", "polkadot-runtime-common?/runtime-benchmarks", @@ -432,6 +438,7 @@ try-runtime = [ "pallet-recovery?/try-runtime", "pallet-referenda?/try-runtime", "pallet-remark?/try-runtime", + "pallet-revive?/try-runtime", "pallet-root-offences?/try-runtime", "pallet-root-testing?/try-runtime", "pallet-safe-mode?/try-runtime", @@ -459,6 +466,7 @@ try-runtime = [ "pallet-xcm-bridge-hub?/try-runtime", "pallet-xcm?/try-runtime", "polkadot-cli?/try-runtime", + "polkadot-parachain-lib?/try-runtime", "polkadot-runtime-common?/try-runtime", "polkadot-runtime-parachains?/try-runtime", "polkadot-sdk-frame?/try-runtime", @@ -484,6 +492,7 @@ serde = [ "pallet-parameters?/serde", "pallet-referenda?/serde", "pallet-remark?/serde", + "pallet-revive?/serde", "pallet-state-trie-migration?/serde", "pallet-tips?/serde", "pallet-transaction-payment?/serde", @@ -527,37 +536,8 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "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", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-fixtures", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ - "assets-common", - "binary-merkle-tree", - "bp-header-chain", - "bp-messages", - "bp-parachains", - "bp-polkadot", - "bp-polkadot-core", - "bp-relayers", - "bp-runtime", - "bp-test-utils", - "bp-xcm-bridge-hub", - "bp-xcm-bridge-hub-router", - "bridge-hub-common", - "bridge-runtime-common", - "cumulus-pallet-aura-ext", - "cumulus-pallet-dmp-queue", - "cumulus-pallet-parachain-system", - "cumulus-pallet-parachain-system-proc-macro", - "cumulus-pallet-session-benchmarking", - "cumulus-pallet-solo-to-para", - "cumulus-pallet-xcm", - "cumulus-pallet-xcmp-queue", - "cumulus-ping", - "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", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", @@ -571,134 +551,8 @@ runtime = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", - "pallet-alliance", - "pallet-asset-conversion", - "pallet-asset-conversion-ops", - "pallet-asset-conversion-tx-payment", - "pallet-asset-rate", - "pallet-asset-tx-payment", - "pallet-assets", - "pallet-assets-freezer", - "pallet-atomic-swap", - "pallet-aura", - "pallet-authority-discovery", - "pallet-authorship", - "pallet-babe", - "pallet-bags-list", - "pallet-balances", - "pallet-beefy", - "pallet-beefy-mmr", - "pallet-bounties", - "pallet-bridge-grandpa", - "pallet-bridge-messages", - "pallet-bridge-parachains", - "pallet-bridge-relayers", - "pallet-broker", - "pallet-child-bounties", - "pallet-collator-selection", - "pallet-collective", - "pallet-collective-content", - "pallet-contracts", - "pallet-contracts-proc-macro", - "pallet-contracts-uapi", - "pallet-conviction-voting", - "pallet-core-fellowship", - "pallet-delegated-staking", - "pallet-democracy", - "pallet-dev-mode", - "pallet-election-provider-multi-phase", - "pallet-election-provider-support-benchmarking", - "pallet-elections-phragmen", - "pallet-fast-unstake", - "pallet-glutton", - "pallet-grandpa", - "pallet-identity", - "pallet-im-online", - "pallet-indices", - "pallet-insecure-randomness-collective-flip", - "pallet-lottery", - "pallet-membership", - "pallet-message-queue", - "pallet-migrations", - "pallet-mixnet", - "pallet-mmr", - "pallet-multisig", - "pallet-nft-fractionalization", - "pallet-nfts", - "pallet-nfts-runtime-api", - "pallet-nis", - "pallet-node-authorization", - "pallet-nomination-pools", - "pallet-nomination-pools-benchmarking", - "pallet-nomination-pools-runtime-api", - "pallet-offences", - "pallet-offences-benchmarking", - "pallet-paged-list", - "pallet-parameters", - "pallet-preimage", - "pallet-proxy", - "pallet-ranked-collective", - "pallet-recovery", - "pallet-referenda", - "pallet-remark", - "pallet-root-offences", - "pallet-root-testing", - "pallet-safe-mode", - "pallet-salary", - "pallet-scheduler", - "pallet-scored-pool", - "pallet-session", - "pallet-session-benchmarking", - "pallet-skip-feeless-payment", - "pallet-society", - "pallet-staking", - "pallet-staking-reward-curve", - "pallet-staking-reward-fn", - "pallet-staking-runtime-api", - "pallet-state-trie-migration", - "pallet-statement", - "pallet-sudo", - "pallet-timestamp", - "pallet-tips", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "pallet-transaction-storage", - "pallet-treasury", - "pallet-tx-pause", - "pallet-uniques", - "pallet-utility", - "pallet-vesting", - "pallet-whitelist", - "pallet-xcm", - "pallet-xcm-benchmarks", - "pallet-xcm-bridge-hub", - "pallet-xcm-bridge-hub-router", - "parachains-common", - "polkadot-core-primitives", - "polkadot-parachain-primitives", - "polkadot-primitives", - "polkadot-runtime-common", - "polkadot-runtime-metrics", - "polkadot-runtime-parachains", "polkadot-sdk-frame", "polkadot-sdk-frame?/runtime", - "sc-chain-spec-derive", - "sc-tracing-proc-macro", - "slot-range-helper", - "snowbridge-beacon-primitives", - "snowbridge-core", - "snowbridge-ethereum", - "snowbridge-outbound-queue-merkle-tree", - "snowbridge-outbound-queue-runtime-api", - "snowbridge-pallet-ethereum-client", - "snowbridge-pallet-ethereum-client-fixtures", - "snowbridge-pallet-inbound-queue", - "snowbridge-pallet-inbound-queue-fixtures", - "snowbridge-pallet-outbound-queue", - "snowbridge-pallet-system", - "snowbridge-router-primitives", - "snowbridge-runtime-common", - "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", @@ -745,21 +599,16 @@ runtime = [ "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", - "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", - "substrate-bip39", - "testnet-parachains-constants", - "tracing-gum-proc-macro", - "xcm-procedural", - "xcm-runtime-apis", ] -node = ["asset-test-utils", "bridge-hub-test-utils", "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-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "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", "polkadot-node-core-runtime-api", "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] +node = ["asset-test-utils", "bridge-hub-test-utils", "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-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "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", "polkadot-node-core-runtime-api", "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] tuples-96 = [ "frame-support-procedural?/tuples-96", "frame-support?/tuples-96", ] +riscv = [ + "pallet-revive-fixtures?/riscv", + "pallet-revive?/riscv", +] [package.edition] workspace = true @@ -1332,6 +1181,26 @@ path = "../substrate/frame/remark" default-features = false optional = true +[dependencies.pallet-revive] +path = "../substrate/frame/revive" +default-features = false +optional = true + +[dependencies.pallet-revive-fixtures] +path = "../substrate/frame/revive/fixtures" +default-features = false +optional = true + +[dependencies.pallet-revive-proc-macro] +path = "../substrate/frame/revive/proc-macro" +default-features = false +optional = true + +[dependencies.pallet-revive-uapi] +path = "../substrate/frame/revive/uapi" +default-features = false +optional = true + [dependencies.pallet-root-offences] path = "../substrate/frame/root-offences" default-features = false @@ -2022,6 +1891,11 @@ path = "../substrate/frame/contracts/mock-network" default-features = false optional = true +[dependencies.pallet-revive-mock-network] +path = "../substrate/frame/revive/mock-network" +default-features = false +optional = true + [dependencies.pallet-transaction-payment-rpc] path = "../substrate/frame/transaction-payment/rpc" default-features = false @@ -2212,6 +2086,11 @@ path = "../polkadot/node/overseer" default-features = false optional = true +[dependencies.polkadot-parachain-lib] +path = "../cumulus/polkadot-parachain/polkadot-parachain-lib" +default-features = false +optional = true + [dependencies.polkadot-rpc] path = "../polkadot/rpc" default-features = false @@ -2608,5 +2487,5 @@ default-features = false optional = true [package.metadata.docs.rs] -features = ["node", "runtime"] +features = ["node", "runtime-full"] targets = ["x86_64-unknown-linux-gnu"] diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 07f1cbad1262..b7b9c15fe588 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -109,7 +109,7 @@ pub use cumulus_client_network; #[cfg(feature = "cumulus-client-parachain-inherent")] pub use cumulus_client_parachain_inherent; -/// Cumulus-specific networking protocol. +/// Parachain PoV recovery. #[cfg(feature = "cumulus-client-pov-recovery")] pub use cumulus_client_pov_recovery; @@ -272,7 +272,7 @@ pub use frame_system_benchmarking; #[cfg(feature = "frame-system-rpc-runtime-api")] pub use frame_system_rpc_runtime_api; -/// FRAME pallet for democracy. +/// Supporting types for try-runtime, testing and dry-running commands. #[cfg(feature = "frame-try-runtime")] pub use frame_try_runtime; @@ -576,6 +576,26 @@ pub use pallet_referenda; #[cfg(feature = "pallet-remark")] pub use pallet_remark; +/// FRAME pallet for PolkaVM contracts. +#[cfg(feature = "pallet-revive")] +pub use pallet_revive; + +/// Fixtures for testing and benchmarking. +#[cfg(feature = "pallet-revive-fixtures")] +pub use pallet_revive_fixtures; + +/// A mock network for testing pallet-revive. +#[cfg(feature = "pallet-revive-mock-network")] +pub use pallet_revive_mock_network; + +/// Procedural macros used in pallet_revive. +#[cfg(feature = "pallet-revive-proc-macro")] +pub use pallet_revive_proc_macro; + +/// Exposes all the host functions that a contract can import. +#[cfg(feature = "pallet-revive-uapi")] +pub use pallet_revive_uapi; + /// FRAME root offences pallet. #[cfg(feature = "pallet-root-offences")] pub use pallet_root_offences; @@ -689,7 +709,7 @@ pub use pallet_utility; #[cfg(feature = "pallet-vesting")] pub use pallet_vesting; -/// FRAME pallet for whitelisting call, and dispatch from specific origin. +/// FRAME pallet for whitelisting calls, and dispatching from a specific origin. #[cfg(feature = "pallet-whitelist")] pub use pallet_whitelist; @@ -882,6 +902,10 @@ pub use polkadot_node_subsystem_util; #[cfg(feature = "polkadot-overseer")] pub use polkadot_overseer; +/// Helper library that can be used to build a parachain node. +#[cfg(feature = "polkadot-parachain-lib")] +pub use polkadot_parachain_lib; + /// Types and utilities for creating and working with parachains. #[cfg(feature = "polkadot-parachain-primitives")] pub use polkadot_parachain_primitives;